Conventions#
Terminology#
Python Namespaces & Conventions#
kdFlex exposes its functionality through a small set of well‑structured Python packages (namespaces). The design follows a “layered” approach: low‑level C++ core wrapped by pybind11, higher‑level Python helpers, and finally the user‑facing API. Below is a concise map of the main namespaces and the conventions you’ll encounter when writing or reading kdFlex code.
All top‑level modules are prefixed with Karana. – this is the “kdFlex” root.
Naming Style
Classes –
PascalCase(e.g.,Multibody,PhysicalBody).Functions / helpers –
snake_case(e.g.,discard(),allReady()).Constants / enums –
UPPER_SNAKE_CASE(e.g.,IntegratorType.EULER,MMSolverType.SINGLE).
Namespace |
Purpose |
Typical imports |
Key classes / functions |
|---|---|---|---|
|
Core utilities that are shared across all layers (memory, containers, helpers). |
|
|
|
Handles the frame hierarchy – the kinematic tree of coordinate frames. |
|
|
|
Mathematical primitives (vectors, matrices, quaternions, integrators). |
|
|
|
Multibody dynamics engine (bodies, hinges, constraints, kinematics, dynamics). |
|
|
|
Geometry and visualization |
|
|
Most Python classes are thin wrappers around underlying C++ classes. An
important exception are the Eigen based Karana::Math::Vec and
Karana::Math::Mat C++ matrix/vector classes, which instead map into
and use numpy arrays at the Python level.
By keeping these namespaces and conventions in mind, you’ll be able to navigate kdFlex’s API, write clean simulation scripts, and extend the framework with confidence.
C++ namespaces and conventions#
kdFlex is a C++ library that exposes its functionality through a small set of well‑defined namespaces. The design follows a “core‑first” approach: the C++ implementation is wrapped by pybind11 for Python, but the C++ API is the source of truth. Below is a concise map of the main C++ namespaces and the conventions you’ll encounter when reading or extending the source.
All top‑level modules are prefixed with Karana::.
Namespace |
Purpose |
Typical classes / functions |
|---|---|---|
|
Low‑level utilities shared by all modules (memory, containers, helpers). |
|
|
Frame hierarchy – the kinematic tree of coordinate frames. |
|
|
Mathematical primitives (vectors, matrices, quaternions, integrators). |
|
|
Multibody dynamics engine (SOA, ATBI/ABI, constraints). |
|
|
Geometries and visualization |
|
Abbreviations such as below are used for more succinct ways to use the namespaces within C++ code.
namespace kc = Karana::Core;
namespace kf = Karana::Frame;
namespace km = Karana::Math;
namespace kd = Karana::Dynamics;
namespace ks = Karana::Scene;
These namespaces and conventions form the backbone of kdFlex’s C++ code base and are the key to navigating, understanding, and extending the library.
Object identifiers#
Most kdflex classes are derived from the Karana::Core::Base C++ class, and
as such every object has
a name (not necessarily unique), accessed via the
name()methoda unique numeric id, accessed via the
id()methoda
typeString()method that returns a string name for its class
Multibody root body#
Each Multibody instance has a unique name with a virtual root body
named as _MBVROOT_<mbname>. The isRootBody() method returns true
for only the multibody root physical body, and false for other
physical body instances.
Coordinates#
Coordinate values define the state of the multibody system. The following naming conventions are used at all levels (eg. subhinge, hinge, body deformation, subtree etc) for the coordinate variables:
Qis used for configuration coordinatesQdotis used for the time derivatives forQ.Uis used for velocity coordinates. OftenUis the same asQdot, and that is not always the case. In some cases, such as spherical subhinges,Uis made of quasi-velocity coordinatesUdotis used for the time derivatives ofUTis used for generalized forces
The nQ() method is used to query the size of the Q coordinates, and
the nU() method for the size of the U coordinates. In most cases,
the size of the Q and U coordinates are the same. However this is
not a requirement, and an exception to this is the
SphericalQuatSubhinge class where there the number of Q coordinates
is larger than the number of U coordinates. The sizes of U and T
are always equal.
Passive Rotation Convention#
In kdFlex a rotation matrix (or quaternion) is a passive transform that takes a vector from a body‑fixed frame and expresses it in the world (or parent) frame. All internal calculations, Jacobians, and dynamics equations are built around this convention, so users should always interpret rotation data as “world‑to‑body” transforms. Thus a vector transforms as \({}^a v = {}^aR_b\ {}^b v\), i.e., it is rotated from the “b” frame into the “a” frame.
All kdFlex modules adhere to this convention, so you can freely mix and match objects without worrying about hidden frame flips.
UnitQuaternion versus RotationVector attitude representations#
kdFlex relies almost completely on the UnitQuaternion class for
attitude related computations. Unit quaternions avoid the need for
trigonometric values and are computationally efficient. The one drawback
of unit quaternions is that the unit quaternion is a 4-parameter
parameter representation of attitude. While the redundant coordinate
helps avoid singularities in this attitude representation, it does
require the enforcement of the unit norm constraint on the attitude
representation.
The SphericalSubhinge allows arbitrary relative orientation across the
subhinge, and requires choosing Q generalized coordinates to
parameterize such relative orientation. Using unit quaternions for Q
coordinates for this subhinge type would lead to non-minimal, redundant
coordinate representation. Non-minimal coordinates require differential
algebraic solvers (DAE) for numerical integration, as well as requiring
more complex dynamics solution methods to handle the redundant
coordinate constraints.
To avoid the introduction of this unit norm constraint into dynamics
computations, the SphericalSubhinge class uses the minimal 3-vector
RotationVector representation for its Q coordinates for the relative
attitude across the subhinge. This is the one exception to use of unit
quaternions elsewhere. The use of minimal 3-vector representation for
attitude does introduce singularities in the attitude representation.
Local charts are used t to handle these singularities.
Ktime for time representation#
kdFlex – Ktime: the canonical time type
kdFlex represents all simulation times with a single, high‑precision type called Ktime. The design of Ktime is driven by the need for unambiguous, nanosecond‑accurate timestamps that can be used both in the C++ core and in the Python bindings.
What is Ktime?#
Layer |
Definition |
Precision |
Why it matters |
|---|---|---|---|
C++ |
|
1 ns |
Guarantees that every time value is an integer number of nanoseconds, eliminating floating‑point rounding errors. |
Python |
|
1 ns |
Mirrors the C++ type; |
The choice of nanoseconds is deliberate: it is the smallest unit that still fits comfortably in a 64‑bit integer (about 292 years of simulation time) and is large enough to avoid overflow in most practical simulations.
kdFlex ships with a small helper Ktime secondsToKtime(double seconds) to go from a floating‑point second value to Ktime. In Python the conversion is handled automatically by the pybind11 wrapper, so a user can simply pass a float or a numpy.timedelta64 and the binding will take care of the conversion.
Why a dedicated time type?#
Determinism – All time arithmetic is integer‑based, so two runs with identical inputs will produce identical timestamps, even on different hardware or operating systems.
Precision – Nanosecond resolution is more than enough for most simulations (e.g., 1 ms or 10 ms integrator steps) while still allowing very long simulation horizons.
Interoperability – The same type is exposed to Python, so there is no hidden conversion between C++ and Python that could introduce drift.
Performance –
std::chrono::nanosecondsis a lightweight wrapper aroundint64_t; arithmetic is as fast as plain integer math.
Spatial Vectors#
kdFlex uses spatial notation throughout its kinematics and dynamics
code. A SpatialVector is the canonical container for any
6‑dimensional quantity that has an angular part w (usually a torque or
angular velocity) and a linear part v (usually a force or linear
velocity). The class is defined in Karana/Math/SpatialVector.h and is
exposed to both C++ and Python. The class itself is lightweight – it simply holds two Vec3 members. However, on conversion to a 6-vector, the w (angular) values are stored in the first three components, and the v (linear) in the last three components.
*
Why spatial notation?#
Compactness – A 6‑D vector replaces two 3‑D vectors and a cross‑product operation.
Consistency – The same data structure is used for velocities, forces, and Jacobian rows, making the code easier to read and maintain.
Transformations - rigid-body transformations are more concise and simpler simpler with spatial vector notation.
Mathematical elegance – The Newton‑Euler equations and the recursive Newton‑Euler algorithm are naturally expressed in spatial form.
The SpatialVector class is a lightweight, 6‑dimensional container that
holds an angular part (w) and a linear part (v). It is used
everywhere in kdFlex where a quantity naturally has both angular and
linear components—spatial velocities, forces, Jacobian rows, and
constraint forces. By treating these quantities as a single object, the
its kinematics and dynamics quantities are concise, mathematically
clean, and efficient.
Concepts#
Integrator owns the state#
In the context of time domain simulation using the StatePropagator,
the system continuous state consists of the subgraph’s Q and
U coordinates, together with any continuous states that the KModel
instances may have. In this scenario, the integrator is responsible
for storing and updating the system state over time. As such, the
integrator is the component that owns the system state.
An important implications of this is that initializing or changing the
Q and U values in the subgraph by itself has no effect on the system
state. The state value has to be transferred to the integrator for the
new state values to have an effect. The usual steps to do this are:
set the new state values in the subgraph’s coordinates and KModel instances
call
x = StatePropagator::assembleState()to gather up these values into a vector for the integratorcall
StatePropagator::setState(x)to transfer the state to the integrator.
A common mistake is for users to just do the first step above, and see the changes have no effect! The latter steps are required.
Software#
Object life-cycle#
kdFlex follows a strict factory‑plus‑explicit‑destruction pattern.
All objects are created through a static create() method and destroyed
by calling the instance’s discard() method. This convention is
documented in the usage guide and is enforced by the C++ core as well as
the Python bindings. What you should remember:
Never use
newordeletedirectly – always usecreate()anddiscard().Do not keep raw pointers – keep the
kc::ks_ptr<T>(or the Python object) as long as you need the object.Call
discard()when you’re done – this frees resources immediately and avoids dangling references.Python users – you can rely on automatic cleanup, but calling
discard()explicitly is good practice, especially in long‑running simulations.
The one exception to this using is the Node class which has the
lookupOrCreate() method in place of the create() method. For nodes,
lookupOrCreate() is simply a higher‑level facade that leverages
kdFlex’s core creation/destruction convention while adding name‑based
lookup semantics from the pool of detached Node objects. The
Node::detach() method can be used to release a node from a body and
add it to the pool. The discard() can be used to permanently delete a
node.
kdFlex’s object life-cycle is governed by the create() factory method
and the discard() destructor method. This pattern guarantees safe
memory management, consistent API usage, and seamless integration
between C++ and Python.
Object health#
In kdflex, objects constructors generally perform only essential work
and leave objects in a unhealthy state during the over overall model
construction process. ensureHealthy() is a runtime‑sanity helper
that is available for objects to verify that all objects they depend on
are healthy, and to finish up any remaining setup steps needed to get
them into configuration suitable for use by the simulation engine. Thus
for instance the FrameContainer::ensureHealthy() method verifies that
there are no unattached frames in the system, and updates internal
caches needed at run-time.
Many methods can only be called on healthy objects, and use the
isHealthy() method to check the object state. The ensureHealthy()
method is usually called implicitly by higher‑level classes
(Multibody, Scene, StatePropagator) but can be called explicitly
by the user when they want to validate an object after a batch of
changes. It is thus only necessary to call ensureHealthy() on the
highest level object, such as the Multibody or StatePropagator
instance the end of the model definition to get all the objects ready
for computations. Calling it on a healthy object only involves checking
the health flag and is inexpensive and be safely called multiple times.
Run-time configuration changes, such as from adding or reattaching
bodies, will automatically mark objects such as the Multibody instance
as unhealthy, and will thus require calls to ensureHealthy() before
continuing with the simulation.
A healthy state only ensures the structural integrity of an object, and
by itself does not imply that required numerical parameters (eg. mass
properties) etc have been initialized. An object is truly ready for
computational use only after such parameters have been set, and the
isReady() method can be used to verify that this has been done. Bottom
line, the isHealthy() and isReady() methods should both return true
for an object to be ready for computations.
Object numerical readiness#
Uninitialized parameters and state values are some of the most difficult
errors to track down. Downstream errors can be difficult to track down
to the root cause from such uninitialized values. To address this,
kdflex sets all parameter and state values to known uninitialized
values. The isReady() method is available for all objects derived from
Base. This method returns false if any of the numerical parameters and
states for the object are detected as remaining uninitialized. In
addition, the Base::allReady() method is available to verify that all
objects parameters have been initialized, i.e. the objects are ready for
numerical computation. This method will generate messages for any
objects that are detected as uninitialized. It is highly recommended
that allReady() be called before initiating simulation computations.
In summary, it is recommended that the simulation include the following steps:
create simulation objects
call
assert <top level>.ensureHealthy()on the top level object to ensure that all objects are healthyinitialize all numerical parameters and states in the multibody system and the
KModelinstancescall
assert Karana.Core.allReady()to verify that there are no uninitialized parameters remaining in the systemcarry out simulation computations