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