Skip to main content

hoomd_interaction/
lib.rs

1// Copyright (c) 2024-2026 The Regents of the University of Michigan.
2// Part of hoomd-rs, released under the BSD 3-Clause License.
3
4#![doc(
5    html_favicon_url = "https://raw.githubusercontent.com/glotzerlab/hoomd-rs/7352214172a490cc716492e9724ff42720a0018a/doc/theme/favicon.svg"
6)]
7#![doc(
8    html_logo_url = "https://raw.githubusercontent.com/glotzerlab/hoomd-rs/7352214172a490cc716492e9724ff42720a0018a/doc/theme/favicon.svg"
9)]
10
11//! Particle interactions and physical models that apply to microstates.
12//!
13//! `hoomd-interaction` defines traits that describe site and body interactions
14//! needed to perform Monte Carlo and molecular dynamics simulations.
15//!
16//! # Hamiltonian
17//!
18//! A type that describes a Hamiltonian (or a single term in a multi-part Hamiltonian)
19//! implements one or more of the following traits: [`TotalEnergy`], [`DeltaEnergyOne`],
20//! [`DeltaEnergyInsert`], and [`DeltaEnergyRemove`]. Given a microstate, the
21//! [`total_energy`] method computes the total energy of the Hamiltonian. The various
22//! `delta_energy_*` methods compute the *change* in total energy when updating, inserting,
23//! or removing a body (often needed for Monte Carlo simulation). Total energy
24//! computations *typically* cost $` O(N) `$ while `delta_energy` methods typically cost
25//! $` O(1) `$. These costs may vary based on the specific interaction type and/or the
26//! microstate's spatial data structure.
27//!
28//! As a convenience, most Hamiltonian types also implement [`MaximumInteractionRange`],
29//! so that callers can easily determine the maximum site-site interaction range in a
30//! given model.
31//!
32//! All the Hamiltonian traits can be automatically derived using a `#[derive()]` macro
33//! of the same name. The derived implementation sums over all the fields in the struct.
34//!
35//! [`total_energy`]: TotalEnergy::total_energy
36//!
37//! # Force interaction models
38//!
39//! Molecular dynamics simulations are driven by the forces and torques that act
40//! on the *bodies* in the simulation ([`NetBodyForceAndVirial`], [`NetBodyForceVirialAndTorque`]).
41//! The *body* forces and torques result from forces and torques on the *sites*
42//! [`NetSiteForceAndVirial`], [`NetSiteForceVirialAndTorque`]). When an MD model is Hamiltonian,
43//! the same types that implement [`DeltaEnergyOne`] (and related traits) also
44//! implement [`NetSiteForceAndVirial`] and/or [`NetSiteForceVirialAndTorque`].
45//! Virials are always calculated alongside forces.
46//!
47//! Given a microstate and the index of a site in that microstate,
48//! [`NetSiteForceAndVirial`] and/or [`NetSiteForceVirialAndTorque`] compute the net force and virial
49//! (and torque) acting on that site. Thus, you can use your `hamiltonian`
50//! variable to compute both energy and force properties on the system.
51//! Use `Rigid(hamiltonian)` with MD integration methods. The [`Rigid`]
52//! type implements [`NetBodyForceAndVirial`] and/or [`NetBodyForceVirialAndTorque`] for types
53//! that implement [`NetSiteForceAndVirial`] or [`NetSiteForceVirialAndTorque`] respectively.
54//!
55//! Not all MD models are Hamiltonian, so types may implement [`NetSiteForceAndVirial`]
56//! but but not [`TotalEnergy`]. Similarly, not all Hamiltonian types are
57//! differentiable and may implement [`DeltaEnergyOne`] but not [`NetSiteForceAndVirial`].
58//!
59//! All the force interaction model traits can be automatically derived using a
60//! `#[derive()]` macro of the same name. The derived implementation sums over
61//! all the fields in the struct.
62//!
63//! # Univariate interactions
64//!
65//! Many interaction potentials are a function of one variable, typically the
66//! distance between two sites but sometimes the distance between a site and
67//! surface, or an angle. `hoomd-interaction` implements the most commonly
68//! used univariate potentials, such as [`LennardJones`] in [`univariate`].
69//! These types are not Hamiltonian terms on their own, but can be combined
70//! with other types to create interaction models.
71//!
72//! To implement your own univariate interactions, implement the
73//! [`UnivariateEnergy`] and/or [`UnivariateForce`] traits.
74//!
75//! [`LennardJones`]: univariate::LennardJones
76//! [`UnivariateEnergy`]: univariate::UnivariateEnergy
77//! [`UnivariateForce`]: univariate::UnivariateForce
78//!
79//! # Interactions between sites and external objects
80//!
81//! The [`SiteEnergy`] trait describes a type that computes the contribution
82//! of a single site to the total energy as a function only of that site's
83//! properties along with fixed external parameters. [`SiteForceAndVirial`] and
84//! [`SiteForceVirialAndTorque`] describe the force (and torque) on the site commensurate
85//! with that energy. The [`External`] type implements all the Hamiltonian and force
86//! interaction model traits. It applies the wrapped type's [`SiteEnergy`], [`SiteForceAndVirial`],
87//! and/or [`SiteForceVirialAndTorque`] implementations to all the sites in the microstate.
88//! See [`external`] for a list of built-in [`SiteEnergy`], [`SiteForceAndVirial`] and
89//! [`SiteForceVirialAndTorque`] implementations.
90//!
91//! # Interactions between all pairs of sites
92//!
93//! The [`SitePairEnergy`] trait describes a type that computes the energy
94//! that a pair of sites contributes to the Hamiltonian as a function of
95//! the properties of the two sites. Similarly, [`SitePairForceAndVirial`] and
96//! [`SitePairForceVirialAndTorque`] describe the force (and torque) between
97//! the pair commensurate with that energy. The [`PairwiseCutoff`] type implements
98//! all the Hamiltonian and force interaction model traits. It applies the wrapped
99//! type's [`SitePairEnergy`], [`SitePairForceAndVirial`], and/or [`SitePairForceVirialAndTorque`]
100//! to all pairs of sites that are within the maximum interaction range.
101//!
102//! The [`pairwise`] module provides numerous types that implement
103//! [`SitePairEnergy`], [`SitePairForceAndVirial`], and [`SitePairForceVirialAndTorque`]
104//! including [`Isotropic`] (which wraps any univariate potential), [`HardShape`]
105//! (which wraps a shape from [`hoomd_geometry`], and many others.
106//!
107//! # Zero
108//!
109//! [`Zero`] implements all Hamiltonian and force interaction traits. It
110//! represents $` H=0 `$.
111//!
112//! [`Isotropic`]: pairwise::Isotropic
113//! [`HardShape`]: pairwise::HardShape
114//!
115//! # Complete documentation
116//!
117//! `hoomd-interaction` is is a part of *hoomd-rs*. Read the [complete documentation]
118//! for more information.
119//!
120//! [complete documentation]: https://hoomd-rs.readthedocs.io
121
122pub mod external;
123pub mod pairwise;
124pub mod univariate;
125
126mod external_type;
127mod pairwise_cutoff;
128mod rigid;
129mod zero;
130
131pub use external_type::External;
132
133pub use hoomd_derive::{
134    DeltaEnergyInsert, DeltaEnergyOne, DeltaEnergyRemove, MaximumInteractionRange,
135    NetSiteForceAndVirial, NetSiteForceVirialAndTorque, SitePairEnergy, TotalEnergy,
136};
137use hoomd_microstate::{Body, Microstate};
138use hoomd_vector::{Outer, Wedge};
139pub use pairwise_cutoff::PairwiseCutoff;
140pub use rigid::Rigid;
141pub use zero::Zero;
142
143/// Compute the total energy of a potential applied to the microstate.
144///
145/// The `TotalEnergy` trait describes a type that can compute the energy of a
146/// given microstate. Depending on the type, `total_energy` might compute the
147/// total potential energy of the whole Hamiltonian or a single term, such as
148/// the Lennard-Jones potential energy.
149///
150/// # Example
151///
152/// ```
153/// use hoomd_interaction::{
154///     PairwiseCutoff, SitePairEnergy, TotalEnergy, pairwise::Isotropic,
155///     univariate::LennardJones,
156/// };
157/// use hoomd_microstate::{
158///     Body, Microstate,
159///     property::{Point, Position},
160/// };
161/// use hoomd_vector::{Cartesian, Vector};
162///
163/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
164/// let mut microstate = Microstate::new();
165/// microstate.extend_bodies([
166///     Body::point(Cartesian::from([0.0, 0.0])),
167///     Body::point(Cartesian::from([1.0, 0.0])),
168///     Body::point(Cartesian::from([0.0, 5.0])),
169///     Body::point(Cartesian::from([-1.0, 5.0])),
170/// ])?;
171///
172/// let lennard_jones: LennardJones = LennardJones {
173///     epsilon: 1.5,
174///     sigma: 1.0 / 2.0_f64.powf(1.0 / 6.0),
175/// };
176/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
177///     interaction: lennard_jones,
178///     r_cut: 2.5,
179/// });
180///
181/// let total_energy = pairwise_cutoff.total_energy(&microstate);
182/// assert_eq!(total_energy, -3.0);
183/// # Ok(())
184/// # }
185/// ```
186///
187/// # Derive macro
188///
189/// Use the [`TotalEnergy`](macro@TotalEnergy) derive macro to automatically implement
190/// the `TotalEnergy` trait on a type. The derived implementation sums the result of
191/// `total_energy` over all fields in the struct (in the order in which fields
192/// are named in the struct definition). The sum short circuits and returns
193/// `f64::INFINITY` when any one field returns infinity.
194/// ```
195/// use hoomd_interaction::{
196///     External, PairwiseCutoff, TotalEnergy, external::Linear,
197///     pairwise::Isotropic, univariate::Boxcar,
198/// };
199/// use hoomd_microstate::{Body, Microstate, property::Point};
200/// use hoomd_vector::Cartesian;
201///
202/// #[derive(TotalEnergy)]
203/// struct Hamiltonian {
204///     linear: External<Linear<Cartesian<2>>>,
205///     pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
206/// }
207///
208/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
209/// let mut microstate = Microstate::new();
210/// microstate.extend_bodies([
211///     Body::point(Cartesian::from([0.0, 4.0])),
212///     Body::point(Cartesian::from([1.0, 4.0])),
213/// ])?;
214///
215/// let epsilon = 2.0;
216/// let (left, right) = (0.0, 1.5);
217/// let boxcar = Boxcar {
218///     epsilon,
219///     left,
220///     right,
221/// };
222/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
223///     interaction: boxcar,
224///     r_cut: right,
225/// });
226///
227/// let linear = External(Linear {
228///     alpha: 1.0,
229///     plane_origin: Cartesian::default(),
230///     plane_normal: [0.0, 1.0].try_into()?,
231/// });
232///
233/// let hamiltonian = Hamiltonian {
234///     pairwise_cutoff,
235///     linear,
236/// };
237///
238/// let total_energy = hamiltonian.total_energy(&microstate);
239/// assert_eq!(total_energy, 10.0);
240/// # Ok(())
241/// # }
242/// ```
243pub trait TotalEnergy<M> {
244    /// Compute the energy.
245    #[must_use]
246    fn total_energy(&self, microstate: &M) -> f64;
247
248    /// Compute the difference in energy between two microstates.
249    ///
250    /// Returns $` E_\mathrm{final} - E_\mathrm{initial} `$.
251    #[inline]
252    #[must_use]
253    fn delta_energy_total(&self, initial_microstate: &M, final_microstate: &M) -> f64 {
254        self.total_energy(final_microstate) - self.total_energy(initial_microstate)
255    }
256}
257
258/// Compute the energy contribution of a single site.
259///
260/// The `SiteEnergy` trait describes a type that can compute the energy contribution
261/// of a site to the system's total energy *as a function only of that site's
262/// properties*.
263///
264/// The [`external`] module provides a number of commonly used implementations.
265/// Combine them with [`External`] newtype for use with MC and MD simulations or to
266/// compute system-wide properties.
267///
268/// The generic type names are:
269/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
270///
271/// ## Examples
272///
273/// Implement a custom site energy function:
274///
275/// ```
276/// use hoomd_interaction::{External, SiteEnergy, TotalEnergy};
277/// use hoomd_microstate::{
278///     Body, Microstate,
279///     property::{Point, Position},
280/// };
281/// use hoomd_vector::Cartesian;
282///
283/// struct Custom {
284///     a: f64,
285///     b: f64,
286/// }
287///
288/// impl<S> SiteEnergy<S> for Custom
289/// where
290///     S: Position<Position = Cartesian<2>>,
291/// {
292///     fn site_energy(&self, site_properties: &S) -> f64 {
293///         self.a * (site_properties.position()[0] / self.b).cos()
294///     }
295/// }
296///
297/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
298/// let mut microstate = Microstate::new();
299/// microstate.extend_bodies([
300///     Body::point(Cartesian::from([1.0, 0.0])),
301///     Body::point(Cartesian::from([-1.0, 2.0])),
302/// ])?;
303///
304/// let custom = Custom { a: 1.0, b: 10.0 };
305/// let site_energy = custom.site_energy(&microstate.sites()[0].properties);
306///
307/// let custom = External(custom);
308/// let total_energy = custom.total_energy(&microstate);
309/// # Ok(())
310/// # }
311/// ```
312///
313/// Custom method that checks for overlaps of a disk with a circular boundary.
314///
315/// ```
316/// use hoomd_interaction::{External, SiteEnergy, TotalEnergy};
317/// use hoomd_microstate::{
318///     Body, Microstate,
319///     property::{Point, Position},
320/// };
321/// use hoomd_vector::{Cartesian, Metric};
322///
323/// struct Custom {
324///     r: f64,
325/// }
326///
327/// impl<S> SiteEnergy<S> for Custom
328/// where
329///     S: Position<Position = Cartesian<2>>,
330/// {
331///     fn site_energy(&self, site_properties: &S) -> f64 {
332///         if site_properties.position().distance(&Cartesian::default())
333///             > self.r - 0.5
334///         {
335///             f64::INFINITY
336///         } else {
337///             0.0
338///         }
339///     }
340///
341///     fn is_only_infinite_or_zero() -> bool {
342///         true
343///     }
344/// }
345///
346/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
347/// let mut microstate = Microstate::new();
348/// microstate.extend_bodies([Body::point(Cartesian::from([9.6, 0.0]))])?;
349///
350/// let custom = Custom { r: 10.0 };
351/// let site_energy = custom.site_energy(&microstate.sites()[0].properties);
352/// assert_eq!(site_energy, f64::INFINITY);
353///
354/// let custom = External(custom);
355/// let total_energy = custom.total_energy(&microstate);
356/// assert_eq!(total_energy, f64::INFINITY);
357/// # Ok(())
358/// # }
359/// ```
360pub trait SiteEnergy<S> {
361    /// Evaluate the energy contribution of a single site.
362    #[must_use]
363    fn site_energy(&self, site_properties: &S) -> f64;
364
365    /// Evaluate the energy contribution of a single site *in the initial state*.
366    ///
367    /// Override this method in potentials that have both infinite or zero
368    /// terms and finite terms, such as the sum of a hard site-wall interaction
369    /// plus an attractive well. `site_energy` should compute both terms and
370    /// `site_energy_initial` should compute only the finite terms.
371    ///
372    /// [`External`] calls `site_energy_initial` when evaluating the energy of
373    /// the initial state in a trial move. The infinite interaction term can be
374    /// assumed 0 in the initial state because no site will ever be placed in an
375    /// infinite energy configuration.
376    #[must_use]
377    #[inline]
378    fn site_energy_initial(&self, site_properties: &S) -> f64 {
379        self.site_energy(site_properties)
380    }
381
382    /// Does this potential only ever return infinity or zero?
383    ///
384    /// Override this method and return `true` for e.g. hard site-wall
385    /// interactions that always return infinity or zero and **never** any other
386    /// value. When this method returns `true`, [`External`] skips the initial
387    /// energy computation and assumes it is zero.
388    #[must_use]
389    #[inline]
390    fn is_only_infinite_or_zero() -> bool {
391        false
392    }
393}
394
395/// Compute the energy contribution from a pair of sites.
396///
397/// The `SitePairEnergy` trait describes a type that can compute the energy
398/// contribution from a pair of sites to the system's total energy *as a function
399/// only of those site's properties*.
400///
401/// The [`pairwise`] module provides a number of commonly used implementations,
402/// such as [`Isotropic`], [`Anisotropic`], and [`HardShape`]. Combine any
403/// of them with the [`PairwiseCutoff`] for use with MC and MD simulations or to
404/// compute system-wide properties.
405///
406/// The generic type names are:
407/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
408///
409/// [`Isotropic`]: pairwise::Isotropic
410/// [`Anisotropic`]: pairwise::Anisotropic
411/// [`HardShape`]: pairwise::HardShape
412///
413/// ## Examples
414///
415/// Implement a custom site energy method:
416/// ```
417/// use hoomd_interaction::{
418///     MaximumInteractionRange, PairwiseCutoff, SitePairEnergy, TotalEnergy,
419/// };
420/// use hoomd_microstate::{
421///     Body, Microstate,
422///     property::{Point, Position},
423/// };
424/// use hoomd_vector::{Cartesian, InnerProduct};
425///
426/// #[derive(MaximumInteractionRange)]
427/// struct Custom {
428///     epsilon: f64,
429///     maximum_interaction_range: f64,
430/// }
431///
432/// impl<S> SitePairEnergy<S> for Custom
433/// where
434///     S: Position<Position = Cartesian<2>>,
435/// {
436///     fn site_pair_energy(
437///         &self,
438///         site_properties_i: &S,
439///         site_properties_j: &S,
440///     ) -> f64 {
441///         self.epsilon
442///             * site_properties_i
443///                 .position()
444///                 .dot(&site_properties_j.position())
445///     }
446/// }
447///
448/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
449/// let mut microstate = Microstate::new();
450/// microstate.extend_bodies([
451///     Body::point(Cartesian::from([1.0, 0.0])),
452///     Body::point(Cartesian::from([0.0, 1.0])),
453/// ])?;
454///
455/// let custom = Custom {
456///     epsilon: 1.0,
457///     maximum_interaction_range: 2.5,
458/// };
459/// let site_pair_energy = custom.site_pair_energy(
460///     &microstate.sites()[0].properties,
461///     &microstate.sites()[1].properties,
462/// );
463///
464/// let custom = PairwiseCutoff(custom);
465/// let total_energy = custom.total_energy(&microstate);
466/// # Ok(())
467/// # }
468/// ```
469///
470/// Implement a custom site overlap method:
471/// ```
472/// use hoomd_interaction::{
473///     MaximumInteractionRange, PairwiseCutoff, SitePairEnergy, TotalEnergy,
474/// };
475/// use hoomd_microstate::{
476///     Body, Microstate, Transform,
477///     property::{Point, Position},
478/// };
479/// use hoomd_utility::valid::PositiveReal;
480/// use hoomd_vector::{Cartesian, Metric};
481///
482/// #[derive(Default, Position)]
483/// struct CircleSiteProperties {
484///     position: Cartesian<2>,
485///     radius: PositiveReal,
486/// }
487///
488/// impl Transform<CircleSiteProperties> for Point<Cartesian<2>> {
489///     fn transform(
490///         &self,
491///         site_properties: &CircleSiteProperties,
492///     ) -> CircleSiteProperties {
493///         CircleSiteProperties {
494///             position: self.position + site_properties.position,
495///             ..*site_properties
496///         }
497///     }
498/// }
499///
500/// #[derive(MaximumInteractionRange)]
501/// struct PolydisperseCircleOverlap {
502///     maximum_interaction_range: f64,
503/// }
504///
505/// impl SitePairEnergy<CircleSiteProperties> for PolydisperseCircleOverlap {
506///     fn site_pair_energy(
507///         &self,
508///         a: &CircleSiteProperties,
509///         b: &CircleSiteProperties,
510///     ) -> f64 {
511///         let r = a.position().distance(b.position());
512///
513///         if r < a.radius.get() + b.radius.get() {
514///             f64::INFINITY
515///         } else {
516///             0.0
517///         }
518///     }
519///
520///     fn is_only_infinite_or_zero() -> bool {
521///         true
522///     }
523///
524///     fn site_pair_energy_initial(
525///         &self,
526///         _a: &CircleSiteProperties,
527///         _b: &CircleSiteProperties,
528///     ) -> f64 {
529///         0.0
530///     }
531/// }
532///
533/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
534/// let mut microstate = Microstate::new();
535/// microstate.extend_bodies([
536///     Body {
537///         properties: Point::new(Cartesian::from([0.0, 0.0])),
538///         sites: vec![CircleSiteProperties {
539///             position: Cartesian::from([0.0, 0.0]),
540///             radius: 0.5.try_into()?,
541///         }],
542///     },
543///     Body {
544///         properties: Point::new(Cartesian::from([1.4, 0.0])),
545///         sites: vec![CircleSiteProperties {
546///             position: Cartesian::from([0.0, 0.0]),
547///             radius: 1.0.try_into()?,
548///         }],
549///     },
550/// ])?;
551///
552/// let overlap = PolydisperseCircleOverlap {
553///     maximum_interaction_range: 1.5,
554/// };
555/// let site_pair_energy = overlap.site_pair_energy(
556///     &microstate.sites()[0].properties,
557///     &microstate.sites()[1].properties,
558/// );
559/// assert_eq!(site_pair_energy, f64::INFINITY);
560///
561/// let pairwise_cutoff = PairwiseCutoff(overlap);
562/// let total_energy = pairwise_cutoff.total_energy(&microstate);
563/// assert_eq!(total_energy, f64::INFINITY);
564/// # Ok(())
565/// # }
566/// ```
567///
568/// # Derive macro
569///
570/// Use the [`SitePairEnergy`](macro@SitePairEnergy) derive macro to
571/// automatically implement the `SitePairEnergy` trait on a type.
572/// The implemented `site_pair_energy` sums the result of `site_pair_energy`
573/// over all fields. The implementation returns early when any one field returns
574/// infinity. The implemented `site_pair_energy_initial` behaves similarly.
575/// The derived `is_only_infinite_or_zero` returns true only when all fields
576/// also return true for the same method.
577///
578/// ```
579/// use hoomd_interaction::{
580///     MaximumInteractionRange, SitePairEnergy,
581///     pairwise::{AngularMask, Anisotropic, HardSphere},
582///     univariate::Boxcar,
583/// };
584/// use hoomd_vector::Cartesian;
585///
586/// #[derive(MaximumInteractionRange, SitePairEnergy)]
587/// struct SitePairInteraction {
588///     hard_disk: HardSphere,
589///     angular_mask: Anisotropic<AngularMask<Boxcar, Cartesian<2>>>,
590/// }
591/// ```
592pub trait SitePairEnergy<S> {
593    /// Evaluate the energy contribution from a pair of sites.
594    fn site_pair_energy(&self, site_properties_i: &S, site_properties_j: &S) -> f64;
595
596    /// Evaluate the energy contribution from a pair of sites *in the initial state*.
597    ///
598    /// Override this method in potentials that have both infinite or zero terms
599    /// and finite terms, such as the sum of a hard site-wall interaction plus
600    /// an attractive well. `site_pair_energy` should compute both terms and
601    /// `site_pair_energy_initial` should compute only the finite terms.
602    ///
603    /// [`PairwiseCutoff`] calls `site_pair_energy_initial` when evaluating the
604    /// energy of the initial state in a trial move. The infinite interaction
605    /// term can be assumed 0 in the initial state because no site will ever be
606    /// placed in an infinite energy configuration.
607    #[must_use]
608    #[inline]
609    fn site_pair_energy_initial(&self, site_properties_i: &S, site_properties_j: &S) -> f64 {
610        self.site_pair_energy(site_properties_i, site_properties_j)
611    }
612
613    /// Does this potential only ever return infinity or zero?
614    ///
615    /// Override this method and return `true` for e.g. hard particle
616    /// interactions that always return infinity or zero and **never** any other
617    /// value. When this method returns `true`, [`PairwiseCutoff`] skips the
618    /// initial energy computation and assumes it is zero.
619    #[must_use]
620    #[inline]
621    fn is_only_infinite_or_zero() -> bool {
622        false
623    }
624}
625
626/// Largest distance between two sites where the pairwise interaction may be non-zero.
627///
628/// [`PairwiseCutoff`] uses the provided maximum interaction range to
629/// efficiently compute only the needed interactions. All types that implement
630/// `SitePair*` traits must also implement [`MaximumInteractionRange`].
631///
632/// # Derive macro
633///
634/// Use the [`MaximumInteractionRange`](macro@MaximumInteractionRange) derive macro to
635/// automatically implement the `MaximumInteractionRange` trait on a type.
636///
637/// When the type has a field named `maximum_interaction_range`, the derived implementation
638/// returns it. When there is no such field, the derived implementation takes
639/// the maximum of the `maximum_interaction_range` over all fields in the struct.
640/// The former case is intended for use with custom site pair potentials and
641/// the latter is intended for use with multi-term Hamiltonian types.
642/// ```
643/// use hoomd_interaction::{
644///     External, MaximumInteractionRange, PairwiseCutoff, external::Linear,
645/// };
646/// use hoomd_vector::Cartesian;
647///
648/// #[derive(MaximumInteractionRange)]
649/// struct SitePairInteraction {
650///     // ...
651///     maximum_interaction_range: f64,
652/// }
653///
654/// #[derive(MaximumInteractionRange)]
655/// struct Hamiltonian {
656///     linear: External<Linear<Cartesian<2>>>,
657///     pairwise_cutoff: PairwiseCutoff<SitePairInteraction>,
658/// }
659/// ```
660pub trait MaximumInteractionRange {
661    /// The largest distance between two sites where the pairwise interaction may be non-zero.
662    fn maximum_interaction_range(&self) -> f64;
663}
664
665/// Compute the change in energy as a function of a single modified body.
666///
667/// Some trial moves apply to a single body at a time and use a Hamiltonian that
668/// implements `DeltaEnergyOne` to efficiently compute the change in energy.
669///
670/// The generic type names are:
671/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
672/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
673/// * `X`: The spatial data structure type.
674/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
675///
676/// See the [Implementors](#implementors) section below for examples.
677///
678/// # Derive macro
679///
680/// Use the [`DeltaEnergyOne`](macro@DeltaEnergyOne) derive macro to
681/// automatically implement the `DeltaEnergyOne` trait on a type. The derived
682/// implementation sums the result of `delta_energy_one` over all fields in the
683/// struct (in the order in which fields are named in the struct definition).
684/// The sum short circuits and returns `f64::INFINITY` when any one field
685/// returns infinity.
686/// ```
687/// use hoomd_interaction::{
688///     DeltaEnergyOne, External, PairwiseCutoff, external::Linear,
689///     pairwise::Isotropic, univariate::Boxcar,
690/// };
691/// use hoomd_microstate::{Body, Microstate, property::Point};
692/// use hoomd_vector::Cartesian;
693///
694/// #[derive(DeltaEnergyOne)]
695/// struct Hamiltonian {
696///     linear: External<Linear<Cartesian<2>>>,
697///     pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
698/// }
699///
700/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
701/// let mut microstate = Microstate::new();
702/// microstate.extend_bodies([
703///     Body::point(Cartesian::from([0.0, 0.0])),
704///     Body::point(Cartesian::from([1.0, 0.0])),
705/// ])?;
706///
707/// let epsilon = 2.0;
708/// let (left, right) = (0.0, 1.5);
709/// let boxcar = Boxcar {
710///     epsilon,
711///     left,
712///     right,
713/// };
714/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
715///     interaction: boxcar,
716///     r_cut: right,
717/// });
718///
719/// let linear = External(Linear {
720///     alpha: 10.0,
721///     plane_origin: Cartesian::default(),
722///     plane_normal: [0.0, 1.0].try_into()?,
723/// });
724///
725/// let hamiltonian = Hamiltonian {
726///     pairwise_cutoff,
727///     linear,
728/// };
729///
730/// let delta_energy = hamiltonian.delta_energy_one(
731///     &microstate,
732///     0,
733///     &Body::point([-1.0, 0.0].into()),
734/// );
735/// assert_eq!(delta_energy, -2.0);
736/// # Ok(())
737/// # }
738/// ```
739pub trait DeltaEnergyOne<B, S, X, C> {
740    /// Compute the change in energy.
741    ///
742    /// `initial_microstate` describes the initial configuration and `final_body`
743    /// describes the new body configuration. In the final configuration, the
744    /// body may have changed properties and/or sites. The index `body_index`
745    /// identifies which body in `initial_microstate` is changing.
746    ///
747    /// Returns:
748    /// ```math
749    /// \Delta E = E_\mathrm{final} - E_\mathrm{initial}
750    /// ```
751    #[must_use]
752    fn delta_energy_one(
753        &self,
754        initial_microstate: &Microstate<B, S, X, C>,
755        body_index: usize,
756        final_body: &Body<B, S>,
757    ) -> f64;
758}
759
760/// Compute the change in energy when a single body is inserted.
761///
762/// Some trial moves insert a single body at a time and use a Hamiltonian that
763/// implements `DeltaEnergyInsert` to efficiently compute the change in energy.
764///
765/// The generic type names are:
766/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
767/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
768/// * `X`: The spatial data structure type.
769/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
770///
771/// See the [Implementors](#implementors) section below for examples.
772///
773/// # Derive macro
774///
775/// Use the [`DeltaEnergyInsert`](macro@DeltaEnergyInsert) derive macro to
776/// automatically implement the `DeltaEnergyInsert` trait on a type. The derived
777/// implementation sums the result of `delta_energy_insert` over all fields in the
778/// struct (in the order in which fields are named in the struct definition).
779/// The sum short circuits and returns `f64::INFINITY` when any one field
780/// returns infinity.
781/// ```
782/// use hoomd_interaction::{
783///     DeltaEnergyInsert, External, PairwiseCutoff, external::Linear,
784///     pairwise::Isotropic, univariate::Boxcar,
785/// };
786/// use hoomd_microstate::{Body, Microstate, property::Point};
787/// use hoomd_vector::Cartesian;
788///
789/// #[derive(DeltaEnergyInsert)]
790/// struct Hamiltonian {
791///     linear: External<Linear<Cartesian<2>>>,
792///     pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
793/// }
794///
795/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
796/// let mut microstate = Microstate::new();
797/// microstate.extend_bodies([Body::point(Cartesian::from([0.0, 4.0]))])?;
798///
799/// let epsilon = 2.0;
800/// let (left, right) = (0.0, 1.5);
801/// let boxcar = Boxcar {
802///     epsilon,
803///     left,
804///     right,
805/// };
806/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
807///     interaction: boxcar,
808///     r_cut: right,
809/// });
810///
811/// let linear = External(Linear {
812///     alpha: 1.0,
813///     plane_origin: Cartesian::default(),
814///     plane_normal: [0.0, 1.0].try_into()?,
815/// });
816///
817/// let hamiltonian = Hamiltonian {
818///     pairwise_cutoff,
819///     linear,
820/// };
821///
822/// let new_body = Body::point(Cartesian::from([1.0, 4.0]));
823/// let delta_energy = hamiltonian.delta_energy_insert(&microstate, &new_body);
824/// assert_eq!(delta_energy, 6.0);
825/// # Ok(())
826/// # }
827/// ```
828pub trait DeltaEnergyInsert<B, S, X, C> {
829    /// Compute the change in energy.
830    ///
831    /// `initial_microstate` describes the initial configuration and `new_body`
832    /// describes the new body configuration. The final configuration includes
833    /// all bodies in the initial microstate and `new_body`.
834    ///
835    /// Returns:
836    /// ```math
837    /// \Delta E = E_\mathrm{final} - E_\mathrm{initial}
838    /// ```
839    #[must_use]
840    fn delta_energy_insert(
841        &self,
842        initial_microstate: &Microstate<B, S, X, C>,
843        new_body: &Body<B, S>,
844    ) -> f64;
845}
846
847/// Compute the change in energy when a single body is removed.
848///
849/// Some trial moves remove a single body at a time and use a Hamiltonian that
850/// implements `DeltaEnergyRemove` to efficiently compute the change in energy.
851///
852/// The generic type names are:
853/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
854/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
855/// * `X`: The spatial data structure type.
856/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
857///
858/// See the [Implementors](#implementors) section below for examples.
859///
860/// # Derive macro
861///
862/// Use the [`DeltaEnergyRemove`](macro@DeltaEnergyRemove) derive macro to
863/// automatically implement the `DeltaEnergyRemove` trait on a type. The derived
864/// implementation sums the result of `delta_energy_remove` over all fields in the
865/// struct (in the order in which fields are named in the struct definition).
866/// The sum short circuits and returns `f64::INFINITY` when any one field
867/// returns infinity.
868/// ```
869/// use hoomd_interaction::{
870///     DeltaEnergyRemove, External, PairwiseCutoff, external::Linear,
871///     pairwise::Isotropic, univariate::Boxcar,
872/// };
873/// use hoomd_vector::Cartesian;
874///
875/// #[derive(DeltaEnergyRemove)]
876/// struct Hamiltonian {
877///     linear: External<Linear<Cartesian<2>>>,
878///     pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
879/// }
880/// ```
881pub trait DeltaEnergyRemove<B, S, X, C> {
882    /// Compute the change in energy.
883    ///
884    /// `initial_microstate` describes the initial configuration and `body_index` is
885    /// the index of the body to remove. The final configuration includes all bodies
886    /// in the initial microstate except the body previously at `body_index`.
887    ///
888    /// Returns:
889    /// ```math
890    /// \Delta E = E_\mathrm{final} - E_\mathrm{initial}
891    /// ```
892    #[must_use]
893    fn delta_energy_remove(
894        &self,
895        initial_microstate: &Microstate<B, S, X, C>,
896        body_index: usize,
897    ) -> f64;
898}
899
900/// Sum all the forces and virials that act on a given body in a microstate.
901///
902/// See [`Rigid`] for an example.
903///
904/// The generic type names are:
905/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
906/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
907/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
908pub trait NetBodyForceAndVirial<B, S, X, C> {
909    /// The type of the result force.
910    type Force: Outer;
911
912    /// Compute the net force and virial on a body in the microstate.
913    ///
914    /// `microstate` describes the configuration and `body_index` is the index
915    /// of the body to compute.
916    ///
917    /// Returns:
918    ///     TODO
919    #[must_use]
920    fn net_body_force_and_virial(
921        &self,
922        microstate: &Microstate<B, S, X, C>,
923        body_index: usize,
924    ) -> (Self::Force, <Self::Force as Outer>::Tensor);
925}
926
927/// Sum all the forces, virials, and torques that act on a given body in a microstate.
928///
929/// See [`Rigid`] for an example.
930///
931/// The generic type names are:
932/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
933/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
934/// * `X`: The spatial data structure type.
935/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
936pub trait NetBodyForceVirialAndTorque<B, S, X, C> {
937    /// The type of the result force.
938    type Force: Wedge + Outer;
939
940    /// Compute the net force, virial, and torque on a body in the microstate.
941    ///
942    /// `microstate` describes the configuration and `body_index` is the index
943    /// of the body to compute.
944    ///
945    /// Returns:
946    ///     TODO
947    #[must_use]
948    fn net_body_force_virial_and_torque(
949        &self,
950        microstate: &Microstate<B, S, X, C>,
951        body_index: usize,
952    ) -> (
953        Self::Force,
954        <Self::Force as Outer>::Tensor,
955        <Self::Force as Wedge>::Bivector,
956    );
957}
958
959/// Sum all the forces, virials, and torques that act on a given site in a microstate.
960///
961/// In molecular dynamics simulations, bodies move in response to the net force
962/// and torque applied to all sites in the body. As an intermediate step in that
963/// calculation, a type that describes a *force interaction model* must implement
964/// [`NetSiteForceVirialAndTorque`] and compute the net force and torque on a given
965/// *site* in the [`Microstate`].
966///
967/// The generic type names are:
968/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
969/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
970/// * `X`: The spatial data structure type.
971/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
972///
973/// See the [Implementors](#implementors) section below for examples.
974///
975/// # Derive macro
976///
977/// Use the [`NetSiteForceVirialAndTorque`](macro@NetSiteForceVirialAndTorque) derive macro to
978/// automatically implement the `NetSiteForceVirialAndTorque` trait on a type. The derived
979/// implementation sums the result of `net_site_force_and_torque` over all fields in
980/// the struct (in the order in which fields are named in the struct definition).
981/// ```
982/// use hoomd_interaction::{
983///     External, NetSiteForceVirialAndTorque, PairwiseCutoff,
984///     external::ConstantForce, pairwise::Isotropic, univariate::LennardJones,
985/// };
986/// use hoomd_microstate::{Body, Microstate, property::Point};
987/// use hoomd_vector::Cartesian;
988///
989/// #[derive(NetSiteForceVirialAndTorque)]
990/// struct Hamiltonian {
991///     linear: External<ConstantForce<Cartesian<2>>>,
992///     pairwise_cutoff: PairwiseCutoff<Isotropic<LennardJones>>,
993/// }
994/// ```
995pub trait NetSiteForceVirialAndTorque<B, S, X, C> {
996    /// The type of the result force.
997    type Force: Wedge + Outer;
998
999    /// Compute the net force, virial, and torque on a given site.
1000    ///
1001    /// `microstate` describes the configuration and `site_index` is the index
1002    /// of the site to compute.
1003    ///
1004    /// Returns:
1005    ///
1006    /// TODO
1007    #[must_use]
1008    fn net_site_force_virial_and_torque(
1009        &self,
1010        microstate: &Microstate<B, S, X, C>,
1011        site_index: usize,
1012    ) -> (
1013        Self::Force,
1014        <Self::Force as Outer>::Tensor,
1015        <Self::Force as Wedge>::Bivector,
1016    );
1017}
1018
1019/// Sum all the forces and virials that act on a given site in a microstate.
1020///
1021/// In molecular dynamics simulations, bodies move in response to the net
1022/// force applied to all sites in the body. As an intermediate step in that
1023/// calculation, a type that describes a *force interaction model* must
1024/// implement [`NetSiteForceAndVirial`] and compute the net force and virial on
1025/// a given *site* in the [`Microstate`].
1026///
1027/// The generic type names are:
1028/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
1029/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1030/// * `X`: The spatial data structure type.
1031/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
1032///
1033/// See the [Implementors](#implementors) section below for examples.
1034///
1035/// # Derive macro
1036///
1037/// Use the [`NetSiteForceAndVirial`](macro@NetSiteForceAndVirial) derive macro to automatically
1038/// implement the `NetSiteForceAndVirial` trait on a type. The derived implementation
1039/// sums the result of `net_site_force` over all fields in the struct (in the
1040/// order in which fields are named in the struct definition).
1041/// ```
1042/// use hoomd_interaction::{
1043///     External, NetSiteForceAndVirial, PairwiseCutoff,
1044///     external::ConstantForce, pairwise::Isotropic, univariate::LennardJones,
1045/// };
1046/// use hoomd_microstate::{Body, Microstate, property::Point};
1047/// use hoomd_vector::Cartesian;
1048///
1049/// #[derive(NetSiteForceAndVirial)]
1050/// struct Hamiltonian {
1051///     linear: External<ConstantForce<Cartesian<2>>>,
1052///     pairwise_cutoff: PairwiseCutoff<Isotropic<LennardJones>>,
1053/// }
1054/// ```
1055pub trait NetSiteForceAndVirial<B, S, X, C> {
1056    /// The type of the result force.
1057    type Force: Outer;
1058
1059    /// Compute the net force and virial on a given site.
1060    ///
1061    /// `microstate` describes the configuration and `site_index` is the index
1062    /// of the site to compute.
1063    ///
1064    /// Returns:
1065    ///
1066    /// TODO
1067    #[must_use]
1068    fn net_site_force_and_virial(
1069        &self,
1070        microstate: &Microstate<B, S, X, C>,
1071        site_index: usize,
1072    ) -> (Self::Force, <Self::Force as Outer>::Tensor);
1073}
1074
1075/// Compute the force and virial on a single site as a function of its properties.
1076///
1077/// The `SiteForceAndVirial` trait describes a type that can compute the force and virial on a
1078/// site *as a function only of that site's properties*.
1079///
1080/// The [`external`] module provides a number of commonly used implementations.
1081/// Combine them with [`External`] newtype for use with MD simulations.
1082///
1083/// The generic type names are:
1084/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1085pub trait SiteForceAndVirial<S> {
1086    /// The type of the result force.
1087    type Force: Outer;
1088
1089    /// Evaluate the force and virial as a function of a single site's properties.
1090    fn site_force_and_virial(
1091        &self,
1092        site_properties: &S,
1093    ) -> (Self::Force, <Self::Force as Outer>::Tensor);
1094}
1095
1096/// Compute the force, virial, and torque on a single site as a function of its properties.
1097///
1098/// The `SiteForceVirialAndTorque` trait describes a type that can compute the force, virial, and torque
1099/// on a site *as a function only of that site's properties*.
1100///
1101/// The [`external`] module provides a number of commonly used implementations.
1102/// Combine them with [`External`] newtype for use with MD simulations.
1103///
1104/// The generic type names are:
1105/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1106pub trait SiteForceVirialAndTorque<S> {
1107    /// The type of the result force.
1108    type Force: Wedge + Outer;
1109
1110    /// Evaluate the force, virial, and torque as a function of a single site's properties.
1111    fn site_force_virial_and_torque(
1112        &self,
1113        site_properties: &S,
1114    ) -> (
1115        Self::Force,
1116        <Self::Force as Outer>::Tensor,
1117        <Self::Force as Wedge>::Bivector,
1118    );
1119}
1120
1121/// Compute the pairwise force and virial on one site from another site.
1122///
1123/// The `SitePairForceAndVirial` trait describes a type that can compute the
1124/// force and virial on a site by another site *as a function of the two sites'
1125/// properties*.
1126///
1127/// The [`pairwise`] module provides a number of commonly used implementations.
1128/// Combine them with [`PairwiseCutoff`] newtype for use with MD simulations.
1129///
1130/// The generic type names are:
1131/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1132pub trait SitePairForceAndVirial<S> {
1133    /// The type of the result force.
1134    type Force: Outer;
1135
1136    /// Evaluate the force  and virial on site `i` caused by site `j`.
1137    fn site_pair_force_and_virial(
1138        &self,
1139        site_properties_i: &S,
1140        site_properties_j: &S,
1141    ) -> (Self::Force, <Self::Force as Outer>::Tensor);
1142}
1143
1144/// Compute the pairwise force, virial, and torque on one site from another site.
1145///
1146/// The `SitePairForceVirialAndTorque` trait describes a type that can compute
1147/// the force, virial, and torque on a site by another site *as a function of
1148/// the two sites' properties*.
1149///
1150/// The [`pairwise`] module provides a number of commonly used implementations.
1151/// Combine them with [`PairwiseCutoff`] newtype for use with MD simulations.
1152///
1153/// The generic type names are:
1154/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1155pub trait SitePairForceVirialAndTorque<S> {
1156    /// The type of the result force.
1157    type Force: Wedge + Outer;
1158
1159    /// Evaluate the force, torque, and virial on site `i` caused by site `j`.
1160    fn site_pair_force_virial_and_torque(
1161        &self,
1162        site_properties_i: &S,
1163        site_properties_j: &S,
1164    ) -> (
1165        Self::Force,
1166        <Self::Force as Outer>::Tensor,
1167        <Self::Force as Wedge>::Bivector,
1168    );
1169}