use derive_builder::Builder;
use ode_solvers::*;
use crate::bathymetry::BathymetryData;
use crate::bathymetry::DEFAULT_BATHYMETRY;
use crate::current::CurrentData;
use crate::current::DEFAULT_CURRENT;
use crate::datatype::Point;
use crate::error::Error;
use crate::error::Result;
const G: f64 = 9.8;
pub(crate) type State = Vector4<f64>;
pub(crate) type Time = f64;
#[derive(Builder)]
pub(crate) struct WaveRayPath<'a> {
#[builder(default = "&DEFAULT_BATHYMETRY")]
bathymetry_data: &'a dyn BathymetryData,
#[builder(default = "&DEFAULT_CURRENT")]
current_data: &'a dyn CurrentData,
}
#[allow(dead_code)]
impl<'a> WaveRayPath<'a> {
pub(crate) fn new(bathymetry_data: &'a dyn BathymetryData, current_data: &'a dyn CurrentData) -> Self {
WaveRayPath {
bathymetry_data,
current_data,
}
}
pub(crate) fn builder() -> WaveRayPathBuilder<'a> {
WaveRayPathBuilder::default()
}
pub(crate) fn odes(&self, x: &f64, y: &f64, kx: &f64, ky: &f64) -> Result<(f64, f64, f64, f64)> {
let point = crate::Point::new(*x, *y);
let (h, dh) = self
.bathymetry_data
.depth_and_gradient(&Point::new(*x as f32, *y as f32))?;
let h = h as f64;
let dhdx = *dh.dx() as f64;
let dhdy = *dh.dy() as f64;
let (current, (du, dv)) = self.current_data.current_and_gradient(&point)?;
let k = (kx * kx + ky * ky).sqrt();
let theta = ky.atan2(*kx);
let cg = self.group_velocity(&k, &h)?;
let cgx = cg * theta.cos() + current.u();
let cgy = cg * theta.sin() + current.v();
let dxdt = cgx;
let dydt = cgy;
let (dkxdt_bathy, dkydt_bathy) = self.dkdt_bathy(&k, &h, &dhdx, &dhdy);
let dkxdt = dkxdt_bathy - kx * du.dx() - ky * dv.dx();
let dkydt = dkydt_bathy - kx * du.dy() - ky * dv.dy();
Ok((dxdt, dydt, dkxdt, dkydt))
}
pub(crate) fn group_velocity(&self, k: &f64, h: &f64) -> Result<f64> {
if *h <= 0.0 {
return Ok(f64::NAN);
}
if *k <= 0.0 {
return Err(Error::ArgumentOutOfBounds);
}
let cg = (G / 2.0)
* (((k * h).tanh() + (k * h) / (k * h).cosh().powi(2))
/ (k * G * (k * h).tanh()).sqrt());
Ok(cg)
}
fn dkdt_bathy(&self, k: &f64, h: &f64, dhdx: &f64, dhdy: &f64) -> (f64, f64) {
let dkxdt_bathy = (-0.5) * k * 1.0 / (k * h).sinh() * 1.0 / (k * h).cosh()
* (G * k * (k * h).tanh()).sqrt()
* dhdx;
let dkydt_bathy = (-0.5) * k * 1.0 / (k * h).sinh() * 1.0 / (k * h).cosh()
* (G * k * (k * h).tanh()).sqrt()
* dhdy;
(dkxdt_bathy, dkydt_bathy)
}
}
impl<'a> ode_solvers::System<Time, State> for WaveRayPath<'a> {
fn system(&self, _t: Time, s: &State, ds: &mut State) {
let (dxdt, dydt, dkxdt, dkydt) = match self.odes(&s[0], &s[1], &s[2], &s[3]) {
Err(_) => {
(f64::NAN, f64::NAN, f64::NAN, f64::NAN)
}
Ok(v) => v,
};
ds[0] = dxdt;
ds[1] = dydt;
ds[2] = dkxdt;
ds[3] = dkydt;
}
fn solout(&mut self, _x: Time, y: &State, dy: &State) -> bool {
if (dy[0].is_nan() && dy[1].is_nan() && dy[2].is_nan() && dy[3].is_nan())
|| (y[0].is_nan() && y[1].is_nan() && y[2].is_nan() && y[3].is_nan())
{
true
} else {
false
}
}
}
#[cfg(test)]
mod test_constant_bathymetry {
use crate::current::ConstantCurrent;
use crate::wave_ray_path::{State, WaveRayPath};
use crate::{bathymetry::ArrayDepth, bathymetry::BathymetryData, bathymetry::ConstantDepth};
use ode_solvers::*;
fn run_check_ode_solvers(
depth_data: &dyn BathymetryData,
check_axis: [(f64, f64, f64, f64); 4],
) {
for (kx, ky, xf, yf) in check_axis {
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(depth_data, ¤t);
let y0 = State::new(0.0, 0.0, kx, ky);
let mut stepper = Rk4::new(system, 0.0, y0, 1.0, 1.0);
if stepper.integrate().is_ok() {
let last_state = stepper.y_out().last().unwrap();
assert!(
(last_state.x - xf).abs() < f64::EPSILON && (last_state.y - yf).abs() < f64::EPSILON,
"expected xf: {}, actual: {} \nexpected yf: {}, actual: {}",
xf,
last_state.x,
yf,
last_state.y
);
} else {
panic!("Error during ode_solvers integration")
}
}
}
#[test]
fn test_group_velocity() {
let depth = ConstantDepth::new(1000.0);
let current = ConstantCurrent::new(0.0, 0.0);
let wave_ray_path = WaveRayPath::new(&depth, ¤t);
let results = [
(1.0, 1.565247584249853),
(3.0, 0.9036961141150639),
(5.0, 0.7),
(10.0, 0.4949747468305833),
];
for (k, ans) in results {
assert!(
(wave_ray_path.group_velocity(&k, &1000.0).unwrap() - ans).abs() < 1.0e-4,
"k: {}, ans: {}",
k,
ans
);
}
}
#[test]
fn test_negative_k() {
let depth = ConstantDepth::new(1000.0);
let current = ConstantCurrent::new(0.0, 0.0);
let wave_ray_path = WaveRayPath::new(&depth, ¤t);
assert!(wave_ray_path.group_velocity(&-1.0, &1000.0).is_err());
assert!(wave_ray_path.group_velocity(&-12.0, &1000.0).is_err())
}
#[test]
fn test_odes() {
let results = [
(1.0, 0.0, 1.565247584249853, 0.0),
(0.0, 1.0, 0.0, 1.565247584249853),
(-1.0, 0.0, -1.565247584249853, 0.0),
(0.0, -1.0, 0.0, -1.565247584249853),
];
let data: &dyn BathymetryData = &ConstantDepth::new(1000.0);
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(data, ¤t);
for (kx, ky, ans_dxdt, ans_dydt) in results {
let (dxdt, dydt, _, _) = system.odes(&0.0, &0.0, &kx, &ky).unwrap();
assert!(
(ans_dxdt - dxdt).abs() < 1.0e-4 && (ans_dydt - dydt).abs() < 1.0e-4,
"ans_dxdt: {}, ans_dydt: {}, dxdt: {}, dydt: {}, kx: {}, ky: {}",
ans_dxdt,
ans_dydt,
dxdt,
dydt,
kx,
ky
);
}
}
#[test]
fn test_zero_k() {
let data: &dyn BathymetryData = &ConstantDepth::new(1000.0);
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(data, ¤t);
let y0 = State::new(0.0, 0.0, 0.0, 0.0);
let t0 = 0.0;
let tf = 10.0;
let step_size = 1.0;
let mut stepper = Rk4::new(system, t0, y0, tf, step_size);
let _ = stepper.integrate();
assert!(stepper.y_out().last().unwrap().x.is_nan());
assert!(stepper.y_out().last().unwrap().y.is_nan());
assert!(stepper.y_out().last().unwrap().z.is_nan());
assert!(stepper.y_out().last().unwrap().w.is_nan());
}
#[test]
fn test_zero_h() {
let data: &dyn BathymetryData = &ConstantDepth::new(0.0);
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(data, ¤t);
let y0 = State::new(0.0, 0.0, 1.0, 1.0);
let t0 = 0.0;
let tf = 10.0;
let step_size = 1.0;
let mut stepper = Rk4::new(system, t0, y0, tf, step_size);
let _ = stepper.integrate();
assert!(stepper.y_out().last().unwrap().x.is_nan());
assert!(stepper.y_out().last().unwrap().y.is_nan());
assert!(stepper.y_out().last().unwrap().z.is_nan());
assert!(stepper.y_out().last().unwrap().w.is_nan());
}
#[test]
fn test_axis() {
let data: &dyn BathymetryData = &ConstantDepth::new(1000.0);
let check_axis = [
(0.0, 1.0, 0.0, (9.8_f64).sqrt() / 2.0),
(1.0, 0.0, (9.8_f64).sqrt() / 2.0, 0.0),
(0.0, -1.0, 0.0, -(9.8_f64).sqrt() / 2.0),
(-1.0, 0.0, -(9.8_f64).sqrt() / 2.0, 0.0),
];
run_check_ode_solvers(data, check_axis)
}
#[test]
fn test_array_as_parameter() {
let data: &dyn BathymetryData = &ArrayDepth::new(vec![
vec![1000.0, 1000.0, 1000.0],
vec![1000.0, 1000.0, 1000.0],
vec![1000.0, 1000.0, 1000.0],
]);
let check_axis = [
(0.0, 1.0, 0.0, (9.8_f64).sqrt() / 2.0),
(1.0, 0.0, (9.8_f64).sqrt() / 2.0, 0.0),
(0.0, -1.0, 0.0, -(9.8_f64).sqrt() / 2.0),
(-1.0, 0.0, -(9.8_f64).sqrt() / 2.0, 0.0),
];
run_check_ode_solvers(data, check_axis)
}
#[test]
fn test_x_nan() {
let data: &dyn BathymetryData = &ConstantDepth::new(1000.0);
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(data, ¤t);
let nan = f64::NAN;
let y0 = State::new(nan, 0.0, 1.0, 0.0);
let t0 = 0.0;
let tf = 1.0;
let step_size = 1.0;
let mut stepper = Rk4::new(system, t0, y0, tf, step_size);
let _ = stepper.integrate();
assert!(stepper.y_out().last().unwrap().x.is_nan());
}
#[test]
fn test_y_nan() {
let data: &dyn BathymetryData = &ConstantDepth::new(1000.0);
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(data, ¤t);
let nan = f64::NAN;
let y0 = State::new(0.0, nan, 1.0, 0.0);
let t0 = 0.0;
let tf = 1.0;
let step_size = 1.0;
let mut stepper = Rk4::new(system, t0, y0, tf, step_size);
let _ = stepper.integrate();
assert!(stepper.y_out().last().unwrap().y.is_nan());
}
#[test]
fn test_kx_nan() {
let data: &dyn BathymetryData = &ConstantDepth::new(1000.0);
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(data, ¤t);
let nan = f64::NAN;
let y0 = State::new(0.0, 0.0, nan, 0.0);
let t0 = 0.0;
let tf = 1.0;
let step_size = 1.0;
let mut stepper = Rk4::new(system, t0, y0, tf, step_size);
let _ = stepper.integrate();
assert!(stepper.y_out().last().unwrap().x.is_nan());
assert!(stepper.y_out().last().unwrap().y.is_nan());
}
#[test]
fn test_ky_nan() {
let data: &dyn BathymetryData = &ConstantDepth::new(1000.0);
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(data, ¤t);
let nan = f64::NAN;
let y0 = State::new(0.0, 0.0, 0.0, nan);
let t0 = 0.0;
let tf = 1.0;
let step_size = 1.0;
let mut stepper = Rk4::new(system, t0, y0, tf, step_size);
let _ = stepper.integrate();
assert!(stepper.y_out().last().unwrap().x.is_nan());
assert!(stepper.y_out().last().unwrap().y.is_nan());
}
#[test]
fn test_shallow() {
let data: &dyn BathymetryData = &ConstantDepth::new(0.1);
let check_axis = [
(
0.0,
1.0,
0.00000000000000006031543168844801,
0.9850257515953494,
), (1.0, 0.0, 0.9850257515953494, 0.0),
(
0.0,
-1.0,
0.00000000000000006031543168844801,
-0.9850257515953494,
),
(
-1.0,
0.0,
-0.9850257515953494,
0.00000000000000012063086337689602,
),
];
run_check_ode_solvers(data, check_axis)
}
#[test]
fn out_of_range_give_nan() {
let data: &dyn BathymetryData =
&ArrayDepth::new(vec![vec![1000.0, 1000.0], vec![1000.0, 1000.0]]);
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(data, ¤t);
let y0 = State::new(0.0, 0.0, 0.0, 1.0);
let t0 = 0.0;
let tf = 10.0;
let step_size = 1.0;
let mut stepper = Rk4::new(system, t0, y0, tf, step_size);
let _ = stepper.integrate();
let last_step = stepper.y_out().last().unwrap();
assert!(last_step.x.is_nan() && last_step.y.is_nan());
}
#[test]
fn test_dk_deep() {
let k_mag = 1000.0;
let h = 1000.0;
let dhdx = 0.2;
let dhdy = 0.2;
let depth = ConstantDepth::new(1000.0);
let current = ConstantCurrent::new(0.0, 0.0);
let wave_ray_path = WaveRayPath::new(&depth, ¤t);
let ans = wave_ray_path.dkdt_bathy(&k_mag, &h, &dhdx, &dhdy);
assert!(
(ans.0 - 0.0).abs() < f64::EPSILON,
"Expected 0, got {}",
ans.0
);
assert!(
(ans.1 - 0.0).abs() < f64::EPSILON,
"Expected 0, got {}",
ans.1
)
}
#[test]
fn test_solout() {
let data: &dyn BathymetryData =
&ArrayDepth::new(vec![vec![1000.0, 1000.0], vec![1000.0, 1000.0]]);
let current = ConstantCurrent::new(0.0, 0.0);
let system = WaveRayPath::new(data, ¤t);
let y0 = State::new(0.0, 0.0, 0.0, 1.0);
let t0 = 0.0;
let tf = 10.0;
let step_size = 1.0;
let mut stepper = Rk4::new(system, t0, y0, tf, step_size);
let _ = stepper.integrate();
assert_eq!(*(&stepper.results().get().0.len()), 3);
let last_step = stepper.y_out().last().unwrap();
assert!(last_step.x.is_nan());
assert!(last_step.y.is_nan());
assert!(last_step.z.is_nan());
assert!(last_step.w.is_nan());
}
}
#[cfg(test)]
mod test_current {
use crate::{
bathymetry::{BathymetryData, ConstantDepth},
current::{ConstantCurrent, CurrentData},
wave_ray_path::WaveRayPath,
};
#[test]
fn test_wave_ray_path_builder() {
let bd = ConstantDepth::new(1000.0);
let cd = ConstantCurrent::new(0.0, 0.0);
let wave = WaveRayPath::builder()
.bathymetry_data(&bd)
.current_data(&cd)
.build()
.unwrap();
let wave2 = WaveRayPath::builder().bathymetry_data(&bd).build().unwrap();
let results = [
(1.0, 0.0, 1.565247584249853, 0.0),
(0.0, 1.0, 0.0, 1.565247584249853),
(-1.0, 0.0, -1.565247584249853, 0.0),
(0.0, -1.0, 0.0, -1.565247584249853),
];
for (kx, ky, ans_dxdt, ans_dydt) in results {
let (dxdt, dydt, _, _) = wave.odes(&0.0, &0.0, &kx, &ky).unwrap();
assert!(
(ans_dxdt - dxdt).abs() < 1.0e-4 && (ans_dydt - dydt).abs() < 1.0e-4,
"ans_dxdt: {}, ans_dydt: {}, dxdt: {}, dydt: {}, kx: {}, ky: {}",
ans_dxdt,
ans_dydt,
dxdt,
dydt,
kx,
ky
);
}
for (kx, ky, ans_dxdt, ans_dydt) in results {
let (dxdt, dydt, _, _) = wave2.odes(&0.0, &0.0, &kx, &ky).unwrap();
assert!(
(ans_dxdt - dxdt).abs() < 1.0e-4 && (ans_dydt - dydt).abs() < 1.0e-4,
"ans_dxdt: {}, ans_dydt: {}, dxdt: {}, dydt: {}, kx: {}, ky: {}",
ans_dxdt,
ans_dydt,
dxdt,
dydt,
kx,
ky
);
}
}
#[test]
fn test_constant_depth_current() {
let results = [
(1.0, 0.0, 1.565247584249853 + 1.0, 0.0), (1.0, 0.0, 1.565247584249853 - 1.0, 0.0), (1.0, 0.0, 1.565247584249853, 0.0 + 1.0), (1.0, 0.0, 1.565247584249853, 0.0 - 1.0), (1.0, 0.0, 1.565247584249853 + 1.0, 0.0 + 1.0), (1.0, 0.0, 1.565247584249853 - 1.0, 0.0 - 1.0), ];
let bathy_data: &dyn BathymetryData = &ConstantDepth::new(1000.0);
let current_data_1: &dyn CurrentData = &ConstantCurrent::new(1.0, 0.0);
let current_data_2: &dyn CurrentData = &ConstantCurrent::new(-1.0, 0.0);
let current_data_3: &dyn CurrentData = &ConstantCurrent::new(0.0, 1.0);
let current_data_4: &dyn CurrentData = &ConstantCurrent::new(0.0, -1.0);
let current_data_5: &dyn CurrentData = &ConstantCurrent::new(1.0, 1.0);
let current_data_6: &dyn CurrentData = &ConstantCurrent::new(-1.0, -1.0);
for (i, (kx, ky, ans_dxdt, ans_dydt)) in results.iter().enumerate() {
let system = match i {
0 => WaveRayPath::builder()
.bathymetry_data(bathy_data)
.current_data(current_data_1)
.build()
.unwrap(),
1 => WaveRayPath::builder()
.bathymetry_data(bathy_data)
.current_data(current_data_2)
.build()
.unwrap(),
2 => WaveRayPath::builder()
.bathymetry_data(bathy_data)
.current_data(current_data_3)
.build()
.unwrap(),
3 => WaveRayPath::builder()
.bathymetry_data(bathy_data)
.current_data(current_data_4)
.build()
.unwrap(),
4 => WaveRayPath::builder()
.bathymetry_data(bathy_data)
.current_data(current_data_5)
.build()
.unwrap(),
5 => WaveRayPath::builder()
.bathymetry_data(bathy_data)
.current_data(current_data_6)
.build()
.unwrap(),
_ => panic!("Index out of range"),
};
let (dxdt, dydt, _, _) = system.odes(&0.0, &0.0, &kx, &ky).unwrap();
assert!(
(ans_dxdt - dxdt).abs() < f64::EPSILON && (ans_dydt - dydt).abs() < f64::EPSILON,
"ans_dxdt: {}, ans_dydt: {}, dxdt: {}, dydt: {}, kx: {}, ky: {}",
ans_dxdt,
ans_dydt,
dxdt,
dydt,
kx,
ky
);
}
}
}