Symbolic creation and analysis of constrained multi-body systems in python
uraeus is a python package developed for the symbolic creation and analysis of constrained multi-body systems.
A multi-body system is hereby defined as a finite number of material bodies connected in an arbitrary fashion by mechanical joints that limit the relative motion between pairs of bodies. Practitioners of multi-body dynamics study the generation and solution of the equations governing the motion of such systems .
Audience and Fields of Application
Initially, the main targeted audience was the Formula Student community. The motive was encouraging a deeper understanding of the modelling processes and the underlying theories used in other commercial software packages, which is a way of giving back to the community, and supporting the concept of "knowledge share" adopted there by exposing it to the open-source community as well.
Currently, the tool aims to serve a wider domain of users with different usage goals and different backgrounds, such as students, academic researchers and industry professionals.
Fields of application include any domain that deals with the study of interconnected bodies, such as:
- Ground Vehicles' Systems.
- Construction Equipment.
- Industrial Mechanisms.
- .. etc.
What is the problem to be solved?
The primary interest in multi-body dynamics is to analyze the system behaviour for given inputs. In analogy with control systems; a multi-body system can be thought as a system subjected to some inputs producing some outputs. These three parts of the problem are dependent on the analyst end goal of the analysis and simulation.
How is the system physics abstracted mathematically?
An unconstrained body in space is normally defined using 6 generalized coordinates defining its location and orientation. For example, a system of 10 bodies requires 60 generalized coordinates to be fully defined, which in turn requires 60 independent equations to be solved for these unknown generalized coordinates.
The way we achieve a solution for the system is dependent on the type of study we are performing. Mainly we have four types of analysis that are of interest for a given multi-body system. These are:
- Kinematic Analysis
"How does the whole system move if we moved this particular body ?"
- Inverse Dynamic Analysis
"What are the forces needed to achieve this motion we just did ?"
- Equilibrium Analysis
"How does the system look if we did nothing ?"
- Dynamic Analysis
"Now we gave it a force, how does it behave ?"
Each analysis type -or question- can be modelled by a set of algebraic and/or differential equations that can be solved for the system generalized states (positions, velocities and accelerations). A more detailed discussion of each analysis type will be provided in another documentation.
The philosophy of the tool is to isolate the model creation process form the actual numerical and computational representation of the system that will be used in the simulation process. This is done through the ideas of symbolic computing and code-generation as well be shown below.
The topology of the system is represented as a multi-directed graph, where each node represents a body and each edge represents a connection between the end nodes, where this connection may represents a joint, actuator or a force element. This serves mainly two aspects:
- A natural way to create and represent the topology of a given multi-body system.
- A convenient way to abstract the system programmatically, where all the topological data of the system are stored in a graph.
The tool achieves this by making heavy use the NetworkX python package to create topology graphs and to construct the governing equations of the system. The equations themselves are represented symbolically using SymPy, which is a Python library for symbolic mathematics.
The combination of both, NetworkX and SymPy, provides the tool with a very simple, easy-to-use and convenient interface for the process of model creation and topology design, where the user only focuses on the validity of the system topology in hand, as he thinks only in terms of the topological components - bodies, joints, actuators and forces-, without the burden of frequent numerical inputs for each component, or how the actual system is configured in space. In short, the tool divide the typical model creation process in halves, the system topology design and the system configuration assignment.
Code Generation and Model Simulation
The process of performing actual simulations on the created model requires the generation of a valid numerical and computational code of the created model. This is done by taking in the symbolic model and create a valid code files written in the desired programming language with the desired programming paradigm and structure.
These numerical environments are decoupled from this package and developed fairly independently, as each numerical environment is responsible for the translation of the developed symbolic models into valid numerical code and what features it aims to provide for the users.
The development of such environments in different languages requires a good grasp of several aspects such as :
- Good knowledge of the symbolic models' interfaces and structure.
- Good knowledge of the target language.
- Appropriate environment architecture/structure that serves the intended usage requirements.
- Good knowledge of the available linear algebra and math libraries for that language.
- Design for minimal dependencies on 3rd parties libraries.
- Simple API for usage and simple build process for compiled languages.
Note: The development of such environments is discussed in a separate documentation for those interested in developing their own.
Several benefits of the adopted approach can be stated here, but the major theme here is the flexibility and modularity, in both software usage and software development. These can be summarized as follows:
- The distinction between the topology design phase and the configuration assignment phase, which gives proper focus for each at its' own.
- Natural adoption of the template-based modelling theme that emerges from the use of network-graphs to represent the system, which allows convenient assemblage of several graphs to form a new system.
- Uncoupled simulation environment, where the symbolic equations generated form the designed topology is free to be written in any programming language with any desired numerical libraries.
Currently, the tool provides:
- Creation of symbolic template-based and stand-alone multi-body systems using minimal API via python scripting.
- Convenient and easy creation of complex multi-body assemblies.
- Convenient visualization of the system topology as a network graph.
- Viewing the system's symbolic equations in a natural mathematical format using Latex printing.
- Optimization of the system symbolic equations by performing common sub-expressions elimination.
- Creation of symbolic configuration files to facilitate the process of numerical simulation data entry.
Using the tool on Colab
Colaboratory is a free Jupyter notebook environment that requires no setup and runs entirely in the cloud . So, if you do not have an up and running python environment, you still can check out the tool and create multi-body systems seamlessly.
The examples section below, has several ready-to-use Colab notebooks that walks you through a typical modelling process flow.
Using the tool on your machine.
The tool needs a valid python 3.6+ environment. If new to scientific computing in python, Anaconda is a recommended free python distribution from Continuum Analytics that includes SymPy, SciPy, NumPy, Matplotlib, and many more useful packages for scientific computing, which provides a nice coherent platform with most of the tools needed.
As the tool is still under continuous development, cloning this repository is a more versatile way to test and play with it, until a more stable first release is released. This can be done via the following git commands from the command line.
git clone https://github.com/khaledghobashy/uraeus.git
git pull origin master
Or alternatively, download the repository as a zip file and extract it on your machine.
Then, if creating a new python session to use the tool, add the directory path where the tool exists to the python system path.
try: import uraeus except ModuleNotFoundError: import sys pkg_path = 'path/to/uraeus' sys.path.append(pkg_path) # the package can now be imported as uraeus, e.g. : # from uraeus.systems import standalone_topology
Usage Examples & Tutorials
Ready-to-Use Notebooks & Tutorials
This is a list of ready-to-use jupyter notebooks that walks you through the typical flow of the tool modelling process. The examples directory in this repository is planned to include updated versions of working models that can be statically viewed on github, downloaded on your machine or to be ran directly on Colab.
- Spatial Four-bar. (GitHub | Colab)
- Spatial Slider-Crank. (GitHub| Colab)
- Double-Wishbone Suspension. (GitHub| Colab)
- Double-Four-bar Mechanism. (GitHub| Colab)
- Simple Pendulum. (GitHub| Colab)
- Double Pendulum. (GitHub| Colab)
To be discussed ...
Detailed Example - Spatial Fourbar Mechanism
Below is code sample that walks you through the process of building a standalone symbolic topology and configuration as well as the generation of numerical simulation environments.
This model will be created as a standalone topology and project. What this means is that model topological data is fully encapsulated in one topology graph and no need for any topological data from other external systems, which is the case for template-based topologies.
This also means that the project files/database is self-contained, unlike the template-based topologies that need to be organized in a shared database.
Initializing Project Structure
Currently, a standalone project is structured using three top-level directories inside a given
parent_dir; these are
Directory of the numerical environments to be generated.
Directory to store the results of numerical simulations if needed.
Directory to store the numerical inputs used in numerical simulations.
To create a standalone project :
from smbd.systems import standalone_project parent_dir = '' # current working directory project = standalone_project(parent_dir) project.create()
Building the Symbolic Topology.
We start by importing the
standalone_topology class from the
systems module to create our symbolic model instance.
from smbd.systems import standalone_topology model_name = 'fourbar' sym_model = standalone_topology(model_name)
We then start constructing our system by adding the bodies, joints, actuators and forces.
# Adding Bodies sym_model.add_body('l1') sym_model.add_body('l2') sym_model.add_body('l3') # Adding Joints sym_model.add_joint.revolute('a', 'ground', 'rbs_l1') sym_model.add_joint.spherical('b', 'rbs_l1', 'rbs_l2') sym_model.add_joint.universal('c', 'rbs_l2', 'rbs_l3') sym_model.add_joint.revolute('d', 'rbs_l3', 'ground') # Adding Actuators sym_model.add_actuator.rotational_actuator('act', 'jcs_a')
And that's it; we have just created a symbolic topology that represents our fourbar mechanism. The topology graph of the system can be visualized by the method
Also we can check the number of constraint equations, generalized coordinates and the estimated degrees of freedom of the system.
To finalize this step, we call the
assemble() method to construct the governing equations symbolically.
We can check the system equations by accessing the appropriate topology attributes.
# Position level constraint equations. sym_model.topology.pos_equations # System Jacobian of the position level constraint equations. sym_model.topology.jac_equations
Building the symbolic configuration.
The next step is to create a symbolic configuration of our symbolic model, but what is this symbolic configuration?
You may have noticed that we did not care explicitly about how our system is configured in space, we did not care about how our bodies or joints are located or oriented or how we can define these configuration parameters, all we cared about is only the topological connectivity. These configuration parameters already got generated automatically based on the used components. For example, the creation of a symbolic body -body l1 for example- generates automatically the following symbolic parameters:
m_rbs_l1: body mass.
Jbar_rbs_l1: inertia tensor.
R_rbs_l1: body reference point location.
Rd_rbs_l1: body translational velocity.
Rdd_rbs_l1: body translational acceleration.
P_rbs_l1: body orientation.
Pd_rbs_l1: body orientation 1st rate of change.
Pdd_rbs_l1: body orientation 2nd rate of change.
rbs_ initial is short for rigid body single. If the body is mirrored, the system will create two bodies with the initials
rbl_ for right and left respectively.
The same happens for edges' components -joints, actuators and forces- where each component is responsible for creating its own configuration symbolic parameters.
These parameters are extracted from the symbolic topology to form the primary configuration layer that represents the needed user inputs for any given simulation. The benefit of the symbolic configuration is that we can construct our own layer of inputs that we desire to use in the numerical simulation and define the relations between these inputs and the primary parameters extracted from the topology components. This is best shown by example.
Our fourbar mechanism is simply visualized as three links and a ground that are connected at four distinct points, a, b, c and d. We can simply get directly the numerical values of these points in space much easier than -for example- getting directly the orientation of the two axes used to define the universal joint used to connect l2 with l3.
The idea is to construct a directed relational graph that maps the required primary configuration to a set of new configuration parameters that may be easier and more convenient to specify directly.
We start by creating our configuration instance
from smbd.systems import configuration sym_config = configuration('%s_cfg'%model_name, sym_model)
Now we can check the primary configuration parameters extracted the from the symbolic topology by
sym_config.config.input_nodes which returns a list of strings containing the inputs' parameters names.
Now, we create our desired user inputs.
# Adding the desired set of UserInputs # ==================================== sym_config.add_point.UserInput('a') sym_config.add_point.UserInput('b') sym_config.add_point.UserInput('c') sym_config.add_point.UserInput('d') sym_config.add_vector.UserInput('x') sym_config.add_vector.UserInput('y') sym_config.add_vector.UserInput('z')
After that, we set the relations between the primary configuration parameters and our custom configuration inputs.
# Defining Relations between original topology inputs # and our desired UserInputs. # =================================================== # Revolute Joint (a) location and orientation sym_config.add_relation.Equal_to('pt1_jcs_a', ('hps_a',)) sym_config.add_relation.Equal_to('ax1_jcs_a', ('vcs_x',)) # Spherical Joint (b) location and orientation sym_config.add_relation.Equal_to('pt1_jcs_b', ('hps_b',)) sym_config.add_relation.Equal_to('ax1_jcs_b', ('vcs_z',)) # Universal Joint (c) location and orientation sym_config.add_relation.Equal_to('pt1_jcs_c', ('hps_c',)) sym_config.add_relation.Oriented('ax1_jcs_c', ('hps_b', 'hps_c')) sym_config.add_relation.Oriented('ax2_jcs_c', ('hps_c', 'hps_b')) # Revolute Joint (d) location and orientation sym_config.add_relation.Equal_to('pt1_jcs_d', ('hps_d',)) sym_config.add_relation.Equal_to('ax1_jcs_d', ('vcs_y',))
Note: The set of configuration parameters of each component and their naming convention will be discussed in a separate documentation
The first line of the above code-block adds a relation that sets the location of joint
pt1_jsc_a to be
Equal_to the user-input location point
hps_a, where the fifth line adds a relation that sets the orientation of the first axis of the universal joint
ax1_jsc_c to be
Oriented along the user-input location points
hps_c. The rest of the statements follows the same convention.
An optional and recommended step is to create symbolic geometries and assign these geometries to topology bodies to automatically evaluate the bodies configuration parameters stated earlier. Also this will be used to generate a python-blender script that can be used in blender to create 3D visualizations in blender later.
# links radius sym_config.add_scalar.UserInput('links_ro') # Link 1 geometry sym_config.add_geometry.Cylinder_Geometry('l1', ('hps_a','hps_b','s_links_ro')) sym_config.assign_geometry_to_body('rbs_l1', 'gms_l1') # Link 2 geometry sym_config.add_geometry.Cylinder_Geometry('l2', ('hps_b','hps_c','s_links_ro')) sym_config.assign_geometry_to_body('rbs_l2', 'gms_l2') # Link 3 geometry sym_config.add_geometry.Cylinder_Geometry('l3', ('hps_c','hps_d','s_links_ro')) sym_config.assign_geometry_to_body('rbs_l3', 'gms_l3')
The last step is to
assemble the symbolic configuration and extract the updated set of inputs to a .csv file.
To be discussed ...
The Implementation Details will be provided in a separate documentation and linked here
The Theoretical Basis will be provided in a separate documentation and linked here
 : McPhee, J.J. Nonlinear Dyn (1996) 9: 73. https://doi.org/10.1007/BF01833294
uraeus is distributed under the 3-clause BSD license. See the LICENSE file for details.
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Hashes for uraeus-0.0.dev1-py3-none-any.whl