createItemNumbersTest.py

You can view and download this file on Github: createItemNumbersTest.py

  1#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  2# This is an EXUDYN example
  3#
  4# Details:  The Create functions that connect two items take them in itemNumbers (#2863): a body at a
  5#           local position, a node or a marker, as far as the function accepts it; CreateForce and
  6#           CreateTorque take one item in itemNumber. Each function is called with every kind it
  7#           accepts, and the motion is the same for each; a kind it does not accept is a ValueError, as
  8#           is a local position for a node. bodyOrNodeList, and bodyNumbers given together with
  9#           itemNumbers, are a TypeError; bodyNumbers alone is the deprecated name and does the same as
 10#           itemNumbers.
 11#
 12# Author:   Johannes Gerstmayr
 13# Date:     2026-10-05
 14#
 15# Copyright:This file is part of Exudyn. Exudyn is free software. You can redistribute it and/or modify it under the terms of the Exudyn license. See 'LICENSE.txt' for more details.
 16#
 17#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 18
 19import exudyn as exu
 20from exudyn.utilities import * #includes itemInterface and rigidBodyUtilities
 21import numpy as np
 22import warnings
 23
 24testIsActive = exu.sys.get('testIsActive', False)
 25
 26errors = 0
 27def Check(condition, text):
 28    global errors
 29    if not condition:
 30        errors += 1
 31        exu.Print('createItemNumbersTest failed:', text)
 32
 33def Solve(mbs, endTime=0.2):
 34    mbs.Assemble()
 35    simulationSettings = exu.SimulationSettings()
 36    simulationSettings.timeIntegration.numberOfSteps = 100
 37    simulationSettings.timeIntegration.endTime = endTime
 38    simulationSettings.solution.file.write = False
 39    simulationSettings.timeIntegration.verboseMode = 0
 40    mbs.SolveDynamic(simulationSettings)
 41
 42#%%+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 43#a mass point, connected to the ground as a body, through its node, or through a marker
 44def MassPoint(kind, Connect):
 45    """the position of a mass point after 0.2 s, which Connect(mbs, oGround, item) connects to the ground"""
 46    SC = exu.SystemContainer()
 47    mbs = SC.AddSystem()
 48    oGround = mbs.CreateGround()
 49    oMass = mbs.CreateMassPoint(referencePosition=[1,0,0], initialVelocity=[0,0.5,0], mass=2, gravity=[0,-9.81,0])
 50    nMass = mbs.GetObject(oMass)['nodeNumber']
 51    item = {'body': oMass, 'node': nMass,
 52            'marker': mbs.AddMarker(MarkerNodePosition(nodeNumber=nMass)) if kind == 'marker' else None}[kind]
 53    Connect(mbs, oGround, item)
 54    Solve(mbs)
 55    return mbs.GetNodeOutput(nMass, exu.OutputVariableType.Position)
 56
 57massPointConnections = {
 58    'CreateSpringDamper': lambda mbs, g, item: mbs.CreateSpringDamper(itemNumbers=[g, item], localPosition0=[0,0.5,0], stiffness=200, damping=2),
 59    'CreateCartesianSpringDamper': lambda mbs, g, item: mbs.CreateCartesianSpringDamper(itemNumbers=[g, item], localPosition0=[1,0,0],
 60                                                                                        stiffness=[100,200,0], damping=[1,2,0]),
 61    'CreateDistanceConstraint': lambda mbs, g, item: mbs.CreateDistanceConstraint(itemNumbers=[g, item], localPosition0=[0,0.5,0]),
 62    'CreateForce': lambda mbs, g, item: mbs.CreateForce(itemNumber=item, loadVector=[5,0,0]),
 63    'CreateSphereSphereContact': lambda mbs, g, item: mbs.CreateSphereSphereContact(itemNumbers=[g, item], localPosition0=[1,-0.6,0],
 64                                                                                    spheresRadii=[0.5, 0.1], contactStiffness=1e4,
 65                                                                                    contactDamping=10),
 66    }
 67u = 0
 68for (name, Connect) in massPointConnections.items():
 69    results = [MassPoint(kind, Connect) for kind in ['body', 'node', 'marker']]
 70    Check(np.linalg.norm(results[1]-results[0]) < 1e-12 and np.linalg.norm(results[2]-results[0]) < 1e-12, name + ': ' + str(results))
 71    u += np.sum(results[0])
 72    exu.Print(name, results[0])
 73
 74#a coordinate constraint takes a body or a node, and None or a ground object for the ground
 75for name in ['bodyGround', 'nodeGround', 'bodyNone']:
 76    def Constrain(mbs, g, item):
 77        mbs.CreateCoordinateConstraint(itemNumbers=[None if name == 'bodyNone' else g, item], coordinates=[None, 1])
 78    result = MassPoint('node' if name == 'nodeGround' else 'body', Constrain)
 79    Check(abs(result[1]) < 1e-12 and abs(result[0]-1) < 1e-12, 'CreateCoordinateConstraint ' + name + ': ' + str(result))
 80
 81#the coordinate of a body with several nodes counts over its nodes
 82SC = exu.SystemContainer()
 83mbs = SC.AddSystem()
 84n0 = mbs.AddNode(NodePoint2DSlope1(referenceCoordinates=[0,0,1,0]))
 85n1 = mbs.AddNode(NodePoint2DSlope1(referenceCoordinates=[1,0,1,0]))
 86oCable = mbs.AddObject(ObjectANCFCable2D(nodeNumbers=[n0, n1], length=1, massPerLength=1,
 87                                         bendingStiffness=1, axialStiffness=100))
 88oConstraint = mbs.CreateCoordinateConstraint(itemNumbers=[None, oCable], coordinates=[None, 5])
 89marker = mbs.GetMarker(mbs.GetObject(oConstraint)['markerNumbers'][1])
 90Check(int(marker['nodeNumber']) == int(n1) and marker['coordinate'] == 1, 'CreateCoordinateConstraint of a cable: ' + str(marker))
 91
 92#%%+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 93#ONLY the regular module refuses the wrong kind of item: exudynCPPfast skips the checks of the Create functions by
 94#design, so there a wrong item is not refused, or fails later with another error (#2911)
 95hasItemChecks = '[FAST]' not in exu.config.Version(True)
 96
 97#a rigid body, connected as a body, through its node (where accepted) or through a marker
 98def RigidBody(kind, Connect):
 99    """the position and the rotation matrix of a rigid body after 0.2 s, which Connect(mbs, oGround, item) connects"""
100    SC = exu.SystemContainer()
101    mbs = SC.AddSystem()
102    oGround = mbs.CreateGround()
103    oBody = mbs.CreateRigidBody(inertia=InertiaCuboid(density=1000, sideLengths=[1,0.1,0.1]), referencePosition=[1,0,0],
104                                initialAngularVelocity=[0,0.5,1], gravity=[0,-9.81,0])
105    nBody = mbs.GetObject(oBody)['nodeNumber']
106    item = {'body': oBody, 'node': nBody, 'marker': mbs.AddMarker(MarkerBodyRigid(bodyNumber=oBody, localPosition=[-0.5,0,0]))
107            if kind == 'marker' else None}[kind]
108    Connect(mbs, oGround, item, kind)
109    Solve(mbs)
110    return np.hstack([mbs.GetNodeOutput(nBody, exu.OutputVariableType.Position),
111                      mbs.GetNodeOutput(nBody, exu.OutputVariableType.RotationMatrix)])
112
113def JointPosition(kind):
114    """the joint at [0.5,0,0]: given as position, or set by the marker"""
115    return [] if kind == 'marker' else [0.5,0,0]
116
117rigidBodyConnections = {
118    'CreateRevoluteJoint': lambda mbs, g, item, kind: mbs.CreateRevoluteJoint(itemNumbers=[g, item], position=JointPosition(kind), axis=[0,0,1]),
119    'CreatePrismaticJoint': lambda mbs, g, item, kind: mbs.CreatePrismaticJoint(itemNumbers=[g, item], position=JointPosition(kind), axis=[0,1,0]),
120    'CreateSphericalJoint': lambda mbs, g, item, kind: mbs.CreateSphericalJoint(itemNumbers=[g, item], position=JointPosition(kind)),
121    'CreateGenericJoint': lambda mbs, g, item, kind: mbs.CreateGenericJoint(itemNumbers=[g, item], position=JointPosition(kind),
122                                                                            constrainedAxes=[1,1,1,1,1,0]),
123    'CreateTorsionalSpringDamper': lambda mbs, g, item, kind: mbs.CreateTorsionalSpringDamper(itemNumbers=[g, item], position=JointPosition(kind),
124                                                                                              axis=[0,0,1], stiffness=10, damping=0.1),
125    'CreateRigidBodySpringDamper': lambda mbs, g, item, kind: mbs.CreateRigidBodySpringDamper(itemNumbers=[g, item],
126                                      localPosition0=[0.5,0,0] if kind == 'marker' else [1,0,0],
127                                      stiffness=np.diag([1e3,1e3,1e3,10,10,10]), damping=np.diag([1,1,1,0.1,0.1,0.1])),
128    'CreateTorque': lambda mbs, g, item, kind: mbs.CreateTorque(itemNumber=item, loadVector=[0,0,2]),
129    }
130#which give the same result through the node: the spring-damper and the torque, at the center of mass; the joints take no node
131takesNode = ['CreateRigidBodySpringDamper', 'CreateTorque']
132for (name, Connect) in rigidBodyConnections.items():
133    kinds = ['body', 'marker'] + (['node'] if name in takesNode else [])
134    if name in takesNode: #the marker at the center of mass, as body and node
135        Connect0 = Connect
136        def Connect(mbs, g, item, kind, Connect0=Connect0):
137            if kind == 'marker':
138                item = mbs.AddMarker(MarkerBodyRigid(bodyNumber=mbs.GetMarker(item)['bodyNumber'], localPosition=[0,0,0]))
139                kind = 'body'
140            return Connect0(mbs, g, item, kind)
141    results = [RigidBody(kind, Connect) for kind in kinds]
142    Check(all(np.linalg.norm(result-results[0]) < 1e-10 for result in results), name + ': ' + str([list(r[:3]) for r in results]))
143    u += np.sum(results[0][:3])
144    exu.Print(name, results[0][:3])
145    if name not in takesNode and hasItemChecks:
146        try:
147            RigidBody('node', Connect)
148            Check(False, name + ' accepts a node')
149        except ValueError: #the wrong kind of item
150            pass
151
152#%%+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
153#the sphere of a sphere-triangle contact may be on a node or a marker, the triangle only on a body
154SC = exu.SystemContainer()
155mbs = SC.AddSystem()
156oGround = mbs.CreateGround()
157oMass = mbs.CreateMassPoint(referencePosition=[0.2,0.2,0.2], mass=1)
158nMass = mbs.GetObject(oMass)['nodeNumber']
159mNode = mbs.AddMarker(MarkerNodeRigid(nodeNumber=mbs.CreateRigidBody(inertia=InertiaSphere(1,0.1), returnDict=True)['nodeNumber']))
160mbs.CreateSphereTriangleContact(itemNumbers=[mNode, oGround], sphereRadius=0.1, contactStiffness=1e4)
161mbs.CreateSphereQuadContact(itemNumbers=[nMass, oGround], sphereRadius=0.1, contactStiffness=1e4)
162if hasItemChecks:
163    for itemNumbers in [[oGround, nMass], [oMass, mNode]]:
164        try:
165            mbs.CreateSphereQuadContact(itemNumbers=itemNumbers, sphereRadius=0.1, contactStiffness=1e4)
166            Check(False, 'the quad accepts ' + str(itemNumbers[1]))
167        except ValueError: #the wrong kind of item
168            pass
169    #a node needs no local position
170    try:
171        mbs.CreateSpringDamper(itemNumbers=[oGround, nMass], localPosition1=[1,0,0])
172        Check(False, 'a node with a local position')
173    except ValueError:
174        pass
175
176#%%+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
177#bodyNumbers is the deprecated name of itemNumbers; bodyOrNodeList is not accepted, nor two names at once
178deprecations = exu.special.deprecations
179warnOnceStored = deprecations.warnOnce
180deprecations.warnOnce = False
181with warnings.catch_warnings(record=True) as caught:
182    warnings.simplefilter('always')
183    resultOld = MassPoint('body', lambda mbs, g, item: mbs.CreateSpringDamper(bodyNumbers=[g, item], localPosition0=[0,0.5,0], stiffness=200, damping=2))
184    forceOld = MassPoint('body', lambda mbs, g, item: mbs.CreateForce(bodyNumber=item, loadVector=[5,0,0]))
185deprecations.warnOnce = warnOnceStored
186caught = [str(entry.message) for entry in caught if issubclass(entry.category, DeprecationWarning)]
187Check(len(caught) == 2 and 'use itemNumbers' in caught[0] and 'use itemNumber' in caught[1], 'warnings: ' + str(caught))
188Check(np.linalg.norm(resultOld - MassPoint('body', massPointConnections['CreateSpringDamper'])) == 0, 'bodyNumbers')
189Check(np.linalg.norm(forceOld - MassPoint('body', massPointConnections['CreateForce'])) == 0, 'bodyNumber')
190for (arguments, text) in [({'bodyOrNodeList': [oGround, oMass]}, 'use itemNumbers'),
191                          ({'itemNumbers': [oGround, oMass], 'bodyNumbers': [oGround, oMass]}, 'use itemNumbers only')]:
192    try:
193        with warnings.catch_warnings():
194            warnings.simplefilter('ignore')
195            mbs.CreateDistanceConstraint(**arguments)
196        Check(False, str(arguments))
197    except TypeError as error:
198        Check(text in str(error), str(error))
199
200exu.Print('createItemNumbersTest: errors', errors)
201u += errors
202exu.Print('solution of createItemNumbersTest=', u)
203
204exu.sys['testResult'] = u