Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Custom Random Walk

Click to focus. When focused, press m to open the options menu. Refresh the page to restart the simulation.

Overview

hoomd-rs allows you to customize your simulation model at all levels. This tutorial you how to implement a custom boundary condition and a custom trial move. It sets a closed boundary condition that confines bodies inside a circle, and applies trial moves that take discrete steps left, right, down, or up.

  • Objective: Demonstrate the customization of a MC simulation.
  • File: hoomd-rs/examples/mc-tutorial/custom-random-walk.rs
  • Run (interactively):
    cargo run --release --features "bevy" --example custom-random-walk
    
  • Run (in batch mode):
    cargo run --release --example custom-random-walk
    

Use Declarations

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

hoomd-rs uses rand crate to generate pseudorandom numbers.

Custom Boundary Condition

The Random Walk tutorial used the default open boundary condition. This tutorial shows how you can create a custom closed boundary condition.

Define the Circle Type

First, define a type that describes the boundary. In this case, the boundary is a Circle that has a radius:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Implement IsPointInside

Then, implement the IsPointInside trait for Circle. The only required method is is_point_inside() which should return true for points inside the boundary and false for points outside the boundary:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

This implementation of IsPointInside is only for the Cartesian<2> vector type (V in the IsPointInside definition). You can read The Rust Programming Language to learn more about generic types and traits.

Tip

Many shapes in hoomd_geometry implement IsPointInside and can be used for closed boundary conditions. This tutorial creates a new Circle type to teach you about customization, but hoomd_geometry::shape::Circle would work just as well.

Custom Trial Move

The Random Walk tutorial moved points by a random distance (up to a maximum) and in a random direction. Look up “random walk” in a text book and you find will a model that makes random hops on a square lattice. You can implement that in hoomd-rs with a custom trial move.

Define the Discrete Type

Similar to the custom boundary, you need to implement the trait LocalTrial for a type. Here is the type:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Discrete trials always take one step in one direction. Therefore, the Discrete struct needs no fields.

Implement LocalTrial

LocalTrial has one method, propose():

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

propose() takes in the properties (body_properties) of a body in the current microstate of the system. It returns the trial body properties randomly generated using the given rng. In this tutorial, the only body property is position.

Enumerate Possible Steps

First, place the possible moves (down, up, left, right) in an array:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Randomly Select a Step

Then choose one of the steps randomly and add it to the body’s position:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

steps.choose(rng) chooses one of the elements of the array at random (with uniform probability). Arrays in Rust do not have the choose() method by default, it is provided by the IndexedRandom trait in the rand crate. choose() will return None when the input array is empty. That can never happen in this code, so you can safely unpack the option’s value with expect().

Important

Local trial moves MUST satisfy local detailed balance, as defined in Manousiouthakis & Deem.

The Simulation Model

The Random Walk tutorial used local variables in the main() function to store all the elements of the simulation model. You can certainly continue that practice in your applications. As you build more complex codes, however, you will need to move those elements to a struct so that the whole simulation model can be accessed in different modules.

The custom random walk model consists of the microstate, the Hamiltonian, the translation trial moves, and the temperature set point (in units of energy ):

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Notice that this definition explicitly names the generic types of each of these fields. The Random Walk tutorial did not do this because the Rust compiler automatically determined which generic types were used. Structs can be made generic as well, but the details are beyond the scope of this tutorial. Consult the The Rust Programming Language to learn more.

Construct the Simulation Model

The new() method that constructs the CustomRandomWalk simulation model:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

One or more steps might fail, so return a Result<CustomRandomWalk>.

Parameters

Assign all the model parameters in one code block so that they are easy to modify:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Microstate

Construct the Microstate with the circle boundary condition and place n bodies at the origin:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

The newtype Closed can wrap any type that implements IsPointInside (like the custom Circle) so that it can be used as a boundary condition.

Trial Moves

Apply the custom Discrete trial move to all bodies in the microstate:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Sweep can wrap any type that implements LocalTrial.

Hamiltonian

As in the Random Walk tutorial, set so that bodies do not interact:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Initialize the Struct

Now that all of the elements of the simulation model have been constructed, package them in a CustomRandomWalk struct:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

The struct is wrapped in Ok to indicate that the Result of this function is not an error.

Implement Simulation

The Simulation trait provides a common interface that all simulation models can follow:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Advance the Simulation

The first method that all simulation models must have is an advance() method that moves the model forward one step. The implementation is identical to that in the Random Walk tutorial:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

All the complexity of the customizations is contained in the implementation of IsPointInside for Circle and LocalTrial for Discrete.

Get the Simulation Step

All simulation models must also implement a method that provides the current simulation step:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Implement main()

To run the simulation, construct the CustomRandomWalk simulation model. Then call advance() many times:

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Later tutorials will show how you can write simulation trajectories and log properties of the simulation to files.

Note

This main() function runs in batch mode. There is a different main() (not shown here) used in the interactive example.

Conclusion

This tutorial showed you how to customize the random walk simulation with new boundary conditions and apply your own trial moves to the points in it. Rust compiles your customizations into machine code and can inline them into the main simulation loop. This means that your custom simulations run just as fast as they do when using the built-in types.

Navigate to the top of the page to see the simulation in action. Notice that no points leave the boundary. Try pausing the simulation and advancing one step at a time. You should see that every body moves left, right, down, or up on every step.

You can also run the example in batch mode and then open the generated trajectory.gsd in Ovito or another visualization tool:

cargo run --release --example custom-random-walk

Complete Code

use rand::{Rng, seq::IndexedRandom};
use std::iter;

use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
    Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};

/// Closed circular boundary condition.
struct Circle {
    radius: f64,
}

impl IsPointInside<Cartesian<2>> for Circle {
    fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
        point.distance(&[0.0, 0.0].into()) < self.radius
    }
}

/// Take fixed steps left, right, down, or up.
struct Discrete;

impl LocalTrial<Point<Cartesian<2>>> for Discrete {
    fn propose<R: Rng>(
        &self,
        rng: &mut R,
        body_properties: Point<Cartesian<2>>,
    ) -> Point<Cartesian<2>> {
        let steps = [
            [0.0, -1.0].into(),
            [0.0, 1.0].into(),
            [-1.0, 0.0].into(),
            [1.0, 0.0].into(),
        ];

        let mut trial = body_properties;
        trial.position += *steps
            .choose(rng)
            .expect("steps should have at least 1 element");
        trial
    }
}

// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
    /// Positions of all the bodies in the simulation.
    microstate: Microstate<
        Point<Cartesian<2>>,
        Point<Cartesian<2>>,
        AllPairs<SiteKey>,
        Closed<Circle>,
    >,
    /// How sites interact with other sites and fields.
    hamiltonian: Zero,
    /// Trial moves to apply.
    translate_sweep: Sweep<Discrete>,
    /// Temperature set point.
    macrostate: Isothermal,
}

impl CustomRandomWalk {
    /// Construct a new random walk simulation.
    fn new() -> anyhow::Result<CustomRandomWalk> {
        let n = 1000;
        let radius = 50.0;
        let macrostate = Isothermal { temperature: 1.0 };

        let circle = Circle { radius };

        let microstate = Microstate::builder()
            .boundary(Closed(circle))
            .bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
            .try_build()?;

        let translate_sweep = Sweep(Discrete);

        let hamiltonian = Zero;

        Ok(CustomRandomWalk {
            microstate,
            hamiltonian,
            translate_sweep,
            macrostate,
        })
    }
}

impl Simulation for CustomRandomWalk {
    /// Advance the simulation forward one step.
    fn advance(&mut self) -> anyhow::Result<()> {
        self.translate_sweep.apply(
            &mut self.microstate,
            &self.hamiltonian,
            &self.macrostate,
        );
        self.microstate.increment_step();
        Ok(())
    }

    /// Get the current simulation step.
    fn step(&self) -> u64 {
        self.microstate.step()
    }
}

// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
    let mut simulation = CustomRandomWalk::new()?;
    for _ in 0..1_000_000 {
        simulation.advance()?;
    }

    Ok(())
}

#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;

Development of hoomd-rs is led by the Glotzer Group at the University of Michigan.

Copyright © 2024-2026 The Regents of the University of Michigan.