Skip to content

Numeric types

InertialSim makes extensive use of NumPy for numeric data types and operations. Unfamiliar users should consult the NumPy guide: https://numpy.org/doc/stable/user/index.html for a quick start and comparison with other tools.

Basic InertialSim types are the Python standard library types (int,float,tuple, etc.) and NumPy arrays (numpy.ndarray). All floating point types use 64-bit precision (numpy.float64).

Array modification, views, and copies

Arrays are mutable types. Many InertialSim functions modify (or mutate) their inputs or return views to subsets of the inputs. This is similar to passing by reference in other languages. InertialSim methods that take numpy.typing.ArrayLike inputs make a copy since they are typically modified to meet InertialSim conventions (shape, data types, range, etc.). Methods that take InertialSim custom types (e.g. inertialsim.geometry classes) do not copy their inputs and do not modify their inputs. Users should be cautious of unexpected side-effects when modifying data that has already been passed to InertialSim.

Unit conventions

With the exception of specifications, all numeric values input and output from InertialSim use SI units (meter, kilogram, second, etc.). All angles use units of radians.

Array conventions

NumPy arrays support linear algebra but are not primarily designed for linear algebra. As a result, the following conventions are applied.

Input and output types

User inputs are typically of type numpy.typing.ArrayLike. These are types that are already a NumPy array or can be easily converted into an array. Examples include:

import numpy as np

array = [1, 2]  # Python list
array = (1, 2, 3, 4)  # Python tuple
array = np.array([1, 2, 3])  # 1D numpy array
array = np.array([[1], [2], [3]])  # 2D numpy array
array = [[1, 2, 3], [4, 5, 6]]  # List of lists, or row-major matrix

InertialSim outputs are typically of type numpy.typing.NDArray. These are simply NumPy arrays. Lists, tuples, and other array-like inputs are converted explicitly such that all outputs are arrays.

Multiplication operators

Scalar (element-wise) multiplication is performed with the * operator. Linear algebra multiplication (matrix-vector, matrix-matrix) is performed with the @ operator.

Array shapes

Regardless of input shape (list, 1-dimensional array, 2-dimensional array, etc.) all inputs are converted to 3-dimensional arrays internally. All array type outputs are 3-dimensional also. This is to support stacking of inputs and outputs (typically over a time axis) and vectorization (operations without for-loops).

Vector terminology

The term "vector" has many meanings. The most fundamental is a physical vector, for example, Earth's gravitational force at a given point in space. The second is the numerical representation of a physical vector as a 3-tuple of values [x,y,z]. The third is the notion of vectorization of calculations where for loops are replaced in software by array operations. This last meaning can take advantage of vector processors (SIMD, GPU, etc.) but is not required to. See https://en.wikipedia.org/wiki/Array_programming.

Stacking multiple scalars, vectors, or matrices

The NumPy (and Python) convention for stacking multiple scalars, vectors or matrices is to increment the first index (see numpy.matmul for example). Therefore stacking five matrices with 3 rows and 4 columns each results in an array of shape = (5,3,4). Stacking ten scalars results in an array of shape = (10,1,1). Stacking two quaternions results in an array of shape = (2,4,1).

Example: multiplying two vectors by the same matrix

Firstly, we illustrate the need for the 3-dimensional convention. We wish to vectorize the multiplication of one 3x3 matrix with two 3x1 vectors. Using minimum dimension arrays and the @ operator does not work in this instance. NumPy's broadcast behavior complains (reasonably) that the two arrays have mismatched inner dimensions (shape = (3,3) @ shape = (2,3)).

# Two vectors (shape = (2,3))
vectors = np.array([[1, 2, 3], [4, 5, 6]])

# Single matrix (shape = (3,3))
matrix = np.array([[[0, 1, 0], [1, 0, 0], [0, 0, 1]]])

# Matrix-vector multiplication results in an error
matrix @ vectors
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

Cell In[2], line 8
      5 matrix = np.array([[[0, 1, 0], [1, 0, 0], [0, 0, 1]]])
      7 # Matrix-vector multiplication results in an error
----> 8 matrix @ vectors


ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 3)

Making both inputs 3-dimensional fixes this issue. Now the multiplication is shape = (1,3,3) @ shape = (2,3,1) and the broadcast behavior works as expected. This is true in general for vectorizing basic linear algebra operations.

# Two vectors (shape = (2,3,1))
vectors = np.array([[[1], [2], [3]], [[4], [5], [6]]])

# One matrix (shape = (1,3,3))
matrix = np.array([[[0, 1, 0], [1, 0, 0], [0, 0, 1]]])

# Matrix-vector multiplication results in two new vectors (shape = (2,3,1)) that
# are permutations of the input as expected
result = matrix @ vectors
result
array([[[2],
        [1],
        [3]],

       [[5],
        [4],
        [6]]])

For a single vector or matrix output, it may be undesirable or incompatible with other libraries to maintain extra dimensions. Users can recover a minimum dimensional array with numpy.squeeze.

Convenience functions

The inertialsim.geometry module offers three convenience functions if users wish to pre-convert their data to InertialSim array conventions: scalar_array, vector_array, and matrix_array.

from inertialsim import geometry

# Input a list of integers
scalars = [1, 2, 3, 4]
# Outputs an array with shape = (4,1,1) and dtype = np.float64
scalars = geometry.scalar_array(scalars)

# Input a list of 2 3-dimensional vectors
vectors = [[1, 2, 3], [4, 5, 6]]
# Outputs an array with shape = (2,3,1) and dtype = np.float64
vectors = geometry.vector_array(vectors, dimension=3)

# Input one 4x3 matrix
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
# Outputs an array with shape = (1,4,3) and dtype = np.float64
matrix = geometry.matrix_array(matrix, rows=4, columns=3)