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//! # Hamiltonian
14//!
15//! A type that describes a Hamiltonian (or a single term in a multi-part Hamiltonian)
16//! implements one or more of the following traits: [`TotalEnergy`], [`DeltaEnergyOne`],
17//! [`DeltaEnergyInsert`], and [`DeltaEnergyRemove`]. Given a microstate, the
18//! [`total_energy`] method computes the total energy of the Hamiltonian. The various
19//! `delta_energy_*` methods compute the *change* in total energy when updating, inserting,
20//! or removing a body. Total energy computations *typically* cost $` O(N) `$ while
21//! `delta_energy` methods typically cost $` O(1) `$. These costs may vary based
22//! on the specific interaction type and/or the microstate's spatial data structure.
23//!
24//! As a convenience, most Hamiltonian types also implement [`MaximumInteractionRange`],
25//! so that callers can easily determine the maximum site-site interaction range in a
26//! given model.
27//!
28//! All the Hamiltonian traits can be automatically derived using a `#[derive()]` macro
29//! of the same name. The derived implementation sums over all the fields in the struct.
30//!
31//! [`total_energy`]: TotalEnergy::total_energy
32//!
33//! # Univariate interactions
34//!
35//! Many interaction potentials are a function of one variable, typically the
36//! distance between two sites but sometimes the distance between a site and
37//! surface, or an angle. `hoomd-interaction` implements the most commonly
38//! used univariate potentials, such as [`LennardJones`] in [`univariate`].
39//! These types are not Hamiltonian terms on their own, but can be combined
40//! with other types to create interaction models.
41//!
42//! To implement your own univariate interactions, implement the
43//! [`UnivariateEnergy`] and/or [`UnivariateForce`] traits.
44//!
45//! [`LennardJones`]: univariate::LennardJones
46//! [`UnivariateEnergy`]: univariate::UnivariateEnergy
47//! [`UnivariateForce`]: univariate::UnivariateForce
48//!
49//! # Interactions between sites and external objects
50//!
51//! The [`SiteEnergy`] trait describes a type that computes the contribution
52//! of a single site to the total energy as a function only of that site's
53//! properties along with fixed external parameters. The [`External`] type
54//! implements all the Hamiltonian traits. It applies the wrapped [`SiteEnergy`]
55//! to all the sites in the microstate. See [`external`] for a list of built-in
56//! [`SiteEnergy`] implementations.
57//!
58//! # Interactions between all pairs of sites
59//!
60//! The [`SitePairEnergy`] trait describes a type that computes the energy
61//! that a pair of sites contributes to the Hamiltonian as a function of
62//! the properties of the two sites. The [`PairwiseCutoff`] type implements
63//! all the Hamiltonian traits. It applies the wrapped [`SitePairEnergy`]
64//! to all pairs of sites that are within the maximum interaction range.
65//!
66//! The [`pairwise`] module provides numerous types that implement
67//! [`SitePairEnergy`], including [`Isotropic`] (which wraps any
68//! univariate potential), [`HardShape`] (which wraps a shape from
69//! [`hoomd_geometry`], and many others.
70//!
71//! # Zero
72//!
73//! [`Zero`] implements all Hamiltonian traits and represents $` H=0 `$.
74//!
75//! [`Isotropic`]: pairwise::Isotropic
76//! [`HardShape`]: pairwise::HardShape
77//!
78//! # Complete documentation
79//!
80//! `hoomd-interaction` is is a part of *hoomd-rs*. Read the [complete documentation]
81//! for more information.
82//!
83//! [complete documentation]: https://hoomd-rs.readthedocs.io
84
85use hoomd_microstate::{Body, Microstate};
86
87pub mod external;
88pub mod pairwise;
89pub mod univariate;
90
91mod external_type;
92mod pairwise_cutoff;
93mod zero;
94
95pub use external_type::External;
96pub use hoomd_derive::{
97    DeltaEnergyInsert, DeltaEnergyOne, DeltaEnergyRemove, MaximumInteractionRange, SitePairEnergy,
98    TotalEnergy,
99};
100pub use pairwise_cutoff::PairwiseCutoff;
101pub use zero::Zero;
102
103/// Compute the total energy of a potential applied to the microstate.
104///
105/// The `TotalEnergy` trait describes a type that can compute the energy of a
106/// given microstate. Depending on the type, `total_energy` might compute the
107/// total potential energy of the whole Hamiltonian or a single term, such as
108/// the Lennard-Jones potential energy.
109///
110/// # Example
111///
112/// ```
113/// use hoomd_interaction::{
114///     PairwiseCutoff, SitePairEnergy, TotalEnergy, pairwise::Isotropic,
115///     univariate::LennardJones,
116/// };
117/// use hoomd_microstate::{
118///     Body, Microstate,
119///     property::{Point, Position},
120/// };
121/// use hoomd_vector::{Cartesian, Vector};
122///
123/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
124/// let mut microstate = Microstate::new();
125/// microstate.extend_bodies([
126///     Body::point(Cartesian::from([0.0, 0.0])),
127///     Body::point(Cartesian::from([1.0, 0.0])),
128///     Body::point(Cartesian::from([0.0, 5.0])),
129///     Body::point(Cartesian::from([-1.0, 5.0])),
130/// ])?;
131///
132/// let lennard_jones: LennardJones = LennardJones {
133///     epsilon: 1.5,
134///     sigma: 1.0 / 2.0_f64.powf(1.0 / 6.0),
135/// };
136/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
137///     interaction: lennard_jones,
138///     r_cut: 2.5,
139/// });
140///
141/// let total_energy = pairwise_cutoff.total_energy(&microstate);
142/// assert_eq!(total_energy, -3.0);
143/// # Ok(())
144/// # }
145/// ```
146///
147/// # Derive macro
148///
149/// Use the [`TotalEnergy`](macro@TotalEnergy) derive macro to automatically implement
150/// the `TotalEnergy` trait on a type. The derived implementation sums the result of
151/// `total_energy` over all fields in the struct (in the order in which fields
152/// are named in the struct definition). The sum short circuits and returns
153/// `f64::INFINITY` when any one field returns infinity.
154/// ```
155/// use hoomd_interaction::{
156///     External, PairwiseCutoff, TotalEnergy, external::Linear,
157///     pairwise::Isotropic, univariate::Boxcar,
158/// };
159/// use hoomd_microstate::{Body, Microstate, property::Point};
160/// use hoomd_vector::Cartesian;
161///
162/// #[derive(TotalEnergy)]
163/// struct Hamiltonian {
164///     linear: External<Linear<Cartesian<2>>>,
165///     pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
166/// }
167///
168/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
169/// let mut microstate = Microstate::new();
170/// microstate.extend_bodies([
171///     Body::point(Cartesian::from([0.0, 4.0])),
172///     Body::point(Cartesian::from([1.0, 4.0])),
173/// ])?;
174///
175/// let epsilon = 2.0;
176/// let (left, right) = (0.0, 1.5);
177/// let boxcar = Boxcar {
178///     epsilon,
179///     left,
180///     right,
181/// };
182/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
183///     interaction: boxcar,
184///     r_cut: right,
185/// });
186///
187/// let linear = External(Linear {
188///     alpha: 1.0,
189///     plane_origin: Cartesian::default(),
190///     plane_normal: [0.0, 1.0].try_into()?,
191/// });
192///
193/// let hamiltonian = Hamiltonian {
194///     pairwise_cutoff,
195///     linear,
196/// };
197///
198/// let total_energy = hamiltonian.total_energy(&microstate);
199/// assert_eq!(total_energy, 10.0);
200/// # Ok(())
201/// # }
202/// ```
203pub trait TotalEnergy<M> {
204    /// Compute the energy.
205    #[must_use]
206    fn total_energy(&self, microstate: &M) -> f64;
207
208    /// Compute the difference in energy between two microstates.
209    ///
210    /// Returns $` E_\mathrm{final} - E_\mathrm{initial} `$.
211    #[inline]
212    #[must_use]
213    fn delta_energy_total(&self, initial_microstate: &M, final_microstate: &M) -> f64 {
214        self.total_energy(final_microstate) - self.total_energy(initial_microstate)
215    }
216}
217
218/// Compute the energy contribution of a single site.
219///
220/// The `SiteEnergy` trait describes a type that can compute the energy contribution
221/// of a site to the system's total energy *as a function only of that site's
222/// properties*.
223///
224/// The [`external`] module provides a number of commonly used implementations.
225/// Combine them with [`External`] newtype for use with MC and MD simulations or to
226/// compute system-wide properties.
227///
228/// The generic type names are:
229/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
230///
231/// ## Examples
232///
233/// Implement a custom site energy function:
234///
235/// ```
236/// use hoomd_interaction::{External, SiteEnergy, TotalEnergy};
237/// use hoomd_microstate::{
238///     Body, Microstate,
239///     property::{Point, Position},
240/// };
241/// use hoomd_vector::Cartesian;
242///
243/// struct Custom {
244///     a: f64,
245///     b: f64,
246/// }
247///
248/// impl<S> SiteEnergy<S> for Custom
249/// where
250///     S: Position<Position = Cartesian<2>>,
251/// {
252///     fn site_energy(&self, site_properties: &S) -> f64 {
253///         self.a * (site_properties.position()[0] / self.b).cos()
254///     }
255/// }
256///
257/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
258/// let mut microstate = Microstate::new();
259/// microstate.extend_bodies([
260///     Body::point(Cartesian::from([1.0, 0.0])),
261///     Body::point(Cartesian::from([-1.0, 2.0])),
262/// ])?;
263///
264/// let custom = Custom { a: 1.0, b: 10.0 };
265/// let site_energy = custom.site_energy(&microstate.sites()[0].properties);
266///
267/// let custom = External(custom);
268/// let total_energy = custom.total_energy(&microstate);
269/// # Ok(())
270/// # }
271/// ```
272///
273/// Custom method that checks for overlaps of a disk with a circular boundary.
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, Metric};
282///
283/// struct Custom {
284///     r: f64,
285/// }
286///
287/// impl<S> SiteEnergy<S> for Custom
288/// where
289///     S: Position<Position = Cartesian<2>>,
290/// {
291///     fn site_energy(&self, site_properties: &S) -> f64 {
292///         if site_properties.position().distance(&Cartesian::default())
293///             > self.r - 0.5
294///         {
295///             f64::INFINITY
296///         } else {
297///             0.0
298///         }
299///     }
300///
301///     fn is_only_infinite_or_zero() -> bool {
302///         true
303///     }
304/// }
305///
306/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
307/// let mut microstate = Microstate::new();
308/// microstate.extend_bodies([Body::point(Cartesian::from([9.6, 0.0]))])?;
309///
310/// let custom = Custom { r: 10.0 };
311/// let site_energy = custom.site_energy(&microstate.sites()[0].properties);
312/// assert_eq!(site_energy, f64::INFINITY);
313///
314/// let custom = External(custom);
315/// let total_energy = custom.total_energy(&microstate);
316/// assert_eq!(total_energy, f64::INFINITY);
317/// # Ok(())
318/// # }
319/// ```
320pub trait SiteEnergy<S> {
321    /// Evaluate the energy contribution of a single site.
322    #[must_use]
323    fn site_energy(&self, site_properties: &S) -> f64;
324
325    /// Evaluate the energy contribution of a single site *in the initial state*.
326    ///
327    /// Override this method in potentials that have both infinite or zero
328    /// terms and finite terms, such as the sum of a hard site-wall interaction
329    /// plus an attractive well. `site_energy` should compute both terms and
330    /// `site_energy_initial` should compute only the finite terms.
331    ///
332    /// [`External`] calls `site_energy_initial` when evaluating the energy of
333    /// the initial state in a trial move. The infinite interaction term can be
334    /// assumed 0 in the initial state because no site will ever be placed in an
335    /// infinite energy configuration.
336    #[must_use]
337    #[inline]
338    fn site_energy_initial(&self, site_properties: &S) -> f64 {
339        self.site_energy(site_properties)
340    }
341
342    /// Does this potential only ever return infinity or zero?
343    ///
344    /// Override this method and return `true` for e.g. hard site-wall
345    /// interactions that always return infinity or zero and **never** any other
346    /// value. When this method returns `true`, [`External`] skips the initial
347    /// energy computation and assumes it is zero.
348    #[must_use]
349    #[inline]
350    fn is_only_infinite_or_zero() -> bool {
351        false
352    }
353}
354
355/// Compute the energy contribution from a pair of sites.
356///
357/// The `SitePairEnergy` trait describes a type that can compute the energy
358/// contribution from a pair of sites to the system's total energy *as a function
359/// only of those site's properties*.
360///
361/// The [`pairwise`] module provides a number of commonly used implementations,
362/// such as [`Isotropic`], [`Anisotropic`], and [`HardShape`]. Combine any
363/// of them with the [`PairwiseCutoff`] for use with MC and MD simulations or to
364/// compute system-wide properties.
365///
366/// The generic type names are:
367/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
368///
369/// [`Isotropic`]: pairwise::Isotropic
370/// [`Anisotropic`]: pairwise::Anisotropic
371/// [`HardShape`]: pairwise::HardShape
372///
373/// ## Examples
374///
375/// Implement a custom site energy method:
376/// ```
377/// use hoomd_interaction::{
378///     MaximumInteractionRange, PairwiseCutoff, SitePairEnergy, TotalEnergy,
379/// };
380/// use hoomd_microstate::{
381///     Body, Microstate,
382///     property::{Point, Position},
383/// };
384/// use hoomd_vector::{Cartesian, InnerProduct};
385///
386/// #[derive(MaximumInteractionRange)]
387/// struct Custom {
388///     epsilon: f64,
389///     maximum_interaction_range: f64,
390/// }
391///
392/// impl<S> SitePairEnergy<S> for Custom
393/// where
394///     S: Position<Position = Cartesian<2>>,
395/// {
396///     fn site_pair_energy(
397///         &self,
398///         site_properties_i: &S,
399///         site_properties_j: &S,
400///     ) -> f64 {
401///         self.epsilon
402///             * site_properties_i
403///                 .position()
404///                 .dot(&site_properties_j.position())
405///     }
406/// }
407///
408/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
409/// let mut microstate = Microstate::new();
410/// microstate.extend_bodies([
411///     Body::point(Cartesian::from([1.0, 0.0])),
412///     Body::point(Cartesian::from([0.0, 1.0])),
413/// ])?;
414///
415/// let custom = Custom {
416///     epsilon: 1.0,
417///     maximum_interaction_range: 2.5,
418/// };
419/// let site_pair_energy = custom.site_pair_energy(
420///     &microstate.sites()[0].properties,
421///     &microstate.sites()[1].properties,
422/// );
423///
424/// let custom = PairwiseCutoff(custom);
425/// let total_energy = custom.total_energy(&microstate);
426/// # Ok(())
427/// # }
428/// ```
429///
430/// Implement a custom site overlap method:
431/// ```
432/// use hoomd_interaction::{
433///     MaximumInteractionRange, PairwiseCutoff, SitePairEnergy, TotalEnergy,
434/// };
435/// use hoomd_microstate::{
436///     Body, Microstate, Transform,
437///     property::{Point, Position},
438/// };
439/// use hoomd_utility::valid::PositiveReal;
440/// use hoomd_vector::{Cartesian, Metric};
441///
442/// #[derive(Default, Position)]
443/// struct CircleSiteProperties {
444///     position: Cartesian<2>,
445///     radius: PositiveReal,
446/// }
447///
448/// impl Transform<CircleSiteProperties> for Point<Cartesian<2>> {
449///     fn transform(
450///         &self,
451///         site_properties: &CircleSiteProperties,
452///     ) -> CircleSiteProperties {
453///         CircleSiteProperties {
454///             position: self.position + site_properties.position,
455///             ..*site_properties
456///         }
457///     }
458/// }
459///
460/// #[derive(MaximumInteractionRange)]
461/// struct PolydisperseCircleOverlap {
462///     maximum_interaction_range: f64,
463/// }
464///
465/// impl SitePairEnergy<CircleSiteProperties> for PolydisperseCircleOverlap {
466///     fn site_pair_energy(
467///         &self,
468///         a: &CircleSiteProperties,
469///         b: &CircleSiteProperties,
470///     ) -> f64 {
471///         let r = a.position().distance(b.position());
472///
473///         if r < a.radius.get() + b.radius.get() {
474///             f64::INFINITY
475///         } else {
476///             0.0
477///         }
478///     }
479///
480///     fn is_only_infinite_or_zero() -> bool {
481///         true
482///     }
483///
484///     fn site_pair_energy_initial(
485///         &self,
486///         _a: &CircleSiteProperties,
487///         _b: &CircleSiteProperties,
488///     ) -> f64 {
489///         0.0
490///     }
491/// }
492///
493/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
494/// let mut microstate = Microstate::new();
495/// microstate.extend_bodies([
496///     Body {
497///         properties: Point::new(Cartesian::from([0.0, 0.0])),
498///         sites: vec![CircleSiteProperties {
499///             position: Cartesian::from([0.0, 0.0]),
500///             radius: 0.5.try_into()?,
501///         }],
502///     },
503///     Body {
504///         properties: Point::new(Cartesian::from([1.4, 0.0])),
505///         sites: vec![CircleSiteProperties {
506///             position: Cartesian::from([0.0, 0.0]),
507///             radius: 1.0.try_into()?,
508///         }],
509///     },
510/// ])?;
511///
512/// let overlap = PolydisperseCircleOverlap {
513///     maximum_interaction_range: 1.5,
514/// };
515/// let site_pair_energy = overlap.site_pair_energy(
516///     &microstate.sites()[0].properties,
517///     &microstate.sites()[1].properties,
518/// );
519/// assert_eq!(site_pair_energy, f64::INFINITY);
520///
521/// let pairwise_cutoff = PairwiseCutoff(overlap);
522/// let total_energy = pairwise_cutoff.total_energy(&microstate);
523/// assert_eq!(total_energy, f64::INFINITY);
524/// # Ok(())
525/// # }
526/// ```
527///
528/// # Derive macro
529///
530/// Use the [`SitePairEnergy`](macro@SitePairEnergy) derive macro to
531/// automatically implement the `SitePairEnergy` trait on a type.
532/// The implemented `site_pair_energy` sums the result of `site_pair_energy`
533/// over all fields. The implementation returns early when any one field returns
534/// infinity. The implemented `site_pair_energy_initial` behaves similarly.
535/// The derived `is_only_infinite_or_zero` returns true only when all fields
536/// also return true for the same method.
537///
538/// ```
539/// use hoomd_interaction::{
540///     MaximumInteractionRange, SitePairEnergy,
541///     pairwise::{AngularMask, Anisotropic, HardSphere},
542///     univariate::Boxcar,
543/// };
544/// use hoomd_vector::Cartesian;
545///
546/// #[derive(MaximumInteractionRange, SitePairEnergy)]
547/// struct SitePairInteraction {
548///     hard_disk: HardSphere,
549///     angular_mask: Anisotropic<AngularMask<Boxcar, Cartesian<2>>>,
550/// }
551/// ```
552pub trait SitePairEnergy<S> {
553    /// Evaluate the energy contribution from a pair of sites.
554    fn site_pair_energy(&self, site_properties_i: &S, site_properties_j: &S) -> f64;
555
556    /// Evaluate the energy contribution from a pair of sites *in the initial state*.
557    ///
558    /// Override this method in potentials that have both infinite or zero terms
559    /// and finite terms, such as the sum of a hard site-wall interaction plus
560    /// an attractive well. `site_pair_energy` should compute both terms and
561    /// `site_pair_energy_initial` should compute only the finite terms.
562    ///
563    /// [`PairwiseCutoff`] calls `site_pair_energy_initial` when evaluating the
564    /// energy of the initial state in a trial move. The infinite interaction
565    /// term can be assumed 0 in the initial state because no site will ever be
566    /// placed in an infinite energy configuration.
567    #[must_use]
568    #[inline]
569    fn site_pair_energy_initial(&self, site_properties_i: &S, site_properties_j: &S) -> f64 {
570        self.site_pair_energy(site_properties_i, site_properties_j)
571    }
572
573    /// Does this potential only ever return infinity or zero?
574    ///
575    /// Override this method and return `true` for e.g. hard particle
576    /// interactions that always return infinity or zero and **never** any other
577    /// value. When this method returns `true`, [`PairwiseCutoff`] skips the
578    /// initial energy computation and assumes it is zero.
579    #[must_use]
580    #[inline]
581    fn is_only_infinite_or_zero() -> bool {
582        false
583    }
584}
585
586/// Largest distance between two sites where the pairwise interaction may be non-zero.
587///
588/// [`PairwiseCutoff`] uses the provided maximum interaction range to
589/// efficiently compute only the needed interactions. All types that implement
590/// `SitePair*` traits must also implement [`MaximumInteractionRange`].
591///
592/// # Derive macro
593///
594/// Use the [`MaximumInteractionRange`](macro@MaximumInteractionRange) derive macro to
595/// automatically implement the `MaximumInteractionRange` trait on a type.
596///
597/// When the type has a field named `maximum_interaction_range`, the derived implementation
598/// returns it. When there is no such field, the derived implementation takes
599/// the maximum of the `maximum_interaction_range` over all fields in the struct.
600/// The former case is intended for use with custom site pair potentials and
601/// the latter is intended for use with multi-term Hamiltonian types.
602/// ```
603/// use hoomd_interaction::{
604///     External, MaximumInteractionRange, PairwiseCutoff, external::Linear,
605/// };
606/// use hoomd_vector::Cartesian;
607///
608/// #[derive(MaximumInteractionRange)]
609/// struct SitePairInteraction {
610///     // ...
611///     maximum_interaction_range: f64,
612/// }
613///
614/// #[derive(MaximumInteractionRange)]
615/// struct Hamiltonian {
616///     linear: External<Linear<Cartesian<2>>>,
617///     pairwise_cutoff: PairwiseCutoff<SitePairInteraction>,
618/// }
619/// ```
620pub trait MaximumInteractionRange {
621    /// The largest distance between two sites where the pairwise interaction may be non-zero.
622    fn maximum_interaction_range(&self) -> f64;
623}
624
625/// Compute the change in energy as a function of a single modified body.
626///
627/// Some trial moves apply to a single body at a time and use a Hamiltonian that
628/// implements `DeltaEnergyOne` to efficiently compute the change in energy.
629///
630/// The generic type names are:
631/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
632/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
633/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
634///
635/// See the [Implementors](#implementors) section below for examples.
636///
637/// # Derive macro
638///
639/// Use the [`DeltaEnergyOne`](macro@DeltaEnergyOne) derive macro to
640/// automatically implement the `DeltaEnergyOne` trait on a type. The derived
641/// implementation sums the result of `delta_energy_one` over all fields in the
642/// struct (in the order in which fields are named in the struct definition).
643/// The sum short circuits and returns `f64::INFINITY` when any one field
644/// returns infinity.
645/// ```
646/// use hoomd_interaction::{
647///     DeltaEnergyOne, External, PairwiseCutoff, external::Linear,
648///     pairwise::Isotropic, univariate::Boxcar,
649/// };
650/// use hoomd_microstate::{Body, Microstate, property::Point};
651/// use hoomd_vector::Cartesian;
652///
653/// #[derive(DeltaEnergyOne)]
654/// struct Hamiltonian {
655///     linear: External<Linear<Cartesian<2>>>,
656///     pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
657/// }
658///
659/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
660/// let mut microstate = Microstate::new();
661/// microstate.extend_bodies([
662///     Body::point(Cartesian::from([0.0, 0.0])),
663///     Body::point(Cartesian::from([1.0, 0.0])),
664/// ])?;
665///
666/// let epsilon = 2.0;
667/// let (left, right) = (0.0, 1.5);
668/// let boxcar = Boxcar {
669///     epsilon,
670///     left,
671///     right,
672/// };
673/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
674///     interaction: boxcar,
675///     r_cut: right,
676/// });
677///
678/// let linear = External(Linear {
679///     alpha: 10.0,
680///     plane_origin: Cartesian::default(),
681///     plane_normal: [0.0, 1.0].try_into()?,
682/// });
683///
684/// let hamiltonian = Hamiltonian {
685///     pairwise_cutoff,
686///     linear,
687/// };
688///
689/// let delta_energy = hamiltonian.delta_energy_one(
690///     &microstate,
691///     0,
692///     &Body::point([-1.0, 0.0].into()),
693/// );
694/// assert_eq!(delta_energy, -2.0);
695/// # Ok(())
696/// # }
697/// ```
698pub trait DeltaEnergyOne<B, S, X, C> {
699    /// Compute the change in energy.
700    ///
701    /// `initial_microstate` describes the initial configuration and `final_body`
702    /// describes the new body configuration. In the final configuration, the
703    /// body may have changed properties and/or sites. The index `body_index`
704    /// identifies which body in `initial_microstate` is changing.
705    ///
706    /// Returns:
707    /// ```math
708    /// \Delta E = E_\mathrm{final} - E_\mathrm{initial}
709    /// ```
710    #[must_use]
711    fn delta_energy_one(
712        &self,
713        initial_microstate: &Microstate<B, S, X, C>,
714        body_index: usize,
715        final_body: &Body<B, S>,
716    ) -> f64;
717}
718
719/// Compute the change in energy when a single body is inserted.
720///
721/// Some trial moves insert a single body at a time and use a Hamiltonian that
722/// implements `DeltaEnergyInsert` to efficiently compute the change in energy.
723///
724/// The generic type names are:
725/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
726/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
727/// * `X`: The spatial data structure type.
728/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
729///
730/// See the [Implementors](#implementors) section below for examples.
731///
732/// # Derive macro
733///
734/// Use the [`DeltaEnergyInsert`](macro@DeltaEnergyInsert) derive macro to
735/// automatically implement the `DeltaEnergyInsert` trait on a type. The derived
736/// implementation sums the result of `delta_energy_insert` over all fields in the
737/// struct (in the order in which fields are named in the struct definition).
738/// The sum short circuits and returns `f64::INFINITY` when any one field
739/// returns infinity.
740/// ```
741/// use hoomd_interaction::{
742///     DeltaEnergyInsert, External, PairwiseCutoff, external::Linear,
743///     pairwise::Isotropic, univariate::Boxcar,
744/// };
745/// use hoomd_microstate::{Body, Microstate, property::Point};
746/// use hoomd_vector::Cartesian;
747///
748/// #[derive(DeltaEnergyInsert)]
749/// struct Hamiltonian {
750///     linear: External<Linear<Cartesian<2>>>,
751///     pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
752/// }
753///
754/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
755/// let mut microstate = Microstate::new();
756/// microstate.extend_bodies([Body::point(Cartesian::from([0.0, 4.0]))])?;
757///
758/// let epsilon = 2.0;
759/// let (left, right) = (0.0, 1.5);
760/// let boxcar = Boxcar {
761///     epsilon,
762///     left,
763///     right,
764/// };
765/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
766///     interaction: boxcar,
767///     r_cut: right,
768/// });
769///
770/// let linear = External(Linear {
771///     alpha: 1.0,
772///     plane_origin: Cartesian::default(),
773///     plane_normal: [0.0, 1.0].try_into()?,
774/// });
775///
776/// let hamiltonian = Hamiltonian {
777///     pairwise_cutoff,
778///     linear,
779/// };
780///
781/// let new_body = Body::point(Cartesian::from([1.0, 4.0]));
782/// let delta_energy = hamiltonian.delta_energy_insert(&microstate, &new_body);
783/// assert_eq!(delta_energy, 6.0);
784/// # Ok(())
785/// # }
786/// ```
787pub trait DeltaEnergyInsert<B, S, X, C> {
788    /// Compute the change in energy.
789    ///
790    /// `initial_microstate` describes the initial configuration and `new_body`
791    /// describes the new body configuration. The final configuration includes
792    /// all bodies in the initial microstate and `new_body`.
793    ///
794    /// Returns:
795    /// ```math
796    /// \Delta E = E_\mathrm{final} - E_\mathrm{initial}
797    /// ```
798    #[must_use]
799    fn delta_energy_insert(
800        &self,
801        initial_microstate: &Microstate<B, S, X, C>,
802        new_body: &Body<B, S>,
803    ) -> f64;
804}
805
806/// Compute the change in energy when a single body is removed.
807///
808/// Some trial moves remove a single body at a time and use a Hamiltonian that
809/// implements `DeltaEnergyRemove` to efficiently compute the change in energy.
810///
811/// The generic type names are:
812/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
813/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
814/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
815///
816/// See the [Implementors](#implementors) section below for examples.
817///
818/// # Derive macro
819///
820/// Use the [`DeltaEnergyRemove`](macro@DeltaEnergyRemove) derive macro to
821/// automatically implement the `DeltaEnergyRemove` trait on a type. The derived
822/// implementation sums the result of `delta_energy_remove` over all fields in the
823/// struct (in the order in which fields are named in the struct definition).
824/// The sum short circuits and returns `f64::INFINITY` when any one field
825/// returns infinity.
826/// ```
827/// use hoomd_interaction::{
828///     DeltaEnergyRemove, External, PairwiseCutoff, external::Linear,
829///     pairwise::Isotropic, univariate::Boxcar,
830/// };
831/// use hoomd_vector::Cartesian;
832///
833/// #[derive(DeltaEnergyRemove)]
834/// struct Hamiltonian {
835///     linear: External<Linear<Cartesian<2>>>,
836///     pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
837/// }
838/// ```
839pub trait DeltaEnergyRemove<B, S, X, C> {
840    /// Compute the change in energy.
841    ///
842    /// `initial_microstate` describes the initial configuration and `body_index` is
843    /// the index of the body to remove. The final configuration includes all bodies
844    /// in the initial microstate except the body previously at `body_index`.
845    ///
846    /// Returns:
847    /// ```math
848    /// \Delta E = E_\mathrm{final} - E_\mathrm{initial}
849    /// ```
850    #[must_use]
851    fn delta_energy_remove(
852        &self,
853        initial_microstate: &Microstate<B, S, X, C>,
854        body_index: usize,
855    ) -> f64;
856}