Skip to main content

roche/
stream_physics.rs

1use bulirsch::{self, Integrator};
2use crate::Vec3;
3use crate::errors::RocheError;
4use crate::x_l1;
5use pyo3::prelude::*;
6
7/// 
8/// strinit sets a particle just inside the L1 point with the 
9/// correct velocity as given in Lubow and Shu.
10///
11/// Arguments:
12/// 
13/// * `q`: mass ratio = M2/M1
14/// 
15/// Returns:
16/// 
17/// * start position
18/// * start velocity
19///
20#[pyfunction]
21pub fn strinit(q: f64) -> Result<(Vec3, Vec3), RocheError> {
22    
23    const SMALL: f64 = 1.0e-5;
24    let rl1: f64 = x_l1(q)?;
25    let mu: f64 = q/(1.0+q);
26    let a: f64 = (1.0-mu)/rl1.powi(3)+mu/(1.0-rl1).powi(3);
27    let lambda1: f64 = (((a-2.0) + (a*(9.0*a-8.0)).sqrt())/2.0).sqrt();
28    let m1: f64 = (lambda1*lambda1-2.0*a-1.0)/2.0/lambda1;
29
30    let r: Vec3 = Vec3::new(rl1-SMALL, -m1*SMALL, 0.0);
31    let v: Vec3 = Vec3::new(-lambda1*SMALL, -lambda1*m1*SMALL, 0.0);
32
33    Ok((r, v))
34
35}
36
37
38/// 
39/// stradv advances a particle of given position and velocity until
40/// it reaches a specified radius. It then returns with updated position and
41/// velocity. It is up to the user not to request a value that cannot be reached.
42///
43/// Arguments:
44/// 
45/// * `q`:    mass ratio = M2/M1
46/// * `r`:    Initial and final position
47/// * `v`:    Initial and final velocity
48/// * `rad`:  Radius to aim for
49/// * `acc`:  Accuracy with which to place output point at rad.
50/// * `smax`: Largest time step allowed. It is possible that the
51/// routine could take such a large step that it misses
52/// the point when the stream is inside the requested
53/// radius. This allows one to control this. Typical
54/// value = 1.e-3.
55///
56/// Returns:
57/// 
58/// * time step taken
59///
60pub fn stradv(q: f64, r: &mut Vec3, v: &mut Vec3, rad: f64, acc: f64, smax: f64) -> f64 {
61
62    const TMAX: f64 = 10.0;
63    let t_next: f64 = 1.0e-2;
64
65    let mut time: f64 = 0.0;
66
67    // let to: f64;
68    let mut ro = *r;
69    let mut vo = *v;
70    
71    // Store initial radius
72    let rinit: f64 = r.length();
73    let mut rnow: f64 = rinit;
74
75    // set up Bulirsch-Stoer integrator
76    let system = OrbitalSystem{ q: q };
77    let mut integrator = Integrator::default().with_abs_tol(1.0e-8).with_rel_tol(1.0e-8).into_adaptive();
78    // Initialise arrays
79    let mut y = ndarray::array![r.x, r.y, r.z, v.x, v.y, v.z];
80    let mut y_next = ndarray::Array::zeros(y.raw_dim());
81    
82    let mut yo = y.clone();
83    let mut delta_t = t_next.min(smax);
84    // Step until radius crossed
85    while (rinit > rad && rnow > rad) || (rinit < rad && rnow < rad) {
86        ro = *r;
87        vo = *v;
88        yo = y.clone();
89        integrator
90            .step(&system, delta_t, y.view(), y_next.view_mut())
91            .unwrap();
92        y.assign(&y_next);
93        r.set(y[0], y[1], y[2]);
94        v.set(y[3], y[4], y[5]);
95        rnow = r.length();
96        time += delta_t;
97        
98        if time > TMAX {
99            panic!("roche::stradv taken too long without crossing given radius.")
100        }
101    }
102
103    // Now refine by reinitialising and binary chopping until
104    // close enough to requested radius.
105
106    let mut lo: f64 = 0.0;
107    let mut hi: f64 = delta_t;
108    let mut rlo: f64 = ro.length();
109    let mut rhi: f64 = rnow;
110    let to: f64 = time;
111
112    while (rhi-rlo).abs() > acc {
113        delta_t = (lo+hi)/2.0;
114        y = yo.clone();
115        *r = ro;
116        *v = vo;
117        time = to;
118
119        integrator
120            .step(&system, delta_t, y.view(), y_next.view_mut())
121            .unwrap();
122        y.assign(&y_next);
123
124        r.set(y[0], y[1], y[2]);
125        v.set(y[3], y[4], y[5]);
126        rnow = r.length();
127
128        if (rhi > rad && rnow > rad) || (rhi < rad && rnow < rad) {
129            rhi = rnow;
130            hi = delta_t;
131        } else {
132            rlo = rnow;
133            lo = delta_t;
134        }
135    }
136
137    time
138
139}
140
141// wrapper for python library, avoiding mutable references
142
143/// 
144/// stradv advances a particle of given position and velocity until
145/// it reaches a specified radius. It then returns with updated position and
146/// velocity. It is up to the user not to request a value that cannot be reached.
147///
148/// \param q    mass ratio = M2/M1
149/// \param r    Initial position
150/// \param v    Initial velocity
151/// \param rad  Radius to aim for
152/// \param acc  Accuracy with which to place output point at rad.
153/// \param smax Largest time step allowed. It is possible that the
154/// routine could take such a large step that it misses
155/// the point when the stream is inside the requested
156/// radius. This allows one to control this. Typical
157/// value = 1.e-3.
158/// \returns (timestep, new position, new velocity)
159///
160#[pyfunction]
161#[pyo3(name = "stradv")]
162pub fn stradv_wrapper(q: f64, r: &Vec3, v: &Vec3, rad: f64, acc: f64, smax: f64) -> (f64, Vec3, Vec3) {
163    let mut r_mut = *r;
164    let mut v_mut = *v;
165    let timestep = stradv(q, &mut r_mut, &mut v_mut, rad, acc, smax);
166    (timestep, r_mut, v_mut)
167}
168
169///
170/// rocacc calculates and returns the acceleration (in the rotating frame)
171/// in a Roche potential of a particle of given position and velocity.
172///
173/// \param q mass ratio = M2/M1
174/// \param r position, scaled in units of separation.
175/// \param v velocity, scaled in units of separation
176///
177#[pyfunction]
178pub fn rocacc(q: f64, r: &Vec3, v: &Vec3) -> (f64, f64, f64) {
179
180
181    let f1: f64 = 1.0 / (1.0+q);
182    let f2: f64 = f1*q;
183
184    let yzsq: f64 = r.y*r.y + r.z*r.z;
185    let r1sq: f64 = r.x*r.x + yzsq;
186    let r2sq: f64 = (r.x-1.0)*(r.x-1.0) + yzsq;
187    let fm1: f64 = f1/(r1sq*(r1sq.sqrt()));
188    let fm2: f64 = f2/(r2sq*(r2sq.sqrt()));
189    let fm3 = fm1+fm2;
190
191    let x: f64 = -fm3*r.x + fm2 + 2.0*v.y + r.x - f2;
192    let y: f64 = -fm3*r.y       - 2.0*v.x + r.y;
193    let z: f64 = -fm3*r.z;
194    (x, y, z)
195}
196
197
198struct OrbitalSystem {
199    q: f64,
200}
201
202impl bulirsch::System for OrbitalSystem {
203    type Float = f64;
204    
205    fn system(&self, y: bulirsch::ArrayView1<Self::Float>, mut dydt: bulirsch::ArrayViewMut1<Self::Float>) {
206        dydt[[0]] = y[[3]];
207        dydt[[1]] = y[[4]];
208        dydt[[2]] = y[[5]];
209        let r = Vec3::new(y[[0]], y[[1]], y[[2]]);
210        let v = Vec3::new(y[[3]], y[[4]], y[[5]]);
211        (dydt[[3]], dydt[[4]], dydt[[5]]) = rocacc(self.q, &r, &v);
212    }
213}