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(µstate);
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(µstate);
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(µstate.sites()[0].properties);
266///
267/// let custom = External(custom);
268/// let total_energy = custom.total_energy(µstate);
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(µstate.sites()[0].properties);
312/// assert_eq!(site_energy, f64::INFINITY);
313///
314/// let custom = External(custom);
315/// let total_energy = custom.total_energy(µstate);
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/// µstate.sites()[0].properties,
421/// µstate.sites()[1].properties,
422/// );
423///
424/// let custom = PairwiseCutoff(custom);
425/// let total_energy = custom.total_energy(µstate);
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/// µstate.sites()[0].properties,
517/// µstate.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(µstate);
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/// µstate,
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(µstate, &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}