cons_laws/
lib.rs

1//! Deterministic particle methods for 1D conservation laws.
2//!
3//! This crate implements the deterministic particle schemes described in the article
4//! *Entropy solutions of non-local scalar conservation laws with congestion via deterministic particle method*,
5//! E. Radici, F. Stra, (2021), [https://arxiv.org/abs/2107.10760](https://arxiv.org/abs/2107.10760).
6//!
7//! You can cite the article as
8//!
9//! ```text
10//! @online{RadiciStra2021,
11//!     title={Entropy solutions of non-local scalar conservation laws with congestion via deterministic particle method},
12//!     author={Emanuela Radici and Federico Stra},
13//!     year={2021},
14//!     eprint={2107.10760},
15//!     archivePrefix={arXiv},
16//!     primaryClass={math.AP},
17//!     url={https://arxiv.org/abs/2107.10760}
18//! }
19//! ```
20//!
21//! This is a reimplementation in Rust of the Julia package
22//! [ConservationLawsParticles.jl](https://github.com/FedericoStra/ConservationLawsParticles.jl).
23//!
24//! # What's the goal?
25//!
26//! The conservation law to be solved is
27//!
28//! $$
29//! \partial_t\rho(t,x) + \mathop{\mathrm{div}}_x\bigl[\rho(t,x)v\bigl(\rho(t,x)\bigr)\bigl(V(t,x)-(\partial_xW*\rho)(t,x)\bigr)\bigr] = 0 ,
30//! $$
31//!
32//! where
33//!
34//! - $V:[0,\infty)\times\mathbb{R}\to\mathbb{R}$ is the external velocity,
35//! - $W:[0,\infty)\to\mathbb{R}$ is the interaction potential,
36//! - $v:[0,\infty)\to[0,\infty)$ is a non increasing function to model congestion,
37//! - the convolution is performed in space only.
38//!
39//! The approach to solve the conservation law is by tracking the motion of $N+1$ particles
40//! $X=(x_0,x_1,\dots,x_N)$ such that between each consecutive pair of particles there is
41//! a fraction $1/N$ of the total mass.
42//!
43//! Two deterministic particle schemes are described: one with integrated interaction $(\mathrm{ODE}_I)$ and one with sampled interaction $(\mathrm{ODE}_S)$:
44//!
45//! ```math
46//! \begin{aligned}
47//! (\mathrm{ODE}_I): & \left\{\begin{aligned}
48//! x_i'(t) &= v_i(t) \bar U_i(t), \\
49//! \bar U_i(t) &= V\bigl(t,x_i(t)\bigr) - (W*\partial_x\bar\rho)\bigl(t,x_i(t)\bigr)
50//!     = V\bigl(t,x_i(t)\bigr) - \sum_{j=0}^N(\rho_{j+1}(t) - \rho_j(t)) W\bigl(t,x_i(t)-x_j(t)\bigr), \\
51//! v_i(t) &= \begin{cases}
52//!     v\bigl(\rho_i(t)\bigr), & \text{if } \bar U_i(t) < 0, \\
53//!     v\bigl(\rho_{i+1}(t)\bigr), & \text{if } \bar U_i(t) \geq 0, \end{cases}
54//! \end{aligned}\right. \\
55//! (\mathrm{ODE}_S): & \left\{\begin{aligned}
56//! x_i'(t) &= v_i(t) \dot U_i(t), \\
57//! \dot U_i(t) &= V\bigl(t,x_i(t)\bigr) - (\partial_xW*\dot\rho)\bigl(t,x_i(t)\bigr)
58//!     = V\bigl(t,x_i(t)\bigr) - \frac1N\sum_{j=0}^NW'\bigl(t,x_i(t)-x_j(t)\bigr), \\
59//! v_i(t) &= \begin{cases}
60//!     v\bigl(\rho_i(t)\bigr), & \text{if } \dot U_i(t) < 0, \\
61//!     v\bigl(\rho_{i+1}(t)\bigr), & \text{if } \dot U_i(t) \geq 0. \end{cases}
62//! \end{aligned}\right.
63//! \end{aligned}
64//! ```
65//!
66//! Here $\rho_i$ denotes the quantity $\frac1{N(x_i-x_{i-1})}$,
67//! $\bar\rho$ is the piecewise constant density $\sum_{i=1}^N \rho_i 1_{[x_{i-1},x_i]}$
68//! and $\dot\rho$ is the atomic measure $\frac1N \sum_{i=0}^N \delta_{x_i}$.
69//! Notice that $\dot\rho$ is not a probability.
70//!
71//! # Example
72//!
73//! This example requires the `ode_solver` feature.
74//!
75//! Consider the following external velocity, interaction potential and mobility:
76//!
77//! ```math
78//! \begin{aligned}
79//! V(t,x) &= \mathop{\mathrm{sign}}(7.5-t)
80//!     [2 + 0.5\sin(2x) + 0.3\sin(2\sqrt2x) + 0.2\cos(2\sqrt7x)] , \\
81//! W(t,x) &= (1 + \sin(t)) (x^2 - |x|) , \\
82//! v(t,\rho) &= (1-\rho)_+ .
83//! \end{aligned}
84//! ```
85//!
86//! The initial datum $\rho_0 = 1_{[-1.5,1.5]}$ is discretized $N=201$ with equally spaced particles.
87//!
88#![cfg_attr(feature = "ode_solver", doc = "```no_run")]
89#![cfg_attr(not(feature = "ode_solver"), doc = "```ignore")]
90//! use cons_laws::*;
91//! use ode_solvers::{dopri5::Dopri5, SVector};
92//!
93//! #[inline]
94//! fn V(t: f64, x: f64) -> f64 {
95//!     let sign = if t < 7.5 { 1.0 } else { -1.0 };
96//!     let magn = 2.0 + 0.5 * (2.0 * x).sin() + 0.3 * (2.0 * 2.0f64.sqrt() * x).sin()
97//!         + 0.2 * (2.0 * 7.0f64.sqrt() * x).cos();
98//!     sign * magn
99//! }
100//! #[inline]
101//! fn Wprime(t: f64, x: f64) -> f64 { (x + x - x.signum()) * (1.0 + t.sin()) }
102//! #[inline]
103//! fn W(t: f64, x: f64) -> f64 { (x * x - x.abs()) * (1.0 + t.sin()) }
104//! #[inline]
105//! fn v(t: f64, rho: f64) -> f64 { 0.0f64.max(1.0 - rho) }
106//!
107//! // define the model, using the sampled or integrated interaction
108//! let sampl_inter = SampledInteraction::new(Wprime);
109//! let integ_inter = SampledInteraction::new(W);
110//! let model = SingleSpeciesModel::new(V, sampl_inter, v);
111//!
112//! // initial condition
113//! const N: usize = 201;
114//! let x0 = SVector::<f64, N>::from_iterator((0..N).map(|i| 3.0 * i as f64 / (N - 1) as f64 - 1.5));
115//!
116//! // configure the solver
117//! let t_start = 0.0;
118//! let t_end = 15.0;
119//! let dt = 0.001;
120//! let rtol = 1e-10;
121//! let atol = 1e-10;
122//! let mut solver = Dopri5::new(model, t_start, t_end, dt, x0, rtol, atol);
123//!
124//! // solve the system of ODEs
125//! solver.integrate();
126//! let t = solver.x_out(); // times
127//! let x = solver.y_out(); // particles positions
128//! let n_steps = t.len();
129//! ```
130//! Plotting (some of) the trajectories with [`plotters`](https://crates.io/crates/plotters)
131//! produces the following picture:
132//!
133//! ![Traffic with `ode_solver` feature][traffic_ode_solver]
134//!
135#![doc = ::embed_doc_image::embed_image!("traffic_ode_solver", "doc/imgs/traffic_ode_solver.png")]
136#![cfg_attr(docsrs, feature(doc_cfg))]
137
138/// A time-dependent velocity field that affects the particles.
139pub trait ExternalVelocity<T, X = T> {
140    fn eval(&self, t: T, x: X) -> X;
141}
142
143/// A time-dependent interaction between the particles.
144pub trait Interaction<T, X = T> {
145    fn eval<P>(&self, t: T, x: X, p: P) -> X
146    where
147        P: IntoIterator<Item = X>;
148}
149
150/// A time-dependent mobility.
151pub trait Mobility<T, X = T> {
152    fn eval(&self, t: T, x: X) -> X;
153}
154
155mod _zero;
156pub use _zero::{OneMobility, ZeroInteraction, ZeroVelocity};
157
158pub struct Velocity<V>(V);
159
160impl<V> Velocity<V> {
161    #[allow(non_snake_case)]
162    pub fn new(V: V) -> Self {
163        Self(V)
164    }
165}
166
167impl<T, X, V> ExternalVelocity<T, X> for Velocity<V>
168where
169    V: Fn(T, X) -> X,
170{
171    #[inline]
172    fn eval(&self, t: T, x: X) -> X {
173        self.0(t, x)
174    }
175}
176
177impl<T, X, V> ExternalVelocity<T, X> for V
178where
179    V: Fn(T, X) -> X,
180{
181    #[inline]
182    fn eval(&self, t: T, x: X) -> X {
183        self(t, x)
184    }
185}
186
187impl<T, X, F> Mobility<T, X> for F
188where
189    F: Fn(T, X) -> X,
190{
191    #[inline]
192    fn eval(&self, t: T, x: X) -> X {
193        self(t, x)
194    }
195}
196
197pub struct SampledInteraction<Wprime>(Wprime);
198
199impl<Wprime> SampledInteraction<Wprime> {
200    #[allow(non_snake_case)]
201    pub fn new(Wprime: Wprime) -> Self {
202        Self(Wprime)
203    }
204}
205
206impl<T, X, Wprime> Interaction<T, X> for SampledInteraction<Wprime>
207where
208    T: Copy,
209    X: Copy + num_traits::real::Real,
210    Wprime: Fn(T, X) -> X,
211{
212    fn eval<P>(&self, t: T, x: X, p: P) -> X
213    where
214        P: IntoIterator<Item = X>,
215    {
216        let mut sum = X::zero();
217        let mut len = 0;
218        for y in p.into_iter() {
219            len += 1;
220            let r = x - y;
221            // The function `self.0` (which is `W'`) must return `0` if `y == x`.
222            // We can ensure it here by skipping the `x` when we encounter it.
223            if r != X::zero() {
224                sum = sum + self.0(t, r);
225            }
226        }
227        -X::from(sum).unwrap() / X::from(len - 1).unwrap()
228    }
229}
230
231pub struct IntegratedInteraction<W>(W);
232
233impl<W> IntegratedInteraction<W> {
234    #[allow(non_snake_case)]
235    pub fn new(W: W) -> Self {
236        Self(W)
237    }
238}
239
240impl<T, X, W> Interaction<T, X> for IntegratedInteraction<W>
241where
242    T: Copy,
243    X: Copy + num_traits::real::Real,
244    W: Fn(T, X) -> X,
245{
246    fn eval<P>(&self, t: T, x: X, p: P) -> X
247    where
248        P: IntoIterator<Item = X>,
249    {
250        let mut p = p.into_iter().peekable();
251        let mut total = X::zero();
252        let mut left_dens = X::zero();
253        let mut len = 0;
254        while let Some(y) = p.next() {
255            len += 1;
256            let right_dens = if let Some(z) = p.peek() {
257                (*z - y).recip()
258            } else {
259                X::zero()
260            };
261            total = total + self.0(t, x - y) * (right_dens - left_dens);
262            left_dens = right_dens;
263        }
264        -total / X::from(len - 1).unwrap()
265    }
266}
267
268pub struct SingleSpeciesModel<T, X, Vel, Int, Mob> {
269    pub vel: Vel,
270    pub int: Int,
271    pub mob: Mob,
272    _t: std::marker::PhantomData<T>,
273    _x: std::marker::PhantomData<X>,
274}
275
276impl<T, X, Vel, Int, Mob> SingleSpeciesModel<T, X, Vel, Int, Mob>
277where
278    Vel: ExternalVelocity<T, X>,
279    Int: Interaction<T, X>,
280    Mob: Mobility<T, X>,
281{
282    pub fn new(vel: Vel, int: Int, mob: Mob) -> Self {
283        Self {
284            vel,
285            int,
286            mob,
287            _t: std::marker::PhantomData,
288            _x: std::marker::PhantomData,
289        }
290    }
291}
292
293#[cfg(feature = "ode_solver")]
294pub use _ode_solver::*;
295
296#[cfg(feature = "ode_solver")]
297#[cfg_attr(docsrs, doc(cfg(feature = "ode_solver")))]
298mod _ode_solver {
299    use super::*;
300
301    use ode_solvers::{SVector, System};
302
303    impl<X, Vel, Int, Mob, const N: usize> System<SVector<X, N>>
304        for SingleSpeciesModel<f64, X, Vel, Int, Mob>
305    where
306        Vel: ExternalVelocity<f64, X>,
307        Int: Interaction<f64, X>,
308        Mob: Mobility<f64, X>,
309        X: num_traits::real::Real + nalgebra::base::Scalar,
310    {
311        fn system(&self, t: f64, x: &SVector<X, N>, dx: &mut SVector<X, N>) {
312            let n = x.len();
313            let f = X::from(n - 1).unwrap().recip();
314            for i in 0..n {
315                let vel = self.vel.eval(t, x[i]);
316                let int = self.int.eval(t, x[i], x.iter().cloned());
317                let tot = vel + int;
318                let dens = if tot.is_sign_positive() {
319                    if i < n - 1 {
320                        f / (x[i + 1] - x[i])
321                    } else {
322                        X::zero()
323                    }
324                } else {
325                    if i > 0 {
326                        f / (x[i] - x[i - 1])
327                    } else {
328                        X::zero()
329                    }
330                };
331                let m = self.mob.eval(t, dens);
332                dx[i] = tot * m;
333            }
334        }
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use super::*;
341
342    #[test]
343    fn zero_velocity() {
344        assert_eq!(ZeroVelocity.eval(0.0, 0.0), 0.0);
345    }
346
347    #[test]
348    fn zero_interaction_potential() {
349        let p = [1., 2., 3.];
350        assert_eq!(ZeroInteraction.eval(0.0, 0.0, p), 0.0);
351    }
352
353    #[test]
354    fn velocity() {
355        let vel = Velocity(|t: f64, x: f64| t + x);
356        assert_eq!(vel.eval(2., 3.), 5.);
357    }
358
359    #[test]
360    fn sampled_interaction() {
361        let int = SampledInteraction(|_t: f64, x: f64| x);
362        let p = [0., 1., 2., 3.];
363        assert_eq!(int.eval(0., 0., p), (0. + 1. + 2. + 3.) / (4. - 1.));
364    }
365
366    #[test]
367    fn integrated_interaction() {
368        let int = IntegratedInteraction(|_t: f64, x: f64| x * x / 2.);
369        let p = [0., 1., 2., 3.];
370        assert_eq!(int.eval(0., 0., p), (3. * 3. / 2. - 0.) / 3.);
371    }
372}