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

  • ClassesPascalCase (e.g., Multibody, PhysicalBody).

  • Functions / helperssnake_case (e.g., discard(), allReady()).

  • Constants / enumsUPPER_SNAKE_CASE (e.g., IntegratorType.EULER, MMSolverType.SINGLE).

Namespace

Purpose

Typical imports

Key classes / functions

Karana.Core (kc)

Core utilities that are shared across all layers (memory, containers, helpers).

import  Karana.Core as kd

discard, allReady, BaseContainer

Karana.Frame (kf)

Handles the frame hierarchy – the kinematic tree of coordinate frames.

import Karana.Frame as kf

FrameContainer, Frame

Karana.Math (km)

Mathematical primitives (vectors, matrices, quaternions, integrators).

import  Karana.Math as km

SpatialInertia, UnitQuaternion, HomTran

Karana.Dynamics (kd)

Multibody dynamics engine (bodies, hinges, constraints, kinematics, dynamics).

import Karana.Dynamics as kd

Multibody, PhysicalBody, HingeType, StatePropagator, TimedEvent

Karana.Scene (ks)

Geometry and visualization

impost Karana.Scene as ks

Scene, ProxyScene, WebScene

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

Karana::Core (kc)

Low‑level utilities shared by all modules (memory, containers, helpers).

BaseContainer, Allocator, ks_ptr, discard(), allReady()

Karana::Frame (kf)

Frame hierarchy – the kinematic tree of coordinate frames.

FrameContainer, Frame, HomTran

Karana::Math (km)

Mathematical primitives (vectors, matrices, quaternions, integrators).

Vec, Vec3, Mat, Mat33, UnitQuaternion, SpatialInertia, IntegratorType

Karana::Dynamics (kd)

Multibody dynamics engine (SOA, ATBI/ABI, constraints).

Multibody, PhysicalBody, HingeType, StatePropagator, TimedEvent, MMSolverType

Karana::Scene (ks)

Geometries and visualization

ProxyScene, ScenePart, Color

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() method

  • a unique numeric id, accessed via the id() method

  • a 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:

  • Q is used for configuration coordinates

  • Qdot is used for the time derivatives for Q.

  • U is used for velocity coordinates. Often U is the same as Qdot, and that is not always the case. In some cases, such as spherical subhinges, U is made of quasi-velocity coordinates

  • Udot is used for the time derivatives of U

  • T is 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++

using Ktime = std::chrono::nanoseconds; (in Karana::Math::Defs.h)

1 ns

Guarantees that every time value is an integer number of nanoseconds, eliminating floating‑point rounding errors.

Python

numpy.timedelta64 (via a pybind11 wrapper)

1 ns

Mirrors the C++ type; numpy.timedelta64 can represent nanoseconds exactly and integrates cleanly with NumPy arrays.

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?#

  1. Determinism – All time arithmetic is integer‑based, so two runs with identical inputs will produce identical timestamps, even on different hardware or operating systems.

  2. 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.

  3. Interoperability – The same type is exposed to Python, so there is no hidden conversion between C++ and Python that could introduce drift.

  4. Performancestd::chrono::nanoseconds is a lightweight wrapper around int64_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 integrator

  • call 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:

  1. Never use new or delete directly – always use create() and discard().

  2. Do not keep raw pointers – keep the kc::ks_ptr<T> (or the Python object) as long as you need the object.

  3. Call discard() when you’re done – this frees resources immediately and avoids dangling references.

  4. 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 healthy

  • initialize all numerical parameters and states in the multibody system and the KModel instances

  • call assert Karana.Core.allReady()to verify that there are no uninitialized parameters remaining in the system

  • carry out simulation computations