homogeneousTransformationParameterTest.py
You can view and download this file on Github: homogeneousTransformationParameterTest.py
1#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2# This is an EXUDYN example
3#
4# Details: The HT parameters of the items (#2793): ObjectGround takes its frame as referencePosition and
5# referenceRotation or at once as referenceHT - a 4x4 matrix, its 16 values or an exu.HT. A parameter left
6# None is not given; GetObject lists all three, and such a dictionary can be added again; an HT and a part
7# given together must agree, else the item raises. SetObjectParameter of a part keeps the other part.
8# CreateGround and CreateRigidBody take referenceHT and initialHT for the position and rotation matrix;
9# for the three rigid body nodes they give the same coordinates, and an HT with one of its parts raises.
10# The rigid markers take localHT, the marker frame being the body or node frame times localHT: the rotation
11# matrix and the local angular velocity of MarkerBodyRigid, MarkerNodeRigid and MarkerKinematicTreeRigid; a node marker refuses a
12# translation; a joint whose markers carry its rotation in localHT moves as one with rotationMarker0/1.
13# ObjectKinematicTree takes its joint transformations and offsets as one list jointHTs and moves the same;
14# numpy reads an exu.HT as its 4x4 matrix, and the HT functions of rigidBodyUtilities and the robotics
15# classes take it.
16#
17# Author: Johannes Gerstmayr
18# Date: 2026-10-03
19#
20# 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.
21#
22#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
23
24import exudyn as exu
25from exudyn.utilities import * #includes itemInterface and rigidBodyUtilities
26import numpy as np
27import warnings
28
29testIsActive = exu.sys.get('testIsActive', False)
30
31SC = exu.SystemContainer()
32mbs = SC.AddSystem()
33
34A = RotXYZ2RotationMatrix([0.3, -0.2, 0.7])
35p = np.array([1., 2., 3.])
36H44 = HomogeneousTransformation(A, p)
37localPosition = [0.5, -0.1, 0.2]
38pointExpected = A @ localPosition + p
39
40errors = []
41total = 0.
42#the same ground frame in four ways
43grounds = [mbs.AddObject(ObjectGround(referencePosition=p, referenceRotation=A)),
44 mbs.AddObject(ObjectGround(referenceHT=H44)),
45 mbs.AddObject(ObjectGround(referenceHT=H44.flatten())), #16 values, as a sensor stores an HT
46 mbs.AddObject(ObjectGround(referenceHT=exu.HT(A, p)))]
47for g in grounds:
48 errors += [np.abs(mbs.GetObjectOutputBody(g, exu.OutputVariableType.Position, localPosition,
49 exu.ConfigurationType.Reference) - pointExpected).max()]
50
51#the dictionary lists the frame three times; it can be added again
52d = mbs.GetObject(grounds[1])
53errors += [np.abs(d['referencePosition'] - p).max(), np.abs(d['referenceRotation'] - A).max(),
54 np.abs(d['referenceHT'] - H44).max()]
55d = {key: d[key] for key in ['objectType', 'referencePosition', 'referenceRotation', 'referenceHT']}
56gCopy = mbs.AddObject(d)
57errors += [np.abs(mbs.GetObjectParameter(gCopy, 'referenceHT') - H44).max()]
58
59#default: the identity
60gDefault = mbs.AddObject(ObjectGround())
61errors += [np.abs(mbs.GetObjectParameter(gDefault, 'referenceHT') - np.eye(4)).max()]
62
63#a part keeps the other part
64mbs.SetObjectParameter(gDefault, 'referencePosition', p)
65mbs.SetObjectParameter(gDefault, 'referenceRotation', A)
66errors += [np.abs(mbs.GetObjectParameter(gDefault, 'referenceHT') - H44).max()]
67mbs.SetObjectParameter(gDefault, 'referenceHT', np.eye(4))
68errors += [np.abs(mbs.GetObjectParameter(gDefault, 'referencePosition')).max()]
69
70#an HT and a part that differ: the item raises
71raised = 0
72for parameters in [{'referenceHT': H44, 'referencePosition': [0, 0, 0]},
73 {'referenceHT': H44, 'referenceRotation': np.eye(3)},
74 {'referenceHT': A}]: #not a 4x4 matrix
75 try:
76 mbs.AddObject(ObjectGround(**parameters))
77 except Exception:
78 raised += 1
79errors += [3 - raised]
80
81#CreateGround and CreateRigidBody: referenceHT and initialHT for the position and rotation matrix (#2794)
82gCreated = mbs.CreateGround(referenceHT=H44)
83errors += [np.abs(mbs.GetObjectParameter(gCreated, 'referenceHT') - H44).max()]
84Ainit = RotXYZ2RotationMatrix([0.1, 0.2, -0.3])
85dInit = np.array([0.1, -0.2, 0.05])
86inertia = InertiaCuboid(density=1000, sideLengths=[0.4, 0.2, 0.1])
87for nodeType in [exu.NodeType.RotationEulerParameters, exu.NodeType.RotationRxyz, exu.NodeType.RotationRotationVector]:
88 bParts = mbs.CreateRigidBody(inertia=inertia, nodeType=nodeType, referencePosition=p, referenceRotationMatrix=A,
89 initialDisplacement=dInit, initialRotationMatrix=Ainit, returnDict=True)
90 bHT = mbs.CreateRigidBody(inertia=inertia, nodeType=nodeType, referenceHT=exu.HT(A, p),
91 initialHT=HomogeneousTransformation(Ainit, dInit), returnDict=True)
92 for key in ['referenceCoordinates', 'initialCoordinates']:
93 errors += [np.abs(np.array(mbs.GetNodeParameter(bParts['nodeNumber'], key))
94 - mbs.GetNodeParameter(bHT['nodeNumber'], key)).max()]
95raised = 0
96for parameters in [{'referenceHT': H44, 'referencePosition': p}, {'initialHT': H44, 'initialRotationMatrix': A},
97 {'referenceHT': A}]:
98 try:
99 mbs.CreateRigidBody(inertia=inertia, **parameters)
100 except Exception:
101 raised += 1
102errors += [3 - raised]
103
104#the rigid markers (#2795): the marker frame is the body or node frame times localHT
105SC2 = exu.SystemContainer()
106mbs2 = SC2.AddSystem()
107Al = RotXYZ2RotationMatrix([0.4, 0.1, -0.5])
108pl = np.array([0.2, -0.1, 0.3])
109oBody = mbs2.CreateRigidBody(inertia=inertia, referencePosition=p, referenceRotationMatrix=A,
110 initialAngularVelocity=[0.3, -1.2, 2.], initialVelocity=[0.1, 0.2, 0.3])
111nBody = mbs2.GetObject(oBody)['nodeNumber']
112mBodyHT = mbs2.AddMarker(MarkerBodyRigid(bodyNumber=oBody, localHT=HomogeneousTransformation(Al, pl)))
113mBodyParts = mbs2.AddMarker(MarkerBodyRigid(bodyNumber=oBody, localPosition=pl))
114mNodeHT = mbs2.AddMarker(MarkerNodeRigid(nodeNumber=nBody, localHT=exu.HT(rotation=Al)))
115mbs2.Assemble()
116OV = exu.OutputVariableType
117omegaLocal = mbs2.GetMarkerOutput(mBodyParts, OV.AngularVelocityLocal)
118for m in [mBodyHT, mNodeHT]:
119 errors += [np.abs(mbs2.GetMarkerOutput(m, OV.RotationMatrix).reshape(3,3) - A @ Al).max(),
120 np.abs(mbs2.GetMarkerOutput(m, OV.AngularVelocityLocal) - Al.T @ omegaLocal).max(),
121 np.abs(mbs2.GetMarkerOutput(m, OV.AngularVelocity) - mbs2.GetMarkerOutput(mBodyParts, OV.AngularVelocity)).max()]
122errors += [np.abs(mbs2.GetMarkerOutput(mBodyHT, OV.Position) - mbs2.GetMarkerOutput(mBodyParts, OV.Position)).max(),
123 np.abs(mbs2.GetMarkerOutput(mBodyHT, OV.Velocity) - mbs2.GetMarkerOutput(mBodyParts, OV.Velocity)).max(),
124 np.abs(mbs2.GetMarker(mBodyHT)['localPosition'] - pl).max()]
125
126#a kinematic tree: the rotation of localHT on the link frame
127nTree = mbs2.AddNode(NodeGenericODE2(referenceCoordinates=[0.], initialCoordinates=[0.3],
128 initialCoordinates_t=[0.7], numberOfODE2Coordinates=1))
129oTree = mbs2.AddObject(ObjectKinematicTree(nodeNumber=nTree, jointTypes=[exu.JointType.RevoluteZ], linkParents=[-1],
130 jointTransformations=exu.Matrix3DList([np.eye(3)]),
131 jointOffsets=exu.Vector3DList([[0,0,0]]),
132 linkInertiasCOM=exu.Matrix3DList([np.eye(3)]),
133 linkCOMs=exu.Vector3DList([[0,0,0]]), linkMasses=[2.]))
134mLinkHT = mbs2.AddMarker(MarkerKinematicTreeRigid(objectNumber=oTree, linkNumber=0, localHT=HomogeneousTransformation(Al, pl)))
135mLink = mbs2.AddMarker(MarkerKinematicTreeRigid(objectNumber=oTree, linkNumber=0, localPosition=pl))
136mbs2.Assemble()
137errors += [np.abs(mbs2.GetMarkerOutput(mLinkHT, OV.RotationMatrix).reshape(3,3)
138 - mbs2.GetMarkerOutput(mLink, OV.RotationMatrix).reshape(3,3) @ Al).max(),
139 np.abs(mbs2.GetMarkerOutput(mLinkHT, OV.AngularVelocityLocal) - Al.T @ mbs2.GetMarkerOutput(mLink, OV.AngularVelocityLocal)).max(),
140 np.abs(mbs2.GetMarkerOutput(mLinkHT, OV.Position) - mbs2.GetMarkerOutput(mLink, OV.Position)).max()]
141
142#a node marker with a translation in localHT is refused at Assemble
143mbs2.AddMarker(MarkerNodeRigid(nodeNumber=nBody, localHT=HomogeneousTransformation(np.eye(3), pl)))
144try:
145 mbs2.Assemble()
146 errors += [1]
147except Exception:
148 errors += [0]
149
150#a joint: the rotation in localHT of its markers or in rotationMarker0/1 of the joint gives the same motion
151def SwingingBody(useLocalHT, nodeMarker):
152 SCj = exu.SystemContainer()
153 mbsj = SCj.AddSystem()
154 gGround = mbsj.AddObject(ObjectGround())
155 oPendulum = mbsj.CreateRigidBody(inertia=inertia, referencePosition=[0.5, 0, 0], gravity=[0, -9.81, 0],
156 initialAngularVelocity=[0.2, 0.1, 0.])
157 Aaxis = RotXYZ2RotationMatrix([0.3, -0.4, 0.2]) #the joint axis z turned
158 HT0 = HomogeneousTransformation(Aaxis, [0, 0, 0])
159 HT1 = HomogeneousTransformation(Aaxis, [-0.5, 0, 0])
160 mGround = mbsj.AddMarker(MarkerBodyRigid(bodyNumber=gGround, localHT=HT0 if useLocalHT else None))
161 if nodeMarker: #the joint at the node of the body, its rotation in the node marker
162 mPendulum = mbsj.AddMarker(MarkerNodeRigid(nodeNumber=mbsj.GetObject(oPendulum)['nodeNumber'],
163 localHT=HT0 if useLocalHT else None))
164 mGround = mbsj.AddMarker(MarkerBodyRigid(bodyNumber=gGround, localPosition=[0.5, 0, 0],
165 localHT=None))
166 if useLocalHT:
167 mbsj.SetMarkerParameter(mGround, 'localHT', HomogeneousTransformation(Aaxis, [0.5, 0, 0]))
168 else:
169 mPendulum = mbsj.AddMarker(MarkerBodyRigid(bodyNumber=oPendulum, localHT=HT1 if useLocalHT else None,
170 localPosition=None if useLocalHT else [-0.5, 0, 0]))
171 if useLocalHT:
172 mbsj.AddObject(ObjectJointGeneric(markerNumbers=[mGround, mPendulum], constrainedAxes=[1,1,1,1,1,0]))
173 else: #the deprecated rotationMarker0/1, compared with localHT; their warning is expected (#2873)
174 with warnings.catch_warnings():
175 warnings.simplefilter('ignore', DeprecationWarning)
176 mbsj.AddObject(ObjectJointGeneric(markerNumbers=[mGround, mPendulum], constrainedAxes=[1,1,1,1,1,0],
177 rotationMarker0=Aaxis, rotationMarker1=Aaxis))
178 mbsj.Assemble()
179 simulationSettings = exu.SimulationSettings()
180 simulationSettings.timeIntegration.newton.useModifiedNewton = False #Just for the test; modified Newton is usually faster
181 simulationSettings.timeIntegration.numberOfSteps = 200
182 simulationSettings.timeIntegration.endTime = 0.5
183 simulationSettings.timeIntegration.verboseMode = 0
184 mbsj.SolveDynamic(simulationSettings)
185 return mbsj.GetObjectOutputBody(oPendulum, OV.Position, [0.5, 0.1, 0.])
186
187for nodeMarker in [False, True]:
188 pRotationMarkers = SwingingBody(False, nodeMarker)
189 pLocalHT = SwingingBody(True, nodeMarker)
190 exu.Print('joint with localHT, nodeMarker =', nodeMarker, ':', pLocalHT, ', difference', np.abs(pLocalHT - pRotationMarkers).max())
191 errors += [np.abs(pLocalHT - pRotationMarkers).max()*1e6] #both solved the same equations
192 total += np.sum(pLocalHT)
193
194#ObjectKinematicTree: jointHTs, the joint transformations and offsets as one list of HTs (#2798)
195def SwingingTree(useJointHTs):
196 SCk = exu.SystemContainer()
197 mbsk = SCk.AddSystem()
198 rotations = [RotXYZ2RotationMatrix([0.1, 0.2, 0.3]), RotXYZ2RotationMatrix([-0.3, 0.1, 0.])]
199 offsets = [[0., 0., 0.], [0.5, 0., 0.1]]
200 nTree = mbsk.AddNode(NodeGenericODE2(referenceCoordinates=[0., 0.], initialCoordinates=[0.3, -0.2],
201 initialCoordinates_t=[0., 0.], numberOfODE2Coordinates=2))
202 jointData = ({'jointHTs': [exu.HT(rotations[0], offsets[0]), HomogeneousTransformation(rotations[1], offsets[1])]}
203 if useJointHTs else {'jointTransformations': exu.Matrix3DList(rotations), 'jointOffsets': exu.Vector3DList(offsets)})
204 oTree = mbsk.AddObject(ObjectKinematicTree(nodeNumber=nTree, jointTypes=[exu.JointType.RevoluteZ]*2, linkParents=[-1, 0],
205 linkInertiasCOM=exu.Matrix3DList([np.eye(3)*0.01]*2),
206 linkCOMs=exu.Vector3DList([[0.25, 0, 0]]*2), linkMasses=[1., 1.],
207 gravity=[0, -9.81, 0], **jointData))
208 mbsk.Assemble()
209 simulationSettings = exu.SimulationSettings()
210 simulationSettings.timeIntegration.newton.useModifiedNewton = False #Just for the test; modified Newton is usually faster
211 simulationSettings.timeIntegration.numberOfSteps = 100
212 simulationSettings.timeIntegration.endTime = 0.2
213 simulationSettings.timeIntegration.verboseMode = 0
214 mbsk.SolveDynamic(simulationSettings)
215 return (mbsk, oTree, mbsk.GetNodeOutput(nTree, exu.OutputVariableType.Coordinates))
216(mbsLists, oLists, qLists) = SwingingTree(False)
217(mbsHTs, oHTs, qHTs) = SwingingTree(True)
218errors += [np.abs(qLists - qHTs).max()]
219dTree = mbsHTs.GetObject(oHTs)
220errors += [sum(np.abs(np.array(H) - HomogeneousTransformation(A, p)).max() for (H, A, p) in
221 zip(dTree['jointHTs'], dTree['jointTransformations'], dTree['jointOffsets']))]
222mbsHTs.SetObjectParameter(oHTs, 'jointHTs', [np.eye(4), exu.HT(translation=[0.4, 0, 0]).HT44().flatten()])
223errors += [np.abs(np.array(mbsHTs.GetObjectParameter(oHTs, 'jointOffsets')[1]) - [0.4, 0, 0]).max()]
224raised = 0
225for rotation in [RotXYZ2RotationMatrix([0.1, 0, 0]), np.eye(3)]: #jointHTs and the lists differ, then agree
226 try:
227 mbsHTs.AddObject({'objectType': 'KinematicTree', 'nodeNumber': 0, 'jointHTs': [np.eye(4)],
228 'jointTransformations': exu.Matrix3DList([rotation]), 'jointOffsets': exu.Vector3DList([[0, 0, 0]])})
229 except Exception:
230 raised += 1
231errors += [1 - raised]
232total += np.sum(qHTs)
233
234#an exu.HT is read by numpy as its 4x4 matrix, and taken by the HT functions of rigidBodyUtilities (#2799)
235H = exu.HT(A, p)
236errors += [np.abs(np.array(H) - H44).max(), np.abs(HT2translation(H) - p).max(), np.abs(HT2rotationMatrix(H) - A).max(),
237 np.abs(InverseHT(H) @ H44 - np.eye(4)).max(), np.abs(np.eye(4) @ H - H44).max()]
238
239#the robotics classes take an exu.HT where they take a 4x4 array (#2799)
240from exudyn.robotics import Robot, RobotLink, RobotBase, RobotTool
241def MakeRobot(asHT):
242 Convert = (lambda T: exu.HT(T)) if asHT else (lambda T: T)
243 robot = Robot(gravity=[0, 0, -9.81], base=RobotBase(HT=Convert(HTtranslate([0, 0, 0.5]))),
244 tool=RobotTool(HT=Convert(HTtranslate([0, 0, 0.1]))), referenceConfiguration=[])
245 for k in range(2):
246 robot.AddLink(RobotLink(mass=1, COM=[0.1, 0, 0], inertia=np.eye(3)*0.01, jointType='Rz',
247 preHT=Convert(HTtranslate([0.3, 0, 0]) @ HTrotateX(0.2))))
248 return robot
249qRobot = [0.3, -0.4]
250errors += [max(np.abs(np.array(a) - np.array(b)).max() for (a, b) in zip(MakeRobot(False).LinkHT(qRobot), MakeRobot(True).LinkHT(qRobot)))]
251
252exu.Print('homogeneousTransformationParameterTest: errors', np.round(errors, 15))
253u = sum(errors) + np.sum(mbs.GetObjectParameter(gCopy, 'referenceHT')) + total
254exu.Print('solution of homogeneousTransformationParameterTest=', u)
255
256exu.sys['testResult'] = u