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