Skip to main content

rapier3d/dynamics/joint/multibody_joint/
multibody.rs

1use super::multibody_link::{MultibodyLink, MultibodyLinkVec};
2use super::multibody_workspace::MultibodyWorkspace;
3use crate::alloc_prelude::*;
4use crate::dynamics::integration_parameters::SpringCoefficients;
5use crate::dynamics::solver::{GenericJointConstraint, WritebackId};
6use crate::dynamics::{
7    IntegrationParameters, RigidBodyHandle, RigidBodySet, RigidBodyType, RigidBodyVelocity,
8};
9use crate::math::{
10    ANG_DIM, AngDim, AngVector, DIM, DVector, Dim, Jacobian, Pose, Real, SPATIAL_DIM,
11    SimdAngVector, Vector,
12};
13use crate::prelude::MultibodyJoint;
14#[cfg(feature = "dim3")]
15use crate::utils::mat_to_na;
16use crate::utils::{AngularInertiaOps, CrossProduct, CrossProductMatrix, IndexMut2, vect_to_na};
17use na::{
18    self, DMatrix, DVectorView, DVectorViewMut, Dyn, LU, OMatrix, SMatrix, SVector, StorageMut,
19};
20
21#[cfg(doc)]
22use crate::prelude::{GenericJoint, RigidBody};
23
24#[repr(C)]
25#[derive(Copy, Clone, Debug, Default)]
26struct Force {
27    linear: Vector,
28    angular: AngVector,
29}
30
31impl Force {
32    fn new(linear: Vector, angular: AngVector) -> Self {
33        Self { linear, angular }
34    }
35
36    fn as_vector(&self) -> &SVector<Real, SPATIAL_DIM> {
37        unsafe { core::mem::transmute(self) }
38    }
39}
40
41#[cfg(feature = "dim2")]
42fn concat_rb_mass_matrix(mass: Vector, inertia: Real) -> SMatrix<Real, SPATIAL_DIM, SPATIAL_DIM> {
43    let mut result = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
44    result[(0, 0)] = mass.x;
45    result[(1, 1)] = mass.y;
46    result[(2, 2)] = inertia;
47    result
48}
49
50#[cfg(feature = "dim3")]
51fn concat_rb_mass_matrix(
52    mass: Vector,
53    inertia: na::Matrix3<Real>,
54) -> SMatrix<Real, SPATIAL_DIM, SPATIAL_DIM> {
55    let mut result = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
56    result[(0, 0)] = mass.x;
57    result[(1, 1)] = mass.y;
58    result[(2, 2)] = mass.z;
59    result
60        .fixed_view_mut::<ANG_DIM, ANG_DIM>(DIM, DIM)
61        .copy_from(&inertia);
62    result
63}
64
65/// A holonomic coupling between two generalized coordinates of a single
66/// [`Multibody`], `q2 = coeff · q1 + offset`, enforced as a velocity-level
67/// equality constraint. This is how MuJoCo's `<equality><joint>` (a polynomial
68/// joint-to-joint coupling) is represented for the linear (first-order) case —
69/// e.g. the robotiq gripper's two driver joints moving together.
70#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
71#[derive(Copy, Clone, Debug)]
72pub struct MultibodyDofCoupling {
73    /// Internal id of the link carrying the first joint.
74    pub link1: usize,
75    /// Local free-DoF index of the coupled DoF within `link1` (its position in
76    /// that link's slice of the generalized vectors).
77    pub dof1: usize,
78    /// Spatial-coordinate axis (`0..6`) of `link1`'s coupled DoF, used to read
79    /// its generalized position from the joint coords.
80    pub axis1: usize,
81    /// Internal id of the link carrying the second joint.
82    pub link2: usize,
83    /// Local free-DoF index of the coupled DoF within `link2`.
84    pub dof2: usize,
85    /// Spatial-coordinate axis (`0..6`) of `link2`'s coupled DoF.
86    pub axis2: usize,
87    /// Linear coupling coefficient: the constraint is `q2 − coeff·q1 − offset = 0`.
88    pub coeff: Real,
89    /// Constant offset of the coupling.
90    pub offset: Real,
91}
92
93/// An articulated body simulated using the reduced-coordinates approach.
94#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
95#[derive(Clone, Debug)]
96pub struct Multibody {
97    // TODO: serialization: skip the workspace fields.
98    pub(crate) links: MultibodyLinkVec,
99    pub(crate) velocities: DVector,
100    pub(crate) damping: DVector,
101    /// Per-DoF reflected rotor inertia (matches MuJoCo’s concept of `armature`).
102    pub(crate) armature: DVector,
103    pub(crate) accelerations: DVector,
104
105    body_jacobians: Vec<Jacobian<Real>>,
106    // NOTE: the mass matrices are dimensioned based on the non-kinematic degrees of
107    //       freedoms only. The `Self::augmented_mass_permutation` sequence can be used to
108    //       move dofs from/to a format that matches the augmented mass.
109    // TODO: use sparse matrices?
110    augmented_mass: DMatrix<Real>,
111    inv_augmented_mass: LU<Real, Dyn, Dyn>,
112    // The indexing sequence for moving all kinematics degrees of
113    // freedoms to the end of the generalized coordinates vector.
114    augmented_mass_indices: IndexSequence,
115
116    acc_augmented_mass: DMatrix<Real>,
117    acc_inv_augmented_mass: LU<Real, Dyn, Dyn>,
118
119    ndofs: usize,
120    pub(crate) root_is_dynamic: bool,
121    pub(crate) solver_id: u32,
122    self_contacts_enabled: bool,
123    /// Holonomic couplings between two of this multibody's generalized
124    /// coordinates (`q2 = coeff·q1 + offset`), e.g. MuJoCo's
125    /// `<equality><joint>`. Resolved as velocity constraints each step.
126    couplings: Vec<MultibodyDofCoupling>,
127
128    /*
129     * Workspaces.
130     */
131    workspace: MultibodyWorkspace,
132    coriolis_v: Vec<OMatrix<Real, Dim, Dyn>>,
133    coriolis_w: Vec<OMatrix<Real, AngDim, Dyn>>,
134    i_coriolis_dt: Jacobian<Real>,
135}
136impl Default for Multibody {
137    fn default() -> Self {
138        Multibody::new()
139    }
140}
141
142impl Multibody {
143    /// Creates a new multibody with no link.
144    pub fn new() -> Self {
145        Self::with_self_contacts(true)
146    }
147
148    pub(crate) fn with_self_contacts(self_contacts_enabled: bool) -> Self {
149        Multibody {
150            links: MultibodyLinkVec(Vec::new()),
151            velocities: DVector::zeros(0),
152            damping: DVector::zeros(0),
153            armature: DVector::zeros(0),
154            accelerations: DVector::zeros(0),
155            body_jacobians: Vec::new(),
156            augmented_mass: DMatrix::zeros(0, 0),
157            inv_augmented_mass: LU::new(DMatrix::zeros(0, 0)),
158            acc_augmented_mass: DMatrix::zeros(0, 0),
159            acc_inv_augmented_mass: LU::new(DMatrix::zeros(0, 0)),
160            augmented_mass_indices: IndexSequence::new(),
161            ndofs: 0,
162            solver_id: 0,
163            workspace: MultibodyWorkspace::new(),
164            coriolis_v: Vec::new(),
165            coriolis_w: Vec::new(),
166            i_coriolis_dt: Jacobian::zeros(0),
167            root_is_dynamic: false,
168            self_contacts_enabled,
169            couplings: Vec::new(),
170            // solver_workspace: Some(SolverWorkspace::new()),
171        }
172    }
173
174    pub(crate) fn with_root(handle: RigidBodyHandle, self_contacts_enabled: bool) -> Self {
175        let mut mb = Multibody::with_self_contacts(self_contacts_enabled);
176        // NOTE: we have no way of knowing if the root in fixed at this point, so
177        //       we mark it as dynamic and will fix later with `Self::update_root_type`.
178        mb.root_is_dynamic = true;
179        let joint = MultibodyJoint::free(Pose::IDENTITY);
180        mb.add_link(None, joint, handle);
181        mb
182    }
183
184    pub(crate) fn remove_link(self, to_remove: usize, joint_only: bool) -> Vec<Multibody> {
185        let mut result = vec![];
186        let mut link2mb = vec![usize::MAX; self.links.len()];
187        let mut link_id2new_id = vec![usize::MAX; self.links.len()];
188
189        // Split multibody and update the set of links and ndofs.
190        for (i, mut link) in self.links.0.into_iter().enumerate() {
191            let is_new_root = i == 0
192                || !joint_only && link.parent_internal_id == to_remove
193                || joint_only && i == to_remove;
194
195            if !joint_only && i == to_remove {
196                continue;
197            } else if is_new_root {
198                link2mb[i] = result.len();
199                result.push(Multibody::with_self_contacts(self.self_contacts_enabled));
200            } else {
201                link2mb[i] = link2mb[link.parent_internal_id]
202            }
203
204            let curr_mb = &mut result[link2mb[i]];
205            link_id2new_id[i] = curr_mb.links.len();
206
207            if is_new_root {
208                let joint = MultibodyJoint::fixed(*link.local_to_world());
209                link.joint = joint;
210            }
211
212            curr_mb.ndofs += link.joint().ndofs();
213            curr_mb.links.push(link);
214        }
215
216        // Adjust all the internal ids, and copy the data from the
217        // previous multibody to the new one.
218        for mb in &mut result {
219            mb.grow_buffers(mb.ndofs, mb.links.len());
220            mb.workspace.resize(mb.links.len(), mb.ndofs);
221
222            let mut assembly_id = 0;
223            for (i, link) in mb.links.iter_mut().enumerate() {
224                let link_ndofs = link.joint().ndofs();
225                mb.velocities
226                    .rows_mut(assembly_id, link_ndofs)
227                    .copy_from(&self.velocities.rows(link.assembly_id, link_ndofs));
228                mb.damping
229                    .rows_mut(assembly_id, link_ndofs)
230                    .copy_from(&self.damping.rows(link.assembly_id, link_ndofs));
231                mb.armature
232                    .rows_mut(assembly_id, link_ndofs)
233                    .copy_from(&self.armature.rows(link.assembly_id, link_ndofs));
234                mb.accelerations
235                    .rows_mut(assembly_id, link_ndofs)
236                    .copy_from(&self.accelerations.rows(link.assembly_id, link_ndofs));
237
238                link.internal_id = i;
239                link.assembly_id = assembly_id;
240
241                // NOTE: for the root, the current`link.parent_internal_id` is invalid since that
242                //       parent lies in a different multibody now.
243                link.parent_internal_id = if i != 0 {
244                    link_id2new_id[link.parent_internal_id]
245                } else {
246                    0
247                };
248                assembly_id += link_ndofs;
249            }
250        }
251
252        result
253    }
254
255    pub(crate) fn append(&mut self, mut rhs: Multibody, parent: usize, joint: MultibodyJoint) {
256        let joint_ndofs = joint.ndofs();
257        let rhs_root_ndofs = rhs.links[0].joint.ndofs();
258        let ndofs_before_append = self.velocities.len();
259        let base_internal_id = self.links.len();
260        // Values for rhs will be copied into the buffers of `self` starting at this index.
261        let rhs_copy_shift = ndofs_before_append + joint_ndofs;
262        // Number of dofs to copy from rhs. The root’s dofs isn’t included because it will be
263        // replaced by `joint`.
264        let rhs_copy_ndofs = rhs.ndofs - rhs_root_ndofs;
265
266        // Adjust the ids of all the rhs links except the first one.
267        for link in &mut rhs.links.0[1..] {
268            link.assembly_id =
269                (link.assembly_id + ndofs_before_append + joint_ndofs) - rhs_root_ndofs;
270            link.internal_id += base_internal_id;
271            link.parent_internal_id += base_internal_id;
272        }
273
274        // Adjust the first link.
275        {
276            rhs.links[0].joint = joint;
277            rhs.links[0].assembly_id = ndofs_before_append;
278            rhs.links[0].internal_id = base_internal_id;
279            rhs.links[0].parent_internal_id = parent;
280        }
281
282        // Grow buffers then append data from rhs.
283        self.grow_buffers(rhs_copy_ndofs + joint_ndofs, rhs.links.len());
284
285        if rhs_copy_ndofs > 0 {
286            self.velocities
287                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
288                .copy_from(&rhs.velocities.rows(rhs_root_ndofs, rhs_copy_ndofs));
289            self.damping
290                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
291                .copy_from(&rhs.damping.rows(rhs_root_ndofs, rhs_copy_ndofs));
292            self.armature
293                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
294                .copy_from(&rhs.armature.rows(rhs_root_ndofs, rhs_copy_ndofs));
295            self.accelerations
296                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
297                .copy_from(&rhs.accelerations.rows(rhs_root_ndofs, rhs_copy_ndofs));
298        }
299
300        // Set the default damping for the new joint.
301        rhs.links[0]
302            .joint
303            .default_damping(&mut self.damping.rows_mut(ndofs_before_append, joint_ndofs));
304
305        self.links.append(&mut rhs.links);
306        self.ndofs = self.velocities.len();
307        self.workspace.resize(self.links.len(), self.ndofs);
308    }
309
310    /// Whether self-contacts are enabled on this multibody.
311    ///
312    /// If set to `false` no two link from this multibody can generate contacts, even
313    /// if the contact is enabled on the individual joint with [`GenericJoint::contacts_enabled`].
314    pub fn self_contacts_enabled(&self) -> bool {
315        self.self_contacts_enabled
316    }
317
318    /// Sets whether self-contacts are enabled on this multibody.
319    ///
320    /// If set to `false` no two link from this multibody can generate contacts, even
321    /// if the contact is enabled on the individual joint with [`GenericJoint::contacts_enabled`].
322    pub fn set_self_contacts_enabled(&mut self, enabled: bool) {
323        self.self_contacts_enabled = enabled;
324    }
325
326    /// The inverse augmented mass matrix of this multibody.
327    pub fn inv_augmented_mass(&self) -> &LU<Real, Dyn, Dyn> {
328        &self.inv_augmented_mass
329    }
330
331    /// The first link of this multibody.
332    #[inline]
333    pub fn root(&self) -> &MultibodyLink {
334        &self.links[0]
335    }
336
337    /// Mutable reference to the first link of this multibody.
338    #[inline]
339    pub fn root_mut(&mut self) -> &mut MultibodyLink {
340        &mut self.links[0]
341    }
342
343    /// Reference `i`-th multibody link of this multibody.
344    ///
345    /// Return `None` if there is less than `i + 1` multibody links.
346    #[inline]
347    pub fn link(&self, id: usize) -> Option<&MultibodyLink> {
348        self.links.get(id)
349    }
350
351    /// Mutable reference to the multibody link with the given id.
352    ///
353    /// Return `None` if the given id does not identifies a multibody link part of `self`.
354    #[inline]
355    pub fn link_mut(&mut self, id: usize) -> Option<&mut MultibodyLink> {
356        self.links.get_mut(id)
357    }
358
359    /// The number of links on this multibody.
360    pub fn num_links(&self) -> usize {
361        self.links.len()
362    }
363
364    /// Iterator through all the links of this multibody.
365    ///
366    /// All link are guaranteed to be yielded before its descendant.
367    pub fn links(&self) -> impl Iterator<Item = &MultibodyLink> {
368        self.links.iter()
369    }
370
371    /// Mutable iterator through all the links of this multibody.
372    ///
373    /// All link are guaranteed to be yielded before its descendant.
374    pub fn links_mut(&mut self) -> impl Iterator<Item = &mut MultibodyLink> {
375        self.links.iter_mut()
376    }
377
378    /// The vector of damping applied to this multibody.
379    #[inline]
380    pub fn damping(&self) -> &DVector {
381        &self.damping
382    }
383
384    /// Mutable vector of damping applied to this multibody.
385    #[inline]
386    pub fn damping_mut(&mut self) -> &mut DVector {
387        &mut self.damping
388    }
389
390    /// The vector of per-DoF armature (reflected rotor inertia) of this multibody: additional
391    /// inertia added directly to the mass matrix, simulating the joint's own weight distribution.
392    #[inline]
393    pub fn armature(&self) -> &DVector {
394        &self.armature
395    }
396
397    /// Mutable vector of per-DoF armature (reflected rotor inertia) of this
398    /// multibody.
399    #[inline]
400    pub fn armature_mut(&mut self) -> &mut DVector {
401        &mut self.armature
402    }
403
404    pub(crate) fn add_link(
405        &mut self,
406        parent: Option<usize>, // TODO: should be a RigidBodyHandle?
407        dof: MultibodyJoint,
408        body: RigidBodyHandle,
409    ) -> &mut MultibodyLink {
410        assert!(
411            parent.is_none() || !self.links.is_empty(),
412            "Multibody::build_body: invalid parent id."
413        );
414
415        /*
416         * Compute the indices.
417         */
418        let assembly_id = self.velocities.len();
419        let internal_id = self.links.len();
420
421        /*
422         * Grow the buffers.
423         */
424        let ndofs = dof.ndofs();
425        self.grow_buffers(ndofs, 1);
426        self.ndofs += ndofs;
427
428        /*
429         * Setup default damping.
430         */
431        dof.default_damping(&mut self.damping.rows_mut(assembly_id, ndofs));
432
433        /*
434         * Create the multibody.
435         */
436        let local_to_parent = dof.body_to_parent();
437        let local_to_world;
438
439        let parent_internal_id;
440        if let Some(parent) = parent {
441            parent_internal_id = parent;
442            let parent_link = &mut self.links[parent_internal_id];
443            local_to_world = parent_link.local_to_world * local_to_parent;
444        } else {
445            parent_internal_id = 0;
446            local_to_world = local_to_parent;
447        }
448
449        let rb = MultibodyLink::new(
450            body,
451            internal_id,
452            assembly_id,
453            parent_internal_id,
454            dof,
455            local_to_world,
456            local_to_parent,
457        );
458
459        self.links.push(rb);
460        self.workspace.resize(self.links.len(), self.ndofs);
461
462        &mut self.links[internal_id]
463    }
464
465    fn grow_buffers(&mut self, ndofs: usize, num_jacobians: usize) {
466        let len = self.velocities.len();
467        self.velocities.resize_vertically_mut(len + ndofs, 0.0);
468        self.damping.resize_vertically_mut(len + ndofs, 0.0);
469        self.armature.resize_vertically_mut(len + ndofs, 0.0);
470        self.accelerations.resize_vertically_mut(len + ndofs, 0.0);
471        self.body_jacobians
472            .extend((0..num_jacobians).map(|_| Jacobian::zeros(0)));
473    }
474
475    pub(crate) fn update_acceleration(&mut self, dt: Real, bodies: &RigidBodySet) {
476        if self.ndofs == 0 {
477            return; // Nothing to do.
478        }
479
480        self.accelerations.fill(0.0);
481
482        // Eqn 42 to 45
483        for i in 0..self.links.len() {
484            let link = &self.links[i];
485            let rb = &bodies[link.rigid_body];
486
487            let mut acc = RigidBodyVelocity::zero();
488
489            if i != 0 {
490                let parent_id = link.parent_internal_id;
491                let parent_link = &self.links[parent_id];
492                let parent_rb = &bodies[parent_link.rigid_body];
493
494                acc += self.workspace.accs[parent_id];
495                // The 2.0 originates from the two identical terms of Jdot (the terms become
496                // identical once they are multiplied by the generalized velocities).
497                acc.linvel += 2.0 * parent_rb.vels.angvel.gcross(link.joint_velocity.linvel);
498                #[cfg(feature = "dim3")]
499                {
500                    acc.angvel += parent_rb.vels.angvel.cross(link.joint_velocity.angvel);
501                }
502
503                acc.linvel += parent_rb
504                    .vels
505                    .angvel
506                    .gcross(parent_rb.vels.angvel.gcross(link.shift02));
507                acc.linvel += self.workspace.accs[parent_id].angvel.gcross(link.shift02);
508            }
509
510            acc.linvel += rb.vels.angvel.gcross(rb.vels.angvel.gcross(link.shift23));
511            acc.linvel += acc.angvel.gcross(link.shift23);
512
513            self.workspace.accs[i] = acc;
514
515            // TODO: should gyroscopic forces already be computed by the rigid-body itself
516            //       (at the same time that we add the gravity force)?
517            let gyroscopic;
518            let rb_inertia = rb.mprops.effective_angular_inertia();
519            let rb_mass = rb.mprops.effective_mass();
520
521            #[cfg(feature = "dim3")]
522            {
523                let angvel = rb.vels.angvel;
524                let inertia_times_angvel = rb_inertia * angvel;
525                gyroscopic = angvel.cross(inertia_times_angvel);
526            }
527            #[cfg(feature = "dim2")]
528            {
529                gyroscopic = 0.0;
530            }
531
532            let external_forces = Force::new(
533                rb.forces.force - rb_mass * acc.linvel,
534                rb.forces.torque - gyroscopic - rb_inertia * acc.angvel,
535            );
536            self.accelerations.gemv_tr(
537                1.0,
538                &self.body_jacobians[i],
539                external_forces.as_vector(),
540                1.0,
541            );
542        }
543
544        self.accelerations
545            .cmpy(-1.0, &self.damping, &self.velocities, 1.0);
546
547        // Implicit joint springs: backward-Euler at `q⁺ = q + dt·v⁺` gives `-k·(q − rest) − k·dt·v⁺`,
548        // with the `v⁺` part implicit via the `dt²·k` mass-matrix diagonal (see `update_mass_matrix`).
549        // Consistency requires BOTH `-k·(q − rest)` and `-k·dt·v` here, else the spring is only semi-implicit.
550        for li in 0..self.links.len() {
551            let mut idx = self.links[li].assembly_id;
552            let locked = self.links[li].joint.data.locked_axes.bits();
553            for a in 0..SPATIAL_DIM {
554                if (locked >> a) & 1 == 0 {
555                    let k = self.links[li].joint.spring_stiffness[a];
556                    if k != 0.0 {
557                        let q = self.links[li].joint.coords[a];
558                        let rest = self.links[li].joint.spring_ref[a];
559                        self.accelerations[idx] += -k * (q - rest) - k * dt * self.velocities[idx];
560                    }
561                    idx += 1;
562                }
563            }
564        }
565
566        self.augmented_mass_indices
567            .with_rearranged_rows_mut(&mut self.accelerations, |accs| {
568                self.acc_inv_augmented_mass.solve_mut(accs);
569            });
570    }
571
572    /// Computes the constant terms of the dynamics.
573    #[profiling::function]
574    pub(crate) fn update_velocities(&mut self, bodies: &mut RigidBodySet) {
575        /*
576         * Compute velocities.
577         * NOTE: this is needed for kinematic bodies too.
578         */
579        let link = &mut self.links[0];
580        let joint_velocity = link
581            .joint
582            .jacobian_mul_coordinates(&self.velocities.as_slice()[link.assembly_id..]);
583
584        link.joint_velocity = joint_velocity;
585        bodies.index_mut_internal(link.rigid_body).vels = link.joint_velocity;
586
587        for i in 1..self.links.len() {
588            let (link, parent_link) = self.links.get_mut_with_parent(i);
589            let rb = &bodies[link.rigid_body];
590            let parent_rb = &bodies[parent_link.rigid_body];
591
592            let joint_velocity = link
593                .joint
594                .jacobian_mul_coordinates(&self.velocities.as_slice()[link.assembly_id..]);
595            link.joint_velocity = joint_velocity.transformed(
596                &(parent_link.local_to_world.rotation * link.joint.data.local_frame1.rotation),
597            );
598            let mut new_rb_vels = parent_rb.vels + link.joint_velocity;
599            let shift = rb.mprops.world_com - parent_rb.mprops.world_com;
600            new_rb_vels.linvel += parent_rb.vels.angvel.gcross(shift);
601            new_rb_vels.linvel += link.joint_velocity.angvel.gcross(link.shift23);
602
603            bodies.index_mut_internal(link.rigid_body).vels = new_rb_vels;
604        }
605    }
606
607    fn update_body_jacobians(&mut self) {
608        for i in 0..self.links.len() {
609            let link = &self.links[i];
610
611            if self.body_jacobians[i].ncols() != self.ndofs {
612                // TODO: use a resize instead.
613                self.body_jacobians[i] = Jacobian::zeros(self.ndofs);
614            }
615
616            let parent_to_world;
617
618            if i != 0 {
619                let parent_id = link.parent_internal_id;
620                let parent_link = &self.links[parent_id];
621                parent_to_world = parent_link.local_to_world;
622
623                let (link_j, parent_j) = self.body_jacobians.index_mut_const(i, parent_id);
624                link_j.copy_from(parent_j);
625
626                {
627                    let mut link_j_v = link_j.fixed_rows_mut::<DIM>(0);
628                    let parent_j_w = parent_j.fixed_rows::<ANG_DIM>(DIM);
629
630                    let shift_tr = vect_to_na(link.shift02).gcross_matrix_tr();
631                    link_j_v.gemm(1.0, &shift_tr, &parent_j_w, 1.0);
632                }
633            } else {
634                self.body_jacobians[i].fill(0.0);
635                parent_to_world = Pose::IDENTITY;
636            }
637
638            let ndofs = link.joint.ndofs();
639            let mut tmp = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
640            let mut link_joint_j = tmp.columns_mut(0, ndofs);
641            let mut link_j_part = self.body_jacobians[i].columns_mut(link.assembly_id, ndofs);
642            link.joint.jacobian(
643                &(parent_to_world.rotation * link.joint.data.local_frame1.rotation),
644                &mut link_joint_j,
645            );
646            link_j_part += link_joint_j;
647
648            {
649                let link_j = &mut self.body_jacobians[i];
650                let (mut link_j_v, link_j_w) =
651                    link_j.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
652                let shift_tr = vect_to_na(link.shift23).gcross_matrix_tr();
653                link_j_v.gemm(1.0, &shift_tr, &link_j_w, 1.0);
654            }
655        }
656    }
657
658    pub(crate) fn update_mass_matrix(&mut self, dt: Real, bodies: &RigidBodySet) {
659        if self.ndofs == 0 {
660            return; // Nothing to do.
661        }
662
663        if self.augmented_mass.ncols() != self.ndofs {
664            // TODO: do a resize instead of a full reallocation.
665            self.augmented_mass = DMatrix::zeros(self.ndofs, self.ndofs);
666            self.acc_augmented_mass = DMatrix::zeros(self.ndofs, self.ndofs);
667        } else {
668            self.augmented_mass.fill(0.0);
669            self.acc_augmented_mass.fill(0.0);
670        }
671
672        self.augmented_mass_indices.clear();
673
674        // Resize coriolis workspaces if the link count or number of DOFs change.
675        let coriolis_ndofs = self.coriolis_v.first().map(|m| m.ncols());
676        if self.coriolis_v.len() != self.links.len() || coriolis_ndofs != Some(self.ndofs) {
677            self.coriolis_v = vec![OMatrix::<Real, Dim, Dyn>::zeros(self.ndofs); self.links.len()];
678            self.coriolis_w =
679                vec![OMatrix::<Real, AngDim, Dyn>::zeros(self.ndofs); self.links.len()];
680            self.i_coriolis_dt = Jacobian::zeros(self.ndofs);
681        }
682
683        let mut curr_assembly_id = 0;
684
685        for i in 0..self.links.len() {
686            let link = &self.links[i];
687            let rb = &bodies[link.rigid_body];
688            let rb_mass = rb.mprops.effective_mass();
689            let rb_inertia = rb.mprops.effective_angular_inertia().into_matrix();
690            let body_jacobian = &self.body_jacobians[i];
691
692            // NOTE: the mass matrix index reordering operates on the assumption that the assembly
693            //       ids are traversed in order. This assert is here to ensure the assumption always
694            //       hold.
695            assert_eq!(
696                curr_assembly_id, link.assembly_id,
697                "Internal error: contiguity assumption on assembly_id does not hold."
698            );
699            curr_assembly_id += link.joint.ndofs();
700
701            if link.joint.kinematic {
702                for k in link.assembly_id..link.assembly_id + link.joint.ndofs() {
703                    self.augmented_mass_indices.remove(k);
704                }
705            } else {
706                for k in link.assembly_id..link.assembly_id + link.joint.ndofs() {
707                    self.augmented_mass_indices.keep(k);
708                }
709            }
710
711            #[allow(unused_mut)] // mut is needed for 3D but not for 2D.
712            let mut augmented_inertia = rb_inertia;
713
714            #[cfg(feature = "dim3")]
715            {
716                // Derivative of gyroscopic forces.
717                let gyroscopic_matrix = rb.vels.angvel.gcross_matrix() * rb_inertia
718                    - (rb_inertia * rb.vels.angvel).gcross_matrix();
719
720                augmented_inertia += gyroscopic_matrix * dt;
721            }
722
723            // TODO: optimize that (knowing the structure of the augmented inertia matrix).
724            // TODO: this could be better optimized in 2D.
725            #[allow(clippy::useless_conversion)] // Needed in 3D, no-op in 2D
726            let rb_mass_matrix_wo_gyro = concat_rb_mass_matrix(rb_mass, rb_inertia.into());
727            #[allow(clippy::useless_conversion)] // Needed in 3D, no-op in 2D
728            let rb_mass_matrix = concat_rb_mass_matrix(rb_mass, augmented_inertia.into());
729            self.augmented_mass
730                .quadform(1.0, &rb_mass_matrix_wo_gyro, body_jacobian, 1.0);
731            self.acc_augmented_mass
732                .quadform(1.0, &rb_mass_matrix, body_jacobian, 1.0);
733
734            /*
735             *
736             * Coriolis matrix.
737             *
738             */
739            let rb_j = &self.body_jacobians[i];
740            let rb_j_w = rb_j.fixed_rows::<ANG_DIM>(DIM);
741
742            let ndofs = link.joint.ndofs();
743
744            if i != 0 {
745                let parent_id = link.parent_internal_id;
746                let parent_link = &self.links[parent_id];
747                let parent_rb = &bodies[parent_link.rigid_body];
748                let parent_j = &self.body_jacobians[parent_id];
749                let parent_j_w = parent_j.fixed_rows::<ANG_DIM>(DIM);
750                let parent_w = SimdAngVector::<Real>::from(parent_rb.vels.angvel).gcross_matrix();
751
752                let (coriolis_v, parent_coriolis_v) = self.coriolis_v.index_mut2(i, parent_id);
753                let (coriolis_w, parent_coriolis_w) = self.coriolis_w.index_mut2(i, parent_id);
754
755                coriolis_v.copy_from(parent_coriolis_v);
756                coriolis_w.copy_from(parent_coriolis_w);
757
758                // [c1 - c0].gcross() * (JDot + JDot/u * qdot)"
759                let shift_cross_tr = vect_to_na(link.shift02).gcross_matrix_tr();
760                coriolis_v.gemm(1.0, &shift_cross_tr, parent_coriolis_w, 1.0);
761
762                // JDot (but the 2.0 originates from the sum of two identical terms in JDot and JDot/u * gdot)
763                let dvel_cross = vect_to_na(
764                    rb.vels.angvel.gcross(link.shift02) + 2.0 * link.joint_velocity.linvel,
765                )
766                .gcross_matrix_tr();
767                coriolis_v.gemm(1.0, &dvel_cross, &parent_j_w, 1.0);
768
769                // JDot/u * qdot
770                coriolis_v.gemm(
771                    1.0,
772                    &vect_to_na(link.joint_velocity.linvel).gcross_matrix_tr(),
773                    &parent_j_w,
774                    1.0,
775                );
776                coriolis_v.gemm(1.0, &(parent_w * shift_cross_tr), &parent_j_w, 1.0);
777
778                #[cfg(feature = "dim3")]
779                {
780                    let vel_wrt_joint_w = vect_to_na(link.joint_velocity.angvel).gcross_matrix();
781                    coriolis_w.gemm(-1.0, &vel_wrt_joint_w, &parent_j_w, 1.0);
782                }
783
784                // JDot (but the 2.0 originates from the sum of two identical terms in JDot and JDot/u * gdot)
785                if !link.joint.kinematic {
786                    let mut coriolis_v_part = coriolis_v.columns_mut(link.assembly_id, ndofs);
787
788                    let mut tmp1 = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
789                    let mut rb_joint_j = tmp1.columns_mut(0, ndofs);
790                    link.joint.jacobian(
791                        &(parent_link.local_to_world.rotation
792                            * link.joint.data.local_frame1.rotation),
793                        &mut rb_joint_j,
794                    );
795
796                    let rb_joint_j_v = rb_joint_j.fixed_rows::<DIM>(0);
797                    coriolis_v_part.gemm(2.0, &parent_w, &rb_joint_j_v, 1.0);
798
799                    #[cfg(feature = "dim3")]
800                    {
801                        let rb_joint_j_w = rb_joint_j.fixed_rows::<ANG_DIM>(DIM);
802                        let mut coriolis_w_part = coriolis_w.columns_mut(link.assembly_id, ndofs);
803                        coriolis_w_part.gemm(1.0, &parent_w, &rb_joint_j_w, 1.0);
804                    }
805                }
806            } else {
807                self.coriolis_v[i].fill(0.0);
808                self.coriolis_w[i].fill(0.0);
809            }
810
811            let coriolis_v = &mut self.coriolis_v[i];
812            let coriolis_w = &mut self.coriolis_w[i];
813
814            {
815                // [c3 - c2].gcross() * (JDot + JDot/u * qdot)
816                let shift_cross_tr = vect_to_na(link.shift23).gcross_matrix_tr();
817                coriolis_v.gemm(1.0, &shift_cross_tr, coriolis_w, 1.0);
818
819                // JDot
820                let dvel_cross = vect_to_na(rb.vels.angvel.gcross(link.shift23)).gcross_matrix_tr();
821                coriolis_v.gemm(1.0, &dvel_cross, &rb_j_w, 1.0);
822
823                // JDot/u * qdot
824                coriolis_v.gemm(
825                    1.0,
826                    &(SimdAngVector::<Real>::from(rb.vels.angvel).gcross_matrix() * shift_cross_tr),
827                    &rb_j_w,
828                    1.0,
829                );
830            }
831
832            let coriolis_v = &mut self.coriolis_v[i];
833            let coriolis_w = &mut self.coriolis_w[i];
834
835            /*
836             * Meld with the mass matrix.
837             */
838            {
839                let mut i_coriolis_dt_v = self.i_coriolis_dt.fixed_rows_mut::<DIM>(0);
840                i_coriolis_dt_v.copy_from(coriolis_v);
841                let rb_mass_dt = vect_to_na(rb_mass * dt);
842                i_coriolis_dt_v
843                    .column_iter_mut()
844                    .for_each(|mut c| c.component_mul_assign(&rb_mass_dt));
845            }
846
847            #[cfg(feature = "dim2")]
848            {
849                let mut i_coriolis_dt_w = self.i_coriolis_dt.fixed_rows_mut::<ANG_DIM>(DIM);
850                // NOTE: this is just an axpy, but on row columns.
851                i_coriolis_dt_w.zip_apply(coriolis_w, |o, x| *o = x * dt * rb_inertia);
852            }
853            #[cfg(feature = "dim3")]
854            {
855                let mut i_coriolis_dt_w = self.i_coriolis_dt.fixed_rows_mut::<ANG_DIM>(DIM);
856                i_coriolis_dt_w.gemm(dt, &mat_to_na(rb_inertia), coriolis_w, 0.0);
857            }
858
859            self.acc_augmented_mass
860                .gemm_tr(1.0, rb_j, &self.i_coriolis_dt, 1.0);
861        }
862
863        /*
864         * Damping and armature.
865         *
866         * Damping is a velocity-proportional force made implicit, so it adds
867         * `dt · d` to the mass-matrix diagonal. Armature is a "reflected
868         * rotor inertia" (additional inertia to account for the joint’s
869         * mass and geometry itself): it adds to the diagonal directly.
870         */
871        for i in 0..self.ndofs {
872            let diag = self.damping[i] * dt + self.armature[i];
873            self.acc_augmented_mass[(i, i)] += diag;
874            self.augmented_mass[(i, i)] += diag;
875        }
876
877        // Implicit joint springs: `dt²·k` on the mass-matrix diagonal (force term in `update_acceleration`)
878        // keeps a stiff spring on a low-inertia link stable where an explicit position motor injects energy.
879        // The spring lives on the link's `MultibodyJoint` so it travels with the link through topology changes.
880        let dt2 = dt * dt;
881        for li in 0..self.links.len() {
882            let mut idx = self.links[li].assembly_id;
883            let locked = self.links[li].joint.data.locked_axes.bits();
884            for a in 0..SPATIAL_DIM {
885                if (locked >> a) & 1 == 0 {
886                    let k = self.links[li].joint.spring_stiffness[a];
887                    if k != 0.0 {
888                        let d = k * dt2;
889                        self.acc_augmented_mass[(idx, idx)] += d;
890                        self.augmented_mass[(idx, idx)] += d;
891                    }
892                    idx += 1;
893                }
894            }
895        }
896
897        let effective_dim = self
898            .augmented_mass_indices
899            .dim_after_removal(self.acc_augmented_mass.nrows());
900
901        // PERF: since we clone the matrix anyway for LU, should be directly output
902        //       a new matrix instead of applying permutations?
903        self.augmented_mass_indices
904            .rearrange_columns(&mut self.acc_augmented_mass, true);
905        self.augmented_mass_indices
906            .rearrange_columns(&mut self.augmented_mass, true);
907
908        self.augmented_mass_indices
909            .rearrange_rows(&mut self.acc_augmented_mass, true);
910        self.augmented_mass_indices
911            .rearrange_rows(&mut self.augmented_mass, true);
912
913        // TODO: avoid allocation inside LU at each timestep.
914        self.acc_inv_augmented_mass = LU::new(
915            self.acc_augmented_mass
916                .view((0, 0), (effective_dim, effective_dim))
917                .into_owned(),
918        );
919        self.inv_augmented_mass = LU::new(
920            self.augmented_mass
921                .view((0, 0), (effective_dim, effective_dim))
922                .into_owned(),
923        );
924    }
925
926    /// Per-DoF inverse joint-space inertia `diag(M⁻¹)` at the current configuration, where `M`
927    /// includes armature but excludes joint damping and springs — MuJoCo's `dof_invweight0`: the
928    /// apparent inverse inertia at each DoF with all other DoFs free, including the articulated
929    /// coupling. Re-runs forward kinematics and reassembles the mass matrix, so intended for
930    /// occasional use (e.g. sizing `<joint springdamper>` springs at load time), not every step.
931    pub fn dof_inverse_inertia(&mut self, bodies: &RigidBodySet) -> DVector {
932        // Resolve the root joint type (a fixed base may still be a 6-DoF free root pre-collapse)
933        // so `ndofs` is final, then assemble `M`. `dt = 0` drops the `dt·damping` and
934        // `dt²·stiffness` diagonal terms, leaving exactly `M + armature`.
935        self.forward_kinematics(bodies, false);
936        self.update_mass_matrix(0.0, bodies);
937
938        let n = self.ndofs;
939        let mut out = DVector::zeros(n);
940        if n == 0 {
941            return out;
942        }
943        // `(M⁻¹)[i, i]` for each DoF: solve `M x = e_i` and read `x[i]`. The `inv_augmented_mass`
944        // factorization lives in the kinematic-reduced ordering, so route the unit vector through
945        // the same rearrangement the solver uses.
946        let mut e = DVector::zeros(n);
947        for i in 0..n {
948            e.fill(0.0);
949            e[i] = 1.0;
950            self.augmented_mass_indices
951                .with_rearranged_rows_mut(&mut e, |b| {
952                    self.inv_augmented_mass.solve_mut(b);
953                });
954            out[i] = e[i];
955        }
956        out
957    }
958
959    /// Adds a holonomic coupling between two of this multibody's generalized
960    /// coordinates (`q2 = coeff·q1 + offset`), enforced as a velocity-level
961    /// equality constraint each step. See [`MultibodyDofCoupling`].
962    pub fn add_dof_coupling(&mut self, coupling: MultibodyDofCoupling) {
963        self.couplings.push(coupling);
964    }
965
966    /// The DoF couplings declared on this multibody.
967    pub fn couplings(&self) -> &[MultibodyDofCoupling] {
968        &self.couplings
969    }
970
971    /// Number of coupling constraints "owned" by `owner_link` — couplings whose first joint
972    /// (`link1`) is that link. Each coupling is generated once, by `link1` (which always has a
973    /// free DoF and so is an active link in the solver island, unlike a possibly-fixed root).
974    pub(crate) fn num_couplings_owned_by(&self, owner_link: usize) -> usize {
975        self.couplings
976            .iter()
977            .filter(|c| c.link1 == owner_link)
978            .count()
979    }
980
981    /// Generates the velocity constraints for the DoF couplings owned by `owner_link` into
982    /// `out[..]`: each coupling `q2 = coeff·q1 + offset` is one bilateral constraint with jacobian
983    /// `J = e_{q2} − coeff·e_{q1}` and a rhs pulling the position drift back to zero.
984    pub(crate) fn coupling_velocity_constraints(
985        &self,
986        owner_link: usize,
987        params: &IntegrationParameters,
988        mut j_id: usize,
989        jacobians: &mut DVector,
990        out: &mut [GenericJointConstraint],
991    ) -> usize {
992        let ndofs = self.ndofs;
993        let erp_inv_dt = SpringCoefficients::<Real>::joint_defaults().erp_inv_dt(params.dt);
994
995        let mut i = 0;
996        for c in self.couplings.iter().filter(|c| c.link1 == owner_link) {
997            let g1 = self.links[c.link1].assembly_id + c.dof1;
998            let g2 = self.links[c.link2].assembly_id + c.dof2;
999            let q1 = self.links[c.link1].joint().coords()[c.axis1];
1000            let q2 = self.links[c.link2].joint().coords()[c.axis2];
1001
1002            // Jacobian J = e_{g2} − coeff·e_{g1}, then WJ = M⁻¹ J.
1003            jacobians.rows_mut(j_id, ndofs * 2).fill(0.0);
1004            jacobians[j_id + g2] += 1.0;
1005            jacobians[j_id + g1] -= c.coeff;
1006            for k in 0..ndofs {
1007                jacobians[j_id + ndofs + k] = jacobians[j_id + k];
1008            }
1009            self.inv_augmented_mass
1010                .solve_mut(&mut jacobians.rows_mut(j_id + ndofs, ndofs));
1011
1012            // lhs = Jᵀ M⁻¹ J = Σ J[k]·WJ[k]; only g1/g2 entries of J are nonzero.
1013            let lhs = jacobians[j_id + ndofs + g2] - c.coeff * jacobians[j_id + ndofs + g1];
1014
1015            let drift = q2 - c.coeff * q1 - c.offset;
1016
1017            out[i] = GenericJointConstraint {
1018                is_rigid_body1: false,
1019                solver_vel1: u32::MAX,
1020                ndofs1: 0,
1021                j_id1: 0,
1022                is_rigid_body2: false,
1023                solver_vel2: self.solver_id,
1024                ndofs2: ndofs,
1025                j_id2: j_id,
1026                joint_id: usize::MAX, // internal: no impulse writeback.
1027                impulse: 0.0,
1028                impulse_bounds: [-Real::MAX, Real::MAX],
1029                inv_lhs: crate::utils::inv(lhs),
1030                rhs: drift * erp_inv_dt,
1031                rhs_wo_bias: 0.0,
1032                cfm_coeff: 0.0,
1033                cfm_gain: 0.0,
1034                writeback_id: WritebackId::Dof(0),
1035            };
1036
1037            j_id += 2 * ndofs;
1038            i += 1;
1039        }
1040
1041        i
1042    }
1043
1044    /// The generalized velocity at the multibody_joint of the given link.
1045    #[inline]
1046    pub fn joint_velocity(&self, link: &MultibodyLink) -> DVectorView<'_, Real> {
1047        let ndofs = link.joint().ndofs();
1048        DVectorView::from_slice(
1049            &self.velocities.as_slice()[link.assembly_id..link.assembly_id + ndofs],
1050            ndofs,
1051        )
1052    }
1053
1054    /// The generalized accelerations of this multibodies.
1055    #[inline]
1056    pub fn generalized_acceleration(&self) -> DVectorView<'_, Real> {
1057        self.accelerations.rows(0, self.ndofs)
1058    }
1059
1060    /// The generalized velocities of this multibodies.
1061    #[inline]
1062    pub fn generalized_velocity(&self) -> DVectorView<'_, Real> {
1063        self.velocities.rows(0, self.ndofs)
1064    }
1065
1066    /// The body jacobian for link `link_id` calculated by the last call to [`Multibody::forward_kinematics`].
1067    #[inline]
1068    pub fn body_jacobian(&self, link_id: usize) -> &Jacobian<Real> {
1069        &self.body_jacobians[link_id]
1070    }
1071
1072    /// The mutable generalized velocities of this multibodies.
1073    #[inline]
1074    pub fn generalized_velocity_mut(&mut self) -> DVectorViewMut<'_, Real> {
1075        self.velocities.rows_mut(0, self.ndofs)
1076    }
1077
1078    #[inline]
1079    pub(crate) fn integrate(&mut self, dt: Real) {
1080        for rb in self.links.iter_mut() {
1081            rb.joint
1082                .integrate(dt, &self.velocities.as_slice()[rb.assembly_id..])
1083        }
1084    }
1085
1086    /// Apply displacements, in generalized coordinates, to this multibody.
1087    ///
1088    /// Note this does **not** updates the link poses, only their generalized coordinates.
1089    /// To update the link poses and associated rigid-bodies, call [`Self::forward_kinematics`].
1090    pub fn apply_displacements(&mut self, disp: &[Real]) {
1091        for link in self.links.iter_mut() {
1092            link.joint.apply_displacement(&disp[link.assembly_id..])
1093        }
1094    }
1095
1096    pub(crate) fn update_root_type(&mut self, bodies: &RigidBodySet, take_body_pose: bool) {
1097        if let Some(rb) = bodies.get(self.links[0].rigid_body) {
1098            if rb.is_dynamic() != self.root_is_dynamic {
1099                let root_pose = if take_body_pose {
1100                    *rb.position()
1101                } else {
1102                    self.links[0].local_to_world
1103                };
1104
1105                if rb.is_dynamic() {
1106                    let free_joint = MultibodyJoint::free(root_pose);
1107                    let prev_root_ndofs = self.links[0].joint().ndofs();
1108                    self.links[0].joint = free_joint;
1109                    self.links[0].assembly_id = 0;
1110                    self.ndofs += SPATIAL_DIM;
1111
1112                    self.velocities = self.velocities.clone().insert_rows(0, SPATIAL_DIM, 0.0);
1113                    self.damping = self.damping.clone().insert_rows(0, SPATIAL_DIM, 0.0);
1114                    self.armature = self.armature.clone().insert_rows(0, SPATIAL_DIM, 0.0);
1115                    self.accelerations =
1116                        self.accelerations.clone().insert_rows(0, SPATIAL_DIM, 0.0);
1117
1118                    for link in &mut self.links[1..] {
1119                        link.assembly_id += SPATIAL_DIM - prev_root_ndofs;
1120                    }
1121                } else {
1122                    assert!(self.velocities.len() >= SPATIAL_DIM);
1123                    assert!(self.damping.len() >= SPATIAL_DIM);
1124                    assert!(self.armature.len() >= SPATIAL_DIM);
1125                    assert!(self.accelerations.len() >= SPATIAL_DIM);
1126
1127                    let fixed_joint = MultibodyJoint::fixed(root_pose);
1128                    let prev_root_ndofs = self.links[0].joint().ndofs();
1129                    self.links[0].joint = fixed_joint;
1130                    self.links[0].assembly_id = 0;
1131                    self.ndofs -= prev_root_ndofs;
1132
1133                    if self.ndofs == 0 {
1134                        self.velocities = DVector::zeros(0);
1135                        self.damping = DVector::zeros(0);
1136                        self.armature = DVector::zeros(0);
1137                        self.accelerations = DVector::zeros(0);
1138                    } else {
1139                        self.velocities =
1140                            self.velocities.index((prev_root_ndofs.., 0)).into_owned();
1141                        self.damping = self.damping.index((prev_root_ndofs.., 0)).into_owned();
1142                        self.armature = self.armature.index((prev_root_ndofs.., 0)).into_owned();
1143                        self.accelerations = self
1144                            .accelerations
1145                            .index((prev_root_ndofs.., 0))
1146                            .into_owned();
1147                    }
1148
1149                    for link in &mut self.links[1..] {
1150                        link.assembly_id -= prev_root_ndofs;
1151                    }
1152                }
1153
1154                self.root_is_dynamic = rb.is_dynamic();
1155            }
1156
1157            // Make sure the positions are properly set to match the rigid-body’s.
1158            if take_body_pose {
1159                if self.links[0].joint.data.locked_axes.is_empty() {
1160                    self.links[0].joint.set_free_pos(*rb.position());
1161                } else {
1162                    self.links[0].joint.data.local_frame1 = *rb.position();
1163                }
1164            }
1165        }
1166    }
1167
1168    /// Update the rigid-body poses based on this multibody joint poses.
1169    ///
1170    /// This is typically called after [`Self::forward_kinematics`] to apply the new joint poses
1171    /// to the rigid-bodies.
1172    pub fn update_rigid_bodies(&self, bodies: &mut RigidBodySet, update_mass_properties: bool) {
1173        self.update_rigid_bodies_internal(bodies, update_mass_properties, false, true)
1174    }
1175
1176    pub(crate) fn update_rigid_bodies_internal(
1177        &self,
1178        bodies: &mut RigidBodySet,
1179        update_mass_properties: bool,
1180        update_next_positions_only: bool,
1181        change_tracking: bool,
1182    ) {
1183        // Handle the children. They all have a parent within this multibody.
1184        for link in self.links.iter() {
1185            let rb = if change_tracking {
1186                bodies.get_mut_internal_with_modification_tracking(link.rigid_body)
1187            } else {
1188                bodies.get_mut_internal(link.rigid_body)
1189            };
1190
1191            if let Some(rb) = rb {
1192                rb.pos.next_position = link.local_to_world;
1193
1194                if !update_next_positions_only {
1195                    rb.pos.position = link.local_to_world;
1196                }
1197
1198                if update_mass_properties {
1199                    rb.mprops
1200                        .update_world_mass_properties(rb.body_type, &link.local_to_world);
1201                }
1202            }
1203        }
1204    }
1205
1206    // TODO: make a version that doesn’t write back to bodies and doesn’t update the jacobians
1207    //       (i.e. just something used by the velocity solver’s small steps).
1208    /// Apply forward-kinematics to this multibody.
1209    ///
1210    /// This will update the [`MultibodyLink`] pose information as wall as the body jacobians.
1211    /// This will also ensure that the multibody has the proper number of degrees of freedom if
1212    /// its root node changed between dynamic and non-dynamic.
1213    ///
1214    /// Note that this does **not** update the poses of the [`RigidBody`] attached to the joints.
1215    /// Run [`Self::update_rigid_bodies`] to trigger that update.
1216    ///
1217    /// This method updates `self` with the result of the forward-kinematics operation.
1218    /// For a non-mutable version running forward kinematics on a single link, see
1219    /// [`Self::forward_kinematics_single_link`].
1220    ///
1221    /// ## Parameters
1222    /// - `bodies`: the set of rigid-bodies.
1223    /// - `read_root_pose_from_rigid_body`: if set to `true`, the root joint (either a fixed joint,
1224    ///   or a free joint) will have its pose set to its associated-rigid-body pose. Set this to `true`
1225    ///   when the root rigid-body pose has been modified and needs to affect the multibody.
1226    pub fn forward_kinematics(
1227        &mut self,
1228        bodies: &RigidBodySet,
1229        read_root_pose_from_rigid_body: bool,
1230    ) {
1231        // Be sure the degrees of freedom match and take the root position if needed.
1232        self.update_root_type(bodies, read_root_pose_from_rigid_body);
1233
1234        // Special case for the root, which has no parent.
1235        {
1236            let link = &mut self.links[0];
1237            link.local_to_parent = link.joint.body_to_parent();
1238            link.local_to_world = link.local_to_parent;
1239        }
1240
1241        // Handle the children. They all have a parent within this multibody.
1242        for i in 1..self.links.len() {
1243            let (link, parent_link) = self.links.get_mut_with_parent(i);
1244
1245            link.local_to_parent = link.joint.body_to_parent();
1246            link.local_to_world = parent_link.local_to_world * link.local_to_parent;
1247
1248            {
1249                let parent_rb = &bodies[parent_link.rigid_body];
1250                let link_rb = &bodies[link.rigid_body];
1251                let c0 = parent_link.local_to_world * parent_rb.mprops.local_mprops.local_com;
1252                let c2 = link.local_to_world * link.joint.data.local_frame2.translation;
1253                let c3 = link.local_to_world * link_rb.mprops.local_mprops.local_com;
1254
1255                link.shift02 = c2 - c0;
1256                link.shift23 = c3 - c2;
1257            }
1258
1259            assert_eq!(
1260                bodies[link.rigid_body].body_type,
1261                RigidBodyType::Dynamic,
1262                "A rigid-body that is not at the root of a multibody must be dynamic."
1263            );
1264        }
1265
1266        /*
1267         * Compute body jacobians.
1268         */
1269        self.update_body_jacobians();
1270    }
1271
1272    /// Computes the ids of all the links between the root and the link identified by `link_id`.
1273    pub fn kinematic_branch(&self, link_id: usize) -> Vec<usize> {
1274        let mut branch = vec![]; // Perf: avoid allocation.
1275        let mut curr_id = Some(link_id);
1276
1277        while let Some(id) = curr_id {
1278            branch.push(id);
1279            curr_id = self.links[id].parent_id();
1280        }
1281
1282        branch.reverse();
1283        branch
1284    }
1285
1286    /// Apply forward-kinematics to compute the position of a single link of this multibody.
1287    ///
1288    /// If `out_jacobian` is `Some`, this will simultaneously compute the new jacobian of this link.
1289    /// If `displacement` is `Some`, the generalized position considered during transform propagation
1290    /// is the sum of the current position of `self` and this `displacement`.
1291    // TODO: this shares a lot of code with `forward_kinematics` and `update_body_jacobians`, except
1292    //       that we are only traversing a single kinematic chain. Could this be refactored?
1293    pub fn forward_kinematics_single_link(
1294        &self,
1295        bodies: &RigidBodySet,
1296        link_id: usize,
1297        displacement: Option<&[Real]>,
1298        out_jacobian: Option<&mut Jacobian<Real>>,
1299    ) -> Pose {
1300        let branch = self.kinematic_branch(link_id);
1301        self.forward_kinematics_single_branch(bodies, &branch, displacement, out_jacobian)
1302    }
1303
1304    /// Apply forward-kinematics to compute the position of a single sorted branch of this multibody.
1305    ///
1306    /// The given `branch` must have the following properties:
1307    /// - It must be sorted, i.e., `branch[i] < branch[i + 1]`.
1308    /// - All the indices must be part of the same kinematic branch.
1309    /// - If a link is `branch[i]`, then `branch[i - 1]` must be its parent.
1310    ///
1311    /// In general, this method shouldn’t be used directly and [`Self::forward_kinematics_single_link`]
1312    /// should be preferred since it computes the branch indices automatically.
1313    ///
1314    /// If you want to calculate the branch indices manually, see [`Self::kinematic_branch`].
1315    ///
1316    /// If `out_jacobian` is `Some`, this will simultaneously compute the new jacobian of this branch.
1317    /// This represents the body jacobian for the last link in the branch.
1318    ///
1319    /// If `displacement` is `Some`, the generalized position considered during transform propagation
1320    /// is the sum of the current position of `self` and this `displacement`.
1321    // TODO: this shares a lot of code with `forward_kinematics` and `update_body_jacobians`, except
1322    //       that we are only traversing a single kinematic chain. Could this be refactored?
1323    #[profiling::function]
1324    pub fn forward_kinematics_single_branch(
1325        &self,
1326        bodies: &RigidBodySet,
1327        branch: &[usize],
1328        displacement: Option<&[Real]>,
1329        mut out_jacobian: Option<&mut Jacobian<Real>>,
1330    ) -> Pose {
1331        if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
1332            if out_jacobian.ncols() != self.ndofs {
1333                *out_jacobian = Jacobian::zeros(self.ndofs);
1334            } else {
1335                out_jacobian.fill(0.0);
1336            }
1337        }
1338
1339        let mut parent_link: Option<MultibodyLink> = None;
1340
1341        for i in branch {
1342            let mut link = self.links[*i];
1343
1344            if let Some(displacement) = displacement {
1345                link.joint
1346                    .apply_displacement(&displacement[link.assembly_id..]);
1347            }
1348
1349            let parent_to_world;
1350
1351            if let Some(parent_link) = parent_link {
1352                link.local_to_parent = link.joint.body_to_parent();
1353                link.local_to_world = parent_link.local_to_world * link.local_to_parent;
1354
1355                {
1356                    let parent_rb = &bodies[parent_link.rigid_body];
1357                    let link_rb = &bodies[link.rigid_body];
1358                    let c0 = parent_link.local_to_world * parent_rb.mprops.local_mprops.local_com;
1359                    let c2 = link.local_to_world
1360                        * Vector::from(link.joint.data.local_frame2.translation);
1361                    let c3 = link.local_to_world * link_rb.mprops.local_mprops.local_com;
1362
1363                    link.shift02 = c2 - c0;
1364                    link.shift23 = c3 - c2;
1365                }
1366
1367                parent_to_world = parent_link.local_to_world;
1368
1369                if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
1370                    let (mut link_j_v, parent_j_w) =
1371                        out_jacobian.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
1372                    let shift_tr = vect_to_na(link.shift02).gcross_matrix_tr();
1373                    link_j_v.gemm(1.0, &shift_tr, &parent_j_w, 1.0);
1374                }
1375            } else {
1376                link.local_to_parent = link.joint.body_to_parent();
1377                link.local_to_world = link.local_to_parent;
1378                parent_to_world = Pose::IDENTITY;
1379            }
1380
1381            if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
1382                let ndofs = link.joint.ndofs();
1383                let mut tmp = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
1384                let mut link_joint_j = tmp.columns_mut(0, ndofs);
1385                let mut link_j_part = out_jacobian.columns_mut(link.assembly_id, ndofs);
1386                link.joint.jacobian(
1387                    &(parent_to_world.rotation * link.joint.data.local_frame1.rotation),
1388                    &mut link_joint_j,
1389                );
1390                link_j_part += link_joint_j;
1391
1392                {
1393                    let (mut link_j_v, link_j_w) =
1394                        out_jacobian.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
1395                    let shift_tr = vect_to_na(link.shift23).gcross_matrix_tr();
1396                    link_j_v.gemm(1.0, &shift_tr, &link_j_w, 1.0);
1397                }
1398            }
1399
1400            parent_link = Some(link);
1401        }
1402
1403        parent_link
1404            .map(|link| link.local_to_world)
1405            .unwrap_or(Pose::IDENTITY)
1406    }
1407
1408    /// The total number of freedoms of this multibody.
1409    #[inline]
1410    pub fn ndofs(&self) -> usize {
1411        self.ndofs
1412    }
1413
1414    pub(crate) fn fill_jacobians(
1415        &self,
1416        link_id: usize,
1417        unit_force: Vector,
1418        unit_torque: AngVector,
1419        j_id: &mut usize,
1420        jacobians: &mut DVector,
1421    ) -> (Real, Real) {
1422        if self.ndofs == 0 {
1423            return (0.0, 0.0);
1424        }
1425
1426        let wj_id = *j_id + self.ndofs;
1427        let force = Force {
1428            linear: unit_force,
1429            #[cfg(feature = "dim2")]
1430            angular: unit_torque,
1431            #[cfg(feature = "dim3")]
1432            angular: unit_torque,
1433        };
1434
1435        let link = &self.links[link_id];
1436        let mut out_j = jacobians.rows_mut(*j_id, self.ndofs);
1437        self.body_jacobians[link.internal_id].tr_mul_to(force.as_vector(), &mut out_j);
1438
1439        // TODO: Optimize with a copy_nonoverlapping?
1440        for i in 0..self.ndofs {
1441            jacobians[wj_id + i] = jacobians[*j_id + i];
1442        }
1443
1444        {
1445            let mut out_invm_j = jacobians.rows_mut(wj_id, self.ndofs);
1446            self.augmented_mass_indices
1447                .with_rearranged_rows_mut(&mut out_invm_j, |out_invm_j| {
1448                    self.inv_augmented_mass.solve_mut(out_invm_j);
1449                });
1450        }
1451
1452        let j = jacobians.rows(*j_id, self.ndofs);
1453        let invm_j = jacobians.rows(wj_id, self.ndofs);
1454        *j_id += self.ndofs * 2;
1455
1456        (j.dot(&invm_j), j.dot(&self.generalized_velocity()))
1457    }
1458
1459    /// Fills `jacobians` with the relative jacobian `J = J2ᵀ·f2 − J1ᵀ·f1` of two links of `self`, then `M⁻¹·J`
1460    /// (e.g. a loop closure). The difference must be explicit: per-link blocks lose the `J1ᵀ·W·J2` effective-mass
1461    /// coupling since both act on the same generalized velocities. Cancellation-vanished rows are zeroed so the solver skips them.
1462    pub(crate) fn fill_relative_jacobians(
1463        &self,
1464        link_id1: usize,
1465        unit_force1: Vector,
1466        unit_torque1: AngVector,
1467        link_id2: usize,
1468        unit_force2: Vector,
1469        unit_torque2: AngVector,
1470        j_id: &mut usize,
1471        jacobians: &mut DVector,
1472    ) {
1473        if self.ndofs == 0 {
1474            return;
1475        }
1476
1477        let wj_id = *j_id + self.ndofs;
1478        let force1 = Force {
1479            linear: unit_force1,
1480            angular: unit_torque1,
1481        };
1482        let force2 = Force {
1483            linear: unit_force2,
1484            angular: unit_torque2,
1485        };
1486
1487        let link1 = &self.links[link_id1];
1488        let link2 = &self.links[link_id2];
1489
1490        {
1491            let jb1 = &self.body_jacobians[link1.internal_id];
1492            let jb2 = &self.body_jacobians[link2.internal_id];
1493
1494            // Use the (overwritten below) W·J slot as scratch for J1ᵀ·f1.
1495            let (mut out_j, mut scratch) =
1496                jacobians.rows_range_pair_mut(*j_id..*j_id + self.ndofs, wj_id..wj_id + self.ndofs);
1497            jb2.tr_mul_to(force2.as_vector(), &mut out_j);
1498            jb1.tr_mul_to(force1.as_vector(), &mut scratch);
1499            out_j.axpy(-1.0, &scratch, 1.0);
1500
1501            // Cancellation guard: the reference scale is the magnitude of the dot-product operands,
1502            // not their results (which may be pure cancellation noise when the direction isn’t
1503            // expressible by the dofs). Rows below ~1000·ε times that scale are noise, not a constraint.
1504            let scale_sq = jb1.norm_squared() * force1.as_vector().norm_squared()
1505                + jb2.norm_squared() * force2.as_vector().norm_squared();
1506            let eps = Real::EPSILON * 1.0e3;
1507            if out_j.norm_squared() <= eps * eps * scale_sq {
1508                out_j.fill(0.0);
1509            }
1510        }
1511
1512        // TODO: Optimize with a copy_nonoverlapping?
1513        for i in 0..self.ndofs {
1514            jacobians[wj_id + i] = jacobians[*j_id + i];
1515        }
1516
1517        {
1518            let mut out_invm_j = jacobians.rows_mut(wj_id, self.ndofs);
1519            self.augmented_mass_indices
1520                .with_rearranged_rows_mut(&mut out_invm_j, |out_invm_j| {
1521                    self.inv_augmented_mass.solve_mut(out_invm_j);
1522                });
1523        }
1524
1525        *j_id += self.ndofs * 2;
1526    }
1527}
1528
1529#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1530#[derive(Clone, Debug)]
1531struct IndexSequence {
1532    first_to_remove: u32,
1533    index_map: Vec<usize>,
1534}
1535
1536impl IndexSequence {
1537    const NONE: u32 = u32::MAX;
1538
1539    fn new() -> Self {
1540        Self {
1541            first_to_remove: Self::NONE,
1542            index_map: vec![],
1543        }
1544    }
1545
1546    /// Index of the first removed dof, assuming there is one.
1547    fn start(&self) -> usize {
1548        self.first_to_remove as usize
1549    }
1550
1551    fn clear(&mut self) {
1552        self.first_to_remove = Self::NONE;
1553        self.index_map.clear();
1554    }
1555
1556    fn keep(&mut self, i: usize) {
1557        if self.first_to_remove == Self::NONE {
1558            // Nothing got removed yet. No need to register any
1559            // special indexing.
1560            return;
1561        }
1562
1563        self.index_map.push(i);
1564    }
1565
1566    fn remove(&mut self, i: usize) {
1567        if self.first_to_remove == Self::NONE {
1568            self.first_to_remove = i as u32;
1569        }
1570    }
1571
1572    fn dim_after_removal(&self, original_dim: usize) -> usize {
1573        if self.first_to_remove == Self::NONE {
1574            original_dim
1575        } else {
1576            self.start() + self.index_map.len()
1577        }
1578    }
1579
1580    fn rearrange_columns<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>(
1581        &self,
1582        mat: &mut na::Matrix<Real, R, C, S>,
1583        clear_removed: bool,
1584    ) {
1585        if self.first_to_remove == Self::NONE {
1586            // Nothing to rearrange.
1587            return;
1588        }
1589
1590        for (target_shift, source) in self.index_map.iter().enumerate() {
1591            let target = self.start() + target_shift;
1592            let (mut target_col, source_col) = mat.columns_range_pair_mut(target, *source);
1593            target_col.copy_from(&source_col);
1594        }
1595
1596        if clear_removed {
1597            mat.columns_range_mut(self.start() + self.index_map.len()..)
1598                .fill(0.0);
1599        }
1600    }
1601
1602    fn rearrange_rows<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>(
1603        &self,
1604        mat: &mut na::Matrix<Real, R, C, S>,
1605        clear_removed: bool,
1606    ) {
1607        if self.first_to_remove == Self::NONE {
1608            // Nothing to rearrange.
1609            return;
1610        }
1611
1612        for mut col in mat.column_iter_mut() {
1613            for (target_shift, source) in self.index_map.iter().enumerate() {
1614                let target = self.start() + target_shift;
1615                col[target] = col[*source];
1616            }
1617
1618            if clear_removed {
1619                col.rows_range_mut(self.start() + self.index_map.len()..)
1620                    .fill(0.0);
1621            }
1622        }
1623    }
1624
1625    fn inv_rearrange_rows<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>(
1626        &self,
1627        mat: &mut na::Matrix<Real, R, C, S>,
1628    ) {
1629        if self.first_to_remove == Self::NONE {
1630            // Nothing to rearrange.
1631            return;
1632        }
1633
1634        for mut col in mat.column_iter_mut() {
1635            for (target_shift, source) in self.index_map.iter().enumerate().rev() {
1636                let target = self.start() + target_shift;
1637                col[*source] = col[target];
1638                col[target] = 0.0;
1639            }
1640        }
1641    }
1642
1643    fn with_rearranged_rows_mut<C: na::Dim, S: StorageMut<Real, Dyn, C>>(
1644        &self,
1645        mat: &mut na::Matrix<Real, Dyn, C, S>,
1646        mut f: impl FnMut(&mut na::MatrixViewMut<Real, Dyn, C, S::RStride, S::CStride>),
1647    ) {
1648        self.rearrange_rows(mat, true);
1649        let effective_dim = self.dim_after_removal(mat.nrows());
1650        if effective_dim > 0 {
1651            f(&mut mat.rows_mut(0, effective_dim));
1652        }
1653        self.inv_rearrange_rows(mat);
1654    }
1655}
1656
1657#[cfg(test)]
1658mod test {
1659    use super::IndexSequence;
1660    use crate::alloc_prelude::*;
1661    use crate::dynamics::{ImpulseJointSet, IslandManager};
1662    #[cfg(feature = "dim3")]
1663    use crate::math::Vector;
1664    use crate::math::{Real, SPATIAL_DIM};
1665    use crate::prelude::{
1666        ColliderSet, MultibodyJointHandle, MultibodyJointSet, RevoluteJoint, RigidBodyBuilder,
1667        RigidBodySet,
1668    };
1669    use na::{DVector, RowDVector};
1670
1671    #[test]
1672    fn test_multibody_append() {
1673        let mut bodies = RigidBodySet::new();
1674        let mut joints = MultibodyJointSet::new();
1675
1676        let a = bodies.insert(RigidBodyBuilder::dynamic());
1677        let b = bodies.insert(RigidBodyBuilder::dynamic());
1678        let c = bodies.insert(RigidBodyBuilder::dynamic());
1679        let d = bodies.insert(RigidBodyBuilder::dynamic());
1680
1681        #[cfg(feature = "dim2")]
1682        let joint = RevoluteJoint::new();
1683        #[cfg(feature = "dim3")]
1684        let joint = RevoluteJoint::new(Vector::X);
1685
1686        let mb_handle = joints.insert(a, b, joint, true).unwrap();
1687        joints.insert(c, d, joint, true).unwrap();
1688        joints.insert(b, c, joint, true).unwrap();
1689
1690        assert_eq!(joints.get(mb_handle).unwrap().0.ndofs, SPATIAL_DIM + 3);
1691    }
1692
1693    #[test]
1694    fn test_multibody_insert() {
1695        let mut rnd = oorandom::Rand32::new(1234);
1696
1697        for k in 0..10 {
1698            let mut bodies = RigidBodySet::new();
1699            let mut multibody_joints = MultibodyJointSet::new();
1700
1701            let num_links = 100;
1702            let mut handles = vec![];
1703
1704            for _ in 0..num_links {
1705                handles.push(bodies.insert(RigidBodyBuilder::dynamic()));
1706            }
1707
1708            let mut insertion_id: Vec<_> = (0..num_links - 1).collect();
1709
1710            #[cfg(feature = "dim2")]
1711            let joint = RevoluteJoint::new();
1712            #[cfg(feature = "dim3")]
1713            let joint = RevoluteJoint::new(Vector::X);
1714
1715            match k {
1716                0 => {} // Remove in insertion order.
1717                1 => {
1718                    // Remove from leaf to root.
1719                    insertion_id.reverse();
1720                }
1721                _ => {
1722                    // Shuffle the vector a bit.
1723                    // (This test checks multiple shuffle arrangements due to k > 2).
1724                    for l in 0..num_links - 1 {
1725                        insertion_id.swap(l, rnd.rand_range(0..num_links as u32 - 1) as usize);
1726                    }
1727                }
1728            }
1729
1730            let mut mb_handle = MultibodyJointHandle::invalid();
1731            for i in insertion_id {
1732                mb_handle = multibody_joints
1733                    .insert(handles[i], handles[i + 1], joint, true)
1734                    .unwrap();
1735            }
1736
1737            assert_eq!(
1738                multibody_joints.get(mb_handle).unwrap().0.ndofs,
1739                SPATIAL_DIM + num_links - 1
1740            );
1741        }
1742    }
1743
1744    #[test]
1745    fn test_multibody_remove() {
1746        let mut rnd = oorandom::Rand32::new(1234);
1747
1748        for k in 0..10 {
1749            let mut bodies = RigidBodySet::new();
1750            let mut multibody_joints = MultibodyJointSet::new();
1751            let mut colliders = ColliderSet::new();
1752            let mut impulse_joints = ImpulseJointSet::new();
1753            let mut islands = IslandManager::new();
1754
1755            let num_links = 100;
1756            let mut handles = vec![];
1757
1758            for _ in 0..num_links {
1759                handles.push(bodies.insert(RigidBodyBuilder::dynamic()));
1760            }
1761
1762            #[cfg(feature = "dim2")]
1763            let joint = RevoluteJoint::new();
1764            #[cfg(feature = "dim3")]
1765            let joint = RevoluteJoint::new(Vector::X);
1766
1767            for i in 0..num_links - 1 {
1768                multibody_joints
1769                    .insert(handles[i], handles[i + 1], joint, true)
1770                    .unwrap();
1771            }
1772
1773            match k {
1774                0 => {} // Remove in insertion order.
1775                1 => {
1776                    // Remove from leaf to root.
1777                    handles.reverse();
1778                }
1779                _ => {
1780                    // Shuffle the vector a bit.
1781                    // (This test checks multiple shuffle arrangements due to k > 2).
1782                    for l in 0..num_links {
1783                        handles.swap(l, rnd.rand_range(0..num_links as u32) as usize);
1784                    }
1785                }
1786            }
1787
1788            for handle in handles {
1789                bodies.remove(
1790                    handle,
1791                    &mut islands,
1792                    &mut colliders,
1793                    &mut impulse_joints,
1794                    &mut multibody_joints,
1795                    true,
1796                );
1797            }
1798        }
1799    }
1800
1801    fn test_sequence() -> IndexSequence {
1802        let mut seq = IndexSequence::new();
1803        seq.remove(2);
1804        seq.remove(3);
1805        seq.remove(4);
1806        seq.keep(5);
1807        seq.keep(6);
1808        seq.remove(7);
1809        seq.keep(8);
1810        seq
1811    }
1812
1813    #[test]
1814    fn index_sequence_rearrange_columns() {
1815        let seq = test_sequence();
1816        let mut vec = RowDVector::from_fn(10, |_, c| c as Real);
1817        seq.rearrange_columns(&mut vec, true);
1818        assert_eq!(
1819            vec,
1820            RowDVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0])
1821        );
1822    }
1823
1824    #[test]
1825    fn index_sequence_rearrange_rows() {
1826        let seq = test_sequence();
1827        let mut vec = DVector::from_fn(10, |r, _| r as Real);
1828        seq.rearrange_rows(&mut vec, true);
1829        assert_eq!(
1830            vec,
1831            DVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0])
1832        );
1833        seq.inv_rearrange_rows(&mut vec);
1834        assert_eq!(
1835            vec,
1836            DVector::from(vec![0.0, 1.0, 0.0, 0.0, 0.0, 5.0, 6.0, 0.0, 8.0, 0.0])
1837        );
1838    }
1839
1840    #[test]
1841    fn index_sequence_with_rearranged_rows_mut() {
1842        let seq = test_sequence();
1843        let mut vec = DVector::from_fn(10, |r, _| r as Real);
1844        seq.with_rearranged_rows_mut(&mut vec, |v| {
1845            assert_eq!(v.len(), 5);
1846            assert_eq!(*v, DVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0]));
1847            *v *= 10.0;
1848        });
1849        assert_eq!(
1850            vec,
1851            DVector::from(vec![0.0, 10.0, 0.0, 0.0, 0.0, 50.0, 60.0, 0.0, 80.0, 0.0])
1852        );
1853    }
1854}