.. _integratorArchitecture: Dynamics state and integrator architecture ========================================== Basilisk separates the definition of a dynamics model from the storage and arithmetic used to integrate it. A model registers named states once, then reads their values and writes derivatives through stable ``StateData`` handles. Integrators bind to the finalized storage and reuse flat numerical buffers for every stage. BSM spacecraft and MuJoCo scenes use this same integration layer, with different equations of motion and state-update rules. This guide explains the boundaries between those components. For a minimal model implementation, start with :ref:`creatingDynObject`; for MuJoCo model setup, see :ref:`mujocoDynObject`. Components and ownership ------------------------ .. list-table:: :header-rows: 1 :widths: 24 39 37 * - Component - Responsibility - Owns or borrows * - ``DynamicObject`` - Coordinates Reset, integration, and synchronized models. - Owns its ``DynParamManager`` and active integrator. * - ``DynParamManager`` - Exposes registration, state lookup, and named properties to modules. - Owns properties and one native ``StateRegistry``. * - ``StateRegistry`` - Validates declarations and allocates fixed contiguous state storage. - Owns state handles, update policies, layouts, and value buffers. * - ``StateData`` - Provides shaped access to one state's values, drift, and diffusion. - Borrows storage from its registry; models borrow the handle. * - ``StateVecIntegrator`` - Prepares workspaces once and calls the dynamics models. - Borrows the ordered list of synchronized dynamics objects. * - ``FlatStateBinding`` - Translates registered states into validated offsets and update runs. - Borrows finalized buffers and policies; owns descriptors. * - Numerical integrator and workspace - Evaluates stages and combines their derivatives or noise tangents. - Owns preallocated stage, candidate, and rollback buffers. The usual module-facing path is:: DynamicObject dynManager.registerState(...) -> borrowed StateData* equationsOfMotion(...) -> stateView(), derivativeView() integrateState(...) -> owned StateVecIntegrator The integrator-facing path is:: StateVecIntegrator FlatStateBinding -> finalized StateRegistry layouts and buffers stage workspace -> candidate -> live buffers -> dynamics callback <- captured derivatives and diffusion tangents To access contiguous segments or complete state layouts from C++, include ``stateRegistry.h`` and call ``DynParamManager::getStateRegistry()``. Registration, values, and shapes -------------------------------- A state declaration has a name and a ``StateSpec``. The specification records the state matrix shape, derivative shape, diffusion-tangent shape, local noise count, error-control mode, and update kind. The simple ``registerState(rows, cols, name)`` overload declares an ordinary Euclidean state with matching shapes. Each registry stores three column-major arrays of doubles: * state values, in registration order; * derivatives, in registration order; * diffusion tangents, in state order and then local noise-source order. A ``StateLayout`` gives offsets into those arrays. Its offsets count scalars, not bytes. An integrator concatenates objects in synchronized integration order; its descriptors therefore also have offsets into the integrator's combined buffers. Registry-local and integrator-global offsets are different coordinate systems and must not be interchanged. For a Euclidean state, propagation uses ordinary addition. A special state instead supplies an immutable ``StateUpdatePolicy`` that constructs drift candidates and applies diffusion increments. Its derivative or tangent may have fewer components than its stored value: a unit quaternion, for example, has four stored components but can use a three-component angular-velocity derivative. The policy owns the meaning of that update; the integrator owns the stage coefficients and ordering. ``StateData`` is a handle, not an owning matrix. ``stateView()``, ``derivativeView()``, and ``diffusionView(i)`` return borrowed Eigen maps. Use them for local calculations and reacquire them after the first finalization. Later resets preserve their addresses. The copying getters, such as ``getState()``, remain useful when a snapshot must survive a later update. Setters require exact shapes and never resize established state storage. Properties are separate named matrices used for model information that is not itself integrated. They belong to ``DynParamManager`` and do not appear in integrator workspaces. Registration and Reset ---------------------- A model's Reset declares and initializes states, finalizes storage, links handles, and performs the remaining model setup:: Reset register named states and initialize values finalizeStates(): allocate contiguous storage on the first call link handles and finish initialization Before finalization, each state has temporary matrices because the total storage size is not yet known. Finalization computes offsets, allocates the three buffers, copies initial values, and redirects the existing handles into those buffers. The temporary matrices are then released. The layout and buffer addresses remain fixed for the manager's lifetime. Later resets look up states by name and write values directly into the same storage. Declarations may occur in a different order, but each existing state's dimensions, update policy, and noise count must match. Shared-noise groups may be repeated with their endpoints in any order. New states or changed noise connections require a new manager. Repeated calls to ``finalizeStates()`` have no effect, including for an empty finalized manager. Reset is ordinary model initialization. Errors propagate to the caller and may leave partially initialized values. There is no registration transaction, saved pre-Reset copy, or automatic retry protocol. Complete model initialization successfully before integration. Binding and lifetime -------------------- An integrator prepares its descriptors and numerical workspaces on its first step. Later steps reuse that storage. If a native secondary is destroyed, the surviving primary rebuilds its workspace before advancing again. The registry records whether its buffers have been allocated, and the integrator records whether its workspace is prepared. These flags track one-time setup; they do not describe Reset success or recovery from model errors. ``StateBufferSegment`` identifies a registry, a buffer kind, and a range of scalars. Resolve it through the owning registry before using the pointer. Matching resets preserve the range and its address. The registry must outlive the segment and any pointer obtained from it; segments do not track owner destruction or allocator address reuse. The primary ``DynamicObject`` owns the integrator and calls the synchronized objects' pre- and post-integration hooks. Configure synchronization before the first step. Do not destroy objects, replace the integrator, or modify the integration group from a dynamics callback. ``setIntegrator()`` transfers the dynamics list and destroys the previous integrator. Pointers to that previous integrator are no longer valid. Python retains synchronized secondaries for the primary's lifetime. Native connections are detached when either object is destroyed; a surviving secondary can integrate independently again. Keep the dynamics object alive while using its borrowed manager or integrator. Reacquire the integrator property after replacement. Fixed and adaptive Runge--Kutta ------------------------------- For ordinary states, a fixed-step explicit RK method evaluates .. math:: k_i &= f\left(t+c_i h,\;x_n+h\sum_{j