Skip to main content

roche/
vstream_physics.rs

1use crate::errors::RocheError;
2use crate::x_l1;
3use crate::{Vec3, vel_transform, strinit, stradv, strmnx, rocacc, OrbitalSystem};
4use bulirsch::{self, Integrator};
5use pyo3::prelude::*;
6use numpy::{IntoPyArray, PyArray1};
7
8
9
10///
11/// vstream computes the path of the gas stream in a binary in velocity space.
12/// There are a few different options for the type of stream velocities produced.
13/// vstream works by integrating the equations of motion for the Roche
14/// potential using Burlisch-Stoer integration. Every time the speed
15/// changes by step, it interpolates and stores a new point.
16///
17/// The velocities are inertial frame velocities for comparison with Doppler maps.
18/// 
19/// Arguments:
20/// 
21/// * `q`: mass ratio = M2/M1. Stream flows from star 2 to 1.
22/// * `step`: step between points (units of K1+K2).
23/// * `n_points`: number of points to compute.
24/// * `transform_type`: type of velocity, see !!ref{vtrans.html}{vtrans} for supported types.
25/// 
26/// Returns:
27/// 
28/// * `vx`: array of x velocities.
29/// * `vy`: array of y velocities.
30/// * `rad`: array of radii equivalent to velocities (units of a)
31///
32pub fn vstream(q: f64, step: f64, n_points: usize, transform_type: i32) -> Result<(Vec<f64>, Vec<f64>, Vec<f64>), RocheError> {
33
34    if n_points < 2 {
35        return Err(RocheError::ParameterError(
36            "Need at least 2 points in the stream.".to_string(),
37        ));
38    }
39
40    let mut vx_arr: Vec<f64> = vec![];
41    let mut vy_arr: Vec<f64> = vec![];
42    let mut r_arr: Vec<f64> = vec![];
43    let mut frac: f64;
44    let mut acc: f64;
45    let mut ar_x: f64;
46    let mut ar_y: f64;
47
48    // Initialise stream
49    let rl1: f64 = x_l1(q)?;
50    let (mut r, mut v) = strinit(q)?;
51
52    // Store L1 as first point
53    let (mut tvx, mut tvy) = vel_transform(q, transform_type, rl1, 0.0, 0.0, 0.0)?;
54    vx_arr.push(tvx);
55    vy_arr.push(tvy);
56    let mut lp: usize = 0;
57
58    // Store interpolation between L1 and initial point if step
59    // has been set small enough
60
61    (tvx, tvy) = vel_transform(q, transform_type, r.x, r.y, v.x, v.y)?;
62    let mut delta_velocity: f64 = (tvx - vx_arr[0]).hypot(tvy - vy_arr[0]);
63    if delta_velocity > step {
64        frac = step / delta_velocity;
65        vx_arr.push(vx_arr[0] + (tvx - vx_arr[0]) * frac);
66        vy_arr.push(vy_arr[0] + (tvy - vy_arr[0]) * frac);
67        r_arr.push(r.x.hypot(r.y));
68        lp += 1;
69    }
70
71    let mut delta_t: f64 = 1.0e-3;
72    let smax: f64 = 1.0e-3_f64.min(step / 2.0);
73
74    // set up Bulirsch-Stoer integrator
75    let system = OrbitalSystem { q };
76    let mut integrator = Integrator::default()
77        .with_abs_tol(1.0e-8)
78        .with_rel_tol(1.0e-8)
79        .into_adaptive();
80    // Initialise arrays
81    let mut y = ndarray::array![r.x, r.y, r.z, v.x, v.y, v.z];
82    let mut y_next = ndarray::Array::zeros(y.raw_dim());
83
84    while lp < n_points - 1 {
85
86        integrator
87            .step(&system, delta_t, y.view(), y_next.view_mut())
88            .unwrap();
89        y.assign(&y_next);
90
91        r.set(y[0], y[1], y[2]);
92        v.set(y[3], y[4], y[5]);
93        (tvx, tvy) = vel_transform(q, transform_type, r.x, r.y, v.x, v.y)?;
94        delta_velocity = (tvx - vx_arr[lp]).hypot(tvy - vy_arr[lp]);
95        if delta_velocity > step {
96            frac = step / delta_velocity;
97            vx_arr.push(vx_arr[lp] + (tvx - vx_arr[lp]) * frac);
98            vy_arr.push(vy_arr[lp] + (tvy - vy_arr[lp]) * frac);
99            r_arr.push(r.x.hypot(r.y));
100            lp += 1;
101        }
102        (ar_x, ar_y, _) = rocacc(q, &r, &v);
103        acc = ar_x.hypot(ar_y);
104        delta_t = delta_t.min(smax/acc);
105    }
106
107    Ok((vx_arr, vy_arr, r_arr))
108
109}
110
111pub fn vstream_reg(q: f64, step: f64, n_points: usize, transform_type: i32) -> Result<(Vec<f64>, Vec<f64>), RocheError> {
112
113    const TLOC: f64 = 1.0e-8;
114    const RLOC: f64 = 1.0e-8;
115
116    if n_points < 2 {
117        return Err(RocheError::ParameterError(
118            "Need at least 2 points in the stream.".to_string(),
119        ));
120    }
121
122    let mut rm: Vec3;
123    let mut vm: Vec3;
124    let mut r_end: f64;
125    let mut vx_arr: Vec<f64> = vec![];
126    let mut vy_arr: Vec<f64> = vec![];
127
128    let rl1: f64 = x_l1(q)?;
129
130    let (mut tvx, mut tvy) = vel_transform(q, transform_type, rl1, 0.0, 0.0, 0.0)?;
131    vx_arr.push(tvx);
132    vy_arr.push(tvy);
133
134    let mut np: usize = 1;
135    let mut r_next: f64 = rl1 * (1.0 - step);
136    let mut r_decreasing: bool = true;
137
138    // Initialise stream
139    let (mut r, mut v) = strinit(q)?;
140
141    while np < n_points {
142
143        // Advance one step
144        stradv(q, &mut r, &mut v, r_next, RLOC, 1.0e-3);
145        (tvx, tvy) = vel_transform(q, transform_type, r.x, r.y, v.x, v.y)?;
146        vx_arr.push(tvx);
147        vy_arr.push(tvy);
148        np += 1;
149        r_next = if r_decreasing {r_next - rl1 * step} else {r_next + rl1 * step};
150
151        // Locate and store next turning point
152        rm = r;
153        vm = v;
154        strmnx(q, &mut rm, &mut vm, TLOC)?;
155        r_end = rm.length();
156
157        // Loop over all radii wanted before next turning point
158        while np < n_points && ((r_decreasing && r_next > r_end) || (!r_decreasing && r_next < r_end)) {
159            stradv(q, &mut r, &mut v, r_next, RLOC, 1.0e-3);
160            (tvx, tvy) = vel_transform(q, transform_type, r.x, r.y, v.x, v.y)?;
161            vx_arr.push(tvx);
162            vy_arr.push(tvy);
163            np += 1;
164            r_next = if r_decreasing {r_next - rl1 * step} else {r_next + rl1 * step};
165        }
166
167        // Change direction of search and move it to start at turning point
168        r = rm;
169        v = vm;
170        r_decreasing = !r_decreasing;
171        r_next = if r_decreasing {r_next - rl1 * step} else {r_next + rl1 * step};
172    }
173    
174    Ok((vx_arr, vy_arr))
175}
176
177
178#[pyfunction]
179#[pyo3(name = "vstream")]
180#[pyo3(signature = (q, step=0.01, n_points=60, transform_type=1))]
181pub fn vstream_reg_py(py: Python, q: f64, step: f64, n_points: usize, transform_type: i32) -> PyResult<(Py<PyArray1<f64>>, Py<PyArray1<f64>>)> {
182    let (x_arr, y_arr) = vstream_reg(q, step, n_points, transform_type)?;
183    Ok((x_arr.into_pyarray(py).unbind(), y_arr.into_pyarray(py).unbind()))
184}