(sec-cinterface-datastructures)= # Data structures This section describes a set of special data structures which are used in the Python-C++ interface, such as a MatrixContainer for dense/sparse matrices or a list of 3D vectors. Note that there are many native data types, such as lists, dicts and numpy arrays (e.g., 3D vectors), which are not described here as they are native to Pybind11, but can be passed as arguments when appropriate. (sec-matrixcontainer)= ## MatrixContainer The MatrixContainer is a versatile representation for dense and sparse matrices. NOTE: if the MatrixContainer is constructed from a numpy array or a list of lists, both representing a dense matrix, it will go into dense mode; if it is initialized with a scipy sparse csr matrix, it will go into sparse mode. Examples: Example notebook `python/Notebooks/reference/matrixContainer.ipynb`; it needs scipy: ```python #Create empty MatrixContainer: import numpy as np from scipy.sparse import csr_matrix from exudyn import MatrixContainer mc = MatrixContainer() #empty matrix, dense mode #Create MatrixContainer with dense matrix: #container can be initialized with a dense matrix, using list of lists or a numpy array, e.g.: matrix = np.eye(3) #stores matrices internally in dense mode: mcDense1 = MatrixContainer(matrix) mcDense2 = MatrixContainer([[1,2],[3,4]]) #container can be initialized with a scipy csr sparse matrix, then being stored as sparse matrix mcSparse = MatrixContainer(csr_matrix(matrix)) #Set with dense pyArray (a numpy array): pyArray = np.array(matrix) mc.SetWithDenseMatrix(pyArray, useDenseMatrix = True) #Set empty matrix: mc.SetWithDenseMatrix([[]], useDenseMatrix = True) #Set with list of lists, stored as sparse matrix: mc.SetWithDenseMatrix([[1,2],[3,4]], useDenseMatrix = False) #Set with sparse triplets (list of lists or numpy array): mc.SetWithSparseMatrix([[0,0,13.3],[1,1,4.2],[1,2,42.]], numberOfRows=2, numberOfColumns=3, useDenseMatrix=True) print(mc) ``` ```text [[13.3 0. 0. ] [ 0. 4.2 42. ]] ``` With a scipy matrix, and triplets added later: ```python #Set with scipy matrix: #WARNING: only use csr_matrix # csc_matrix would basically run, but gives the transposed!!! spmat = csr_matrix(matrix) mc.SetWithSparseMatrix(spmat) #takes rows and column format automatically #initialize and add triplets later on mc.Initialize(3,3,useDenseMatrix=False) mc.AddSparseMatrix(spmat, factor=1) #can also add smaller matrix mc.AddSparseMatrix(csr_matrix(np.eye(2)), factor=0.5) print('mc=',mc) ``` ```text mc= {'numberOfRows': 3, 'numberOfColumns': 3, 'triplets': array([[0. , 0. , 1. ], [1. , 1. , 1. ], [2. , 2. , 1. ], [0. , 0. , 0.5], [1. , 1. , 0.5]])} ``` The class **MatrixContainer** has the following **functions and structures**: - **`Initialize(numberOfRows, numberOfColumns, useDenseMatrix = True)`**: initialize MatrixContainer with number of rows and columns and set dense/sparse mode - **`SetWithDenseMatrix(pyArray, useDenseMatrix = False, factor = 1.)`**: set MatrixContainer with dense numpy array of size (n x m); array (=matrix) contains values and matrix size information; if useDenseMatrix=True, matrix will be stored internally as dense matrix, otherwise it will be converted and stored as sparse matrix (which may speed up computations for larger problems); pyArray is multiplied with given factor - **`SetWithSparseMatrix(sparseMatrix, numberOfRows = invalid (-1), numberOfColumns = invalid (-1), useDenseMatrix = False, factor = 1.)`**: set with scipy sparse csr_matrix (NOT: csc_matrix!) or with internal sparse triplet format (denoted as CSR): 'sparseMatrix' either contains a scipy matrix create with csr_matrix or a list of lists of sparse triplets (row, col, value) or the list of lists converted into numpy array; numberOfRowsInit and numberOfColumnsInit denote the size of the matrices, which are ignored in case of a scipy sparse matrix; if useDenseMatrix=True, matrix will be converted and stored internally as dense matrix, otherwise it will be stored as sparse matrix triplets; the values of sparseMatrix are multiplied with the given factor before storing - **`AddSparseMatrix(sparseMatrix, factor = 1.)`**: add scipy sparse csr_matrix with factor to already initilized MatrixContainer; sparseMatrix must contain according scipy csr format, otherwise the behavior is undefined! This function allows to efficiently add submatrices to the MatrixContainer - **`GetPythonObject()`**: convert MatrixContainer to numpy array (dense) or dictionary (sparse): containing nr. of rows, nr. of columns, numpy matrix with sparse triplets - **`Convert2DenseMatrix()`**: convert MatrixContainer to dense numpy array (SLOW and may fail for too large sparse matrices) - **`UseDenseMatrix()`**: returns True if dense matrix is used, otherwise False - **`SetAllZero()`**: Set all values to zero; dense mode: set all matrix entries to zero (slow); sparse mode: set number of triplets to zero (fast) - **`SetWithSparseMatrixCSR(numberOfRowsInit, numberOfColumnsInit, pyArrayCSR, useDenseMatrix = False, factor = 1.)`**: DEPRECATED: set with sparse CSR matrix format: numpy array 'pyArrayCSR' contains sparse triplet (row, col, value) per row; numberOfRows and numberOfColumns given extra; if useDenseMatrix=True, matrix will be converted and stored internally as dense matrix, otherwise it will be stored as sparse matrix; the values of pyArrayCSR are multiplied by the given factor; removed in 2029 - **`__repr__()`**: return the string representation of the MatrixContainer (sec-ht)= ## HT The HT is a homogeneous transformation - a rotation matrix A and a translation p, the 4x4 matrix [A p; 0 1] -, the frame of a rigid body, marker or joint, and the place for transformations of rigid bodies: it converts from and to the coordinates of the rigid body nodes (Euler parameters, Tait-Bryan angles Rxyz, rotation vector), composes, inverts and interpolates. It is the C++ class of Exudyn, faster than the 4x4 numpy arrays of exudyn.rigidBodyUtilities: it stores the 12 numbers it needs, and a transformation without rotation (identity, SetTranslation, a unit matrix) skips the rotation in its products. The Set functions return the HT itself, so that they chain. Examples: Example notebook `python/Notebooks/reference/HT.ipynb`: ```python import exudyn as exu import numpy as np from exudyn.rigidBodyUtilities import RotationMatrixZ H0 = exu.HT() #identity H1 = exu.HT(rotation=RotationMatrixZ(0.5), translation=[1,0,0]) H2 = exu.HT(translation=[0,2,0]) #translation only H3 = exu.HT(Rxyz=[0,0,0.5], translation=[1,0,0]) #the same as H1, from Tait-Bryan angles H4 = exu.HT().SetRotationAxis([1,1,0], np.pi/4) #the Set functions return the HT print(H3 == H1) ``` ```text True ``` Read and write access to the parts: ```python A = H1.rotation #3x3 numpy array p = H1.translation #numpy array [1,0,0] H1.translation = [0,0,1] #writing a part keeps the other H1.rotation = np.eye(3) #a unit matrix: H1.HasNoRotation() is True A, p = H3.Get() #both parts H3.Set(rotationVector=[0,0,0.5]) #only the rotation; the translation stays print(H1.HasNoRotation(), H3.translation) ``` ```text True [1. 0. 0.] ``` Composition, points, inverse, numpy: an HT is indexed and multiplied like its 4x4 matrix. ```python H = H3 * H2 #first H2, then H3: an HT x = H3 * [0.1,0,0] #a point transformed, a numpy array Hinv = H.Inverse() H44 = H.HT44() #4x4 numpy array; also np.array(H) H5 = exu.HT(H44) #from a 4x4 matrix, or its 16 values as a sensor stores them print(x, H[0:3,3], (H @ Hinv).HasNoRotation() or np.allclose(np.array(H @ Hinv), np.eye(4))) ``` ```text [1.08775826 0.04794255 0. ] [0.04114892 1.75516512 0. ] True ``` The frame of H2 seen from H3, frames in between, and the motion vector of the screw motion: ```python Hrel = H3.Relative(H2) #H3.Inverse()*H2 Hhalf = H3.InterpolateSE3(H2, 0.5) #halfway, a screw motion v = Hrel.LogSE3() #the motion vector [U, Omega] of the screw motion Hquarter = H3 * exu.HT().SetExpSE3(0.25*v) #a quarter of the way, as InterpolateSE3(H2, 0.25) print(np.round(v, 4)) ``` ```text [-0.4791 2.2082 0. 0. 0. -0.5 ] ``` The coordinates of the rigid body nodes, both ways: ```python q7 = H3.GetCoordinatesEP() #[x,y,z, ep0,ep1,ep2,ep3] for NodeRigidBodyEP q6 = H3.GetCoordinatesRxyz() #[x,y,z, rotX,rotY,rotZ] for NodeRigidBodyRxyz ep = H3.GetEP() #only the rotation, as Euler parameters H6 = exu.HT().SetCoordinatesRotationVector([1,0,0, 0,0,0.5]) angle = H6.RotationAngle() #0.5 axis = H6.RotationAxis() #[0,0,1] print(np.round(q7, 4), angle, axis) ``` ```text [1. 0. 0. 0.9689 0. 0. 0.2474] 0.4999999999999999 [0. 0. 1.] ``` The class **HT** has the following **functions and structures**: - HT(rotation=None, translation=None, eulerParameters=None, Rxyz=None, rotationVector=None): the rotation from at most one of a 3x3 matrix, Euler parameters, Tait-Bryan angles Rxyz and a rotation vector, the translation, each None for the unit rotation or zero; or HT(H44) from a 4x4 matrix or its 16 values row by row - **`rotation`**: the 3x3 rotation matrix as numpy array; setting it keeps the translation - **`translation`**: the translation as numpy array; setting it keeps the rotation - **`Get()`**: [rotation, translation] as numpy arrays - **`Set(rotation = None, translation = None, eulerParameters = None, Rxyz = None, rotationVector = None)`**: set the parts given, a part that is None stays: the rotation from at most one of a 3x3 matrix, Euler parameters, Tait-Bryan angles Rxyz and a rotation vector, and the translation; returns the HT - **`SetIdentity()`**: set the identity: unit rotation, zero translation; returns the HT - **`SetTranslation(translation)`**: set a translation and the unit rotation; returns the HT - **`SetRotationX(angle)`**: set a rotation about the x-axis by angle (in radians) and zero translation; returns the HT - **`SetRotationY(angle)`**: set a rotation about the y-axis by angle (in radians) and zero translation; returns the HT - **`SetRotationZ(angle)`**: set a rotation about the z-axis by angle (in radians) and zero translation; returns the HT - **`SetRotationAxis(axis, angle)`**: set a rotation by angle (in radians) about axis, a 3D vector of any length but zero, and zero translation; returns the HT - **`GetEP()`**: the rotation as the 4 Euler parameters [ep0,ep1,ep2,ep3], the rotation parameters of NodeRigidBodyEP, a numpy array - **`GetRxyz()`**: the rotation as the 3 Tait-Bryan angles [rotX,rotY,rotZ] (rotation about x, then y, then z), the rotation parameters of NodeRigidBodyRxyz, a numpy array - **`GetRotationVector()`**: the rotation as rotation vector (axis times angle), the rotation parameters of NodeRigidBodyRotVecLG, a numpy array - **`GetCoordinatesEP()`**: the 7 coordinates of NodeRigidBodyEP: the translation and the Euler parameters, e.g. for referenceCoordinates, a numpy array - **`GetCoordinatesRxyz()`**: the 6 coordinates of NodeRigidBodyRxyz: the translation and the Tait-Bryan angles, a numpy array - **`GetCoordinatesRotationVector()`**: the 6 coordinates of NodeRigidBodyRotVecLG: the translation and the rotation vector, a numpy array - **`SetCoordinatesEP(coordinates)`**: set from the 7 coordinates of NodeRigidBodyEP, translation and Euler parameters, as given by GetCoordinates... or by a node; returns the HT - **`SetCoordinatesRxyz(coordinates)`**: set from the 6 coordinates of NodeRigidBodyRxyz, translation and Tait-Bryan angles, as given by GetCoordinates... or by a node; returns the HT - **`SetCoordinatesRotationVector(coordinates)`**: set from the 6 coordinates of NodeRigidBodyRotVecLG, translation and rotation vector, as given by GetCoordinates... or by a node; returns the HT - **`RotationAngle()`**: the angle of the rotation in radians, 0 to pi: the length of the rotation vector - **`RotationAxis(raiseError = True)`**: the unit axis of the rotation, from the rotation vector; for no rotation an error, or [0,0,0] if raiseError=False - **`Relative(other)`**: the frame of other seen from this one, H.Inverse()*other, without computing the inverse: what a joint computes between the frames of its two markers - **`InterpolateSO3(other, factor)`**: the transformation between this one (factor=0) and other (factor=1): the translation linear and the rotation on SO(3), about one fixed axis with the angle in proportion - **`InterpolateSE3(other, factor)`**: the transformation between this one (factor=0) and other (factor=1) on SE(3): a screw motion, in which translation and rotation are coupled, as a rigid body moves with constant twist - **`LogSE3()`**: the logarithm on SE(3): the motion vector [U, Omega], 6 components as numpy array, from which SetExpSE3 gives this HT back; Omega is the rotation vector, U the translation in the coordinates of the screw motion - **`SetExpSE3(vector)`**: set from the exponential map on SE(3) of the motion vector [U, Omega], 6 components: the screw motion of constant twist; returns the HT - **`LogR3xSO3()`**: the logarithm on R3xSO(3): the translation and the rotation vector, each on its own, 6 components as numpy array - the same numbers as GetCoordinatesRotationVector() - **`SetExpR3xSO3(vector)`**: set from the exponential map on R3xSO(3) of [U, Omega], 6 components: the translation U and the rotation of the rotation vector Omega, each on its own; returns the HT - **`HT44()`**: the 4x4 matrix [A p; 0 1] as numpy array - **`Inverse()`**: the inverse transformation [A^T, -A^T p], an HT - **`Invert()`**: invert the transformation in place - **`RotateVector(vector)`**: the rotated vector A*v, without the translation - **`RotateVectorTransposed(vector)`**: the vector rotated back, A^T*v - **`HasNoRotation()`**: True if the rotation is the unit matrix - set without rotation (identity, SetTranslation, a product of such) or given as the exact unit matrix from Python -, which its products then skip; a unit matrix computed in C++ is not checked - **`__mul__(other)`**: H1*H2, the composition of two transformations, an HT; H*v, the transformed point A*v+p of a 3D vector, a numpy array - **`__eq__(other)`**: True if rotation and translation are equal, component by component - **`__matmul__(other)`**: H1@H2, the composition of two transformations as H1*H2, an HT; H@x for any other x as numpy computes it with the 4x4 matrix, e.g. H@[x,y,z,1] - **`__copy__()`**: a copy, for copy.copy(H) - **`__deepcopy__(memo)`**: a copy, for copy.deepcopy(H) and objects holding an HT - **`... = data[index]key`**: H[key]: numpy indexing of the 4x4 matrix [A p; 0 1], e.g. H[0:3,3] for the translation; the result is a copy - **`data[index]= ...key, value`**: H[key] = value: numpy indexing of the 4x4 matrix for writing the rotation and the translation, e.g. H[0:3,3] = p; writing the last row [0,0,0,1] raises - **`__array__(dtype = None, copy = None)`**: the 4x4 matrix [A p; 0 1] as numpy array, for np.array(H) and wherever numpy takes the HT - **`__repr__()`**: the string representation of the HT (sec-graphicsmateriallist)= ## GraphicsMaterialList The GraphicsMaterialList contains the list of materials (material properties) for visualization; currently, only the raytracer uses materials. Materials can be accessed via the variable materials in renderer of SystemContainer. ```python #access material 0: mat0 = SC.renderer.materials[0] #convert into dictionary for easier processing: matDict = mat0.GetDictionary() matDict['alpha'] = 0.5 #change material (e.g., using a data base): mat0.SetDictionary(matDict) #or directly update material mat0.name = 'new name' mat0.emission = [0.8,0.6,0.] #update material in renderer: SC.renderer.materials.Set(0,mat0) #update material directly with dictionary: SC.renderer.materials.Set(0,matDict) #create new material: mat10 = SC.renderer.materials.New() mat10.reflectivity = 0.8 SC.renderer.materials.Append(mat10) #returns index of mat10 #10 default graphics materials in Exudyn, with their default color and some properties; #note the increased computational costs for reflection and transparency: # 0 default: steel blue # 1 matt: green # 2 steel: grey (reflection) # 3 plastic: red (reflection) # 4 chrome: light grey (reflection) # 5 shiny: orange (reflection) # 6 transparent: (transparency, slight refraction) # 7 glass: light grey (reflection, transparency, refraction) # 8 mirror: light grey (reflection) # 9 emission: light yellow SC2 = exu.SystemContainer() print([SC2.renderer.materials[i].name for i in range(10)]) ``` ```text ['default', 'matt', 'steel', 'plastic', 'chrome', 'shiny', 'transparent', 'glass', 'mirror', 'emission'] ``` ```{container} notebookorigin `python/Notebooks/reference/systemContainer.ipynb` ``` The class **GraphicsMaterialList** has the following **functions and structures**: - **`Reset()`**: reset materials to 10 default materials - **`Append(material)`**: add single material as dict or VSettingsMaterial to list; returns index of newly added material - **`New()`**: Get new default material, which can be modified or appended to materials list - **`Set(indexOrName, material)`**: set material with index 'materialIndex' as dict or VSettingsMaterial - **`Get(indexOrName)`**: get material with index 'materialIndex' as VSettingsMaterial - **`GetDict(indexOrName)`**: get material with index 'materialIndex' as dict - **`len(data)`**: return length of the Vector3DList, using len(data) where data is the Vector3DList - **`... = data[index]`**: get reference access of material with 'index' as VSettingsMaterial - **`__repr__()`**: return the string representation of the GraphicsMaterialList ## Vector3DList The Vector3DList is used to represent lists of 3D vectors. This is used to transfer such lists from Python to C++. Usage: - Create empty `Vector3DList` with `x = Vector3DList()` - Create `Vector3DList` with list of numpy arrays:`x = Vector3DList([ numpy.array([1.,2.,3.]), numpy.array([4.,5.,6.]) ])` - Create `Vector3DList` with list of lists `x = Vector3DList([[1.,2.,3.], [4.,5.,6.]])` - Append item: `x.Append([0.,2.,4.])` - Convert into list of numpy arrays: `x.GetPythonObject()` The class **Vector3DList** has the following **functions and structures**: - **`Append(pyArray)`**: add single array or list to Vector3DList; array or list must have appropriate dimension! - **`GetPythonObject()`**: convert Vector3DList into (copied) list of numpy arrays - **`len(data)`**: return length of the Vector3DList, using len(data) where data is the Vector3DList - **`data[index]= ...`**: set list item 'index' with data, write: data[index] = ... - **`... = data[index]`**: get copy of list item with 'index' as vector - **`__copy__()`**: copy method to be used for copy.copy(...); in fact does already deep copy - **`__deepcopy__()`**: deepcopy method to be used for copy.copy(...) - **`__repr__()`**: return the string representation of the Vector3DList data, e.g.: print(data) ## Vector2DList The Vector2DList is used to represent lists of 2D vectors. This is used to transfer such lists from Python to C++. Usage: - Create empty `Vector2DList` with `x = Vector2DList()` - Create `Vector2DList` with list of numpy arrays: `x = Vector2DList([ numpy.array([1.,2.]), numpy.array([4.,5.]) ])` - Create `Vector2DList` with list of lists `x = Vector2DList([[1.,2.], [4.,5.]])` - Append item: `x.Append([0.,2.])` - Convert into list of numpy arrays: `x.GetPythonObject()` - similar to Vector3DList ! The class **Vector2DList** has the following **functions and structures**: - **`Append(pyArray)`**: add single array or list to Vector2DList; array or list must have appropriate dimension! - **`GetPythonObject()`**: convert Vector2DList into (copied) list of numpy arrays - **`len(data)`**: return length of the Vector2DList, using len(data) where data is the Vector2DList - **`data[index]= ...`**: set list item 'index' with data, write: data[index] = ... - **`... = data[index]`**: get copy of list item with 'index' as vector - **`__copy__()`**: copy method to be used for copy.copy(...); in fact does already deep copy - **`__deepcopy__()`**: deepcopy method to be used for copy.copy(...) - **`__repr__()`**: return the string representation of the Vector2DList data, e.g.: print(data) ## Vector6DList The Vector6DList is used to represent lists of 6D vectors. This is used to transfer such lists from Python to C++. Usage: - Create empty `Vector6DList` with `x = Vector6DList()` - Convert into list of numpy arrays: `x.GetPythonObject()` - similar to Vector3DList ! The class **Vector6DList** has the following **functions and structures**: - **`Append(pyArray)`**: add single array or list to Vector6DList; array or list must have appropriate dimension! - **`GetPythonObject()`**: convert Vector6DList into (copied) list of numpy arrays - **`len(data)`**: return length of the Vector6DList, using len(data) where data is the Vector6DList - **`data[index]= ...`**: set list item 'index' with data, write: data[index] = ... - **`... = data[index]`**: get copy of list item with 'index' as vector - **`__copy__()`**: copy method to be used for copy.copy(...); in fact does already deep copy - **`__deepcopy__()`**: deepcopy method to be used for copy.copy(...) - **`__repr__()`**: return the string representation of the Vector6DList data, e.g.: print(data) ## Matrix3DList The Matrix3DList is used to represent lists of 3D Matrices. . This is used to transfer such lists from Python to C++. Usage: - Create empty `Matrix3DList` with `x = Matrix3DList()` - Create `Matrix3DList` with list of numpy arrays: `x = Matrix3DList([ numpy.eye(3), numpy.array([[1.,2.,3.],[4.,5.,6.],[7.,8.,9.]]) ])` - Create `Matrix3DList` with one matrix `x = Matrix3DList(13.*numpy.eye(3))` - Append item: `x.Append(numpy.eye(3))` - Convert into list of numpy arrays: `x.GetPythonObject()` - similar to Vector3DList ! The class **Matrix3DList** has the following **functions and structures**: - **`Append(pyArray)`**: add single 3D array or list of lists to Matrix3DList; array or lists must have appropriate dimension! - **`GetPythonObject()`**: convert Matrix3DList into (copied) list of 3x3 numpy arrays - **`len(data)`**: return length of the Matrix3DList, using len(data) where data is the Matrix3DList - **`data[index]= ...`**: set list item 'index' with matrix, write: data[index] = ... - **`... = data[index]`**: get copy of list item with 'index' as matrix - **`__repr__()`**: return the string representation of the Matrix3DList data, e.g.: print(data) ## Matrix6DList The Matrix6DList is used to represent lists of 6D Matrices. . This is used to transfer such lists from Python to C++. Usage: - Create empty `Matrix6DList` with `x = Matrix6DList()` - Create `Matrix6DList` with list of numpy arrays: `x = Matrix6DList([ numpy.eye(6), 2*numpy.eye(6) ])` - Append item: `x.Append(numpy.eye(6))` - Convert into list of numpy arrays: `x.GetPythonObject()` - similar to Matrix3DList ! The class **Matrix6DList** has the following **functions and structures**: - **`Append(pyArray)`**: add single 6D array or list of lists to Matrix6DList; array or lists must have appropriate dimension! - **`GetPythonObject()`**: convert Matrix6DList into (copied) list of 6x6 numpy arrays - **`len(data)`**: return length of the Matrix6DList, using len(data) where data is the Matrix6DList - **`data[index]= ...`**: set list item 'index' with matrix, write: data[index] = ... - **`... = data[index]`**: get copy of list item with 'index' as matrix - **`__repr__()`**: return the string representation of the Matrix6DList data, e.g.: print(data)