use std::f64::consts::PI;
use serde::{Deserialize, Serialize};
use tsify::Tsify;
use wasm_bindgen::prelude::*;
#[derive(Debug, Clone, Copy, Tsify, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct ResponseAccAnalyzerParams {
pub natural_period_ms: u32,
pub dt_ms: u32,
pub damping_h: f64,
pub beta: f64,
pub init_x: f64,
pub init_v: f64,
pub init_a: f64,
pub init_xg: f64,
}
#[derive(Debug, Copy, Clone)]
#[wasm_bindgen]
pub struct ResponseAccAnalyzer {
dt: f64,
hardness: f64,
mass: f64,
damping_c: f64,
beta: f64,
init_x: f64,
init_v: f64,
init_a: f64,
init_xg: f64,
}
#[wasm_bindgen]
impl ResponseAccAnalyzer {
#[wasm_bindgen]
pub fn from_params(params: ResponseAccAnalyzerParams) -> Self {
let mass = 100.;
let hardness = Self::calc_hardness(mass, params.natural_period_ms);
let damping_c = Self::calc_damping_c(params.damping_h, mass, hardness);
Self {
dt: params.dt_ms as f64 / 1000.,
hardness,
mass,
damping_c,
beta: params.beta,
init_x: params.init_x,
init_v: params.init_v,
init_a: params.init_a,
init_xg: params.init_xg,
}
}
fn calc_hardness(mass: f64, natural_period_ms: u32) -> f64 {
4. * PI.powf(2.) * mass / (natural_period_ms as f64 / 1000.).powf(2.)
}
fn calc_damping_c(damping_h: f64, mass: f64, hardness: f64) -> f64 {
damping_h * 2. * (mass * hardness).sqrt()
}
pub fn set_mass(&mut self, mass: f64, natural_period_ms: u32, damping_h: f64) -> Self {
self.mass = mass;
self.hardness = Self::calc_hardness(mass, natural_period_ms);
self.damping_c = Self::calc_damping_c(damping_h, mass, self.hardness);
*self
}
fn a_1(&self, xg: f64, a: f64, v: f64, x: f64) -> f64 {
let p_1 = -(xg * self.mass);
(p_1 - self.damping_c * (v + self.dt / 2. * a) - self.hardness * (x + self.dt * v + (1. / 2. - self.beta) * self.dt.powf(2.) * a)) / (self.mass + self.dt * self.damping_c / 2. + self.beta * self.dt.powf(2.) * self.hardness)
}
fn v_1(&self, a: f64, a_1: f64, v: f64) -> f64 {
v + (a_1 + a) * self.dt / 2.
}
fn x_1(&self, a: f64, a_1: f64, v: f64, x: f64) -> f64 {
x + v * self.dt + (1. / 2. - self.beta) * a * self.dt.powf(2.) + self.beta * a_1 * self.dt.powf(2.)
}
fn abs_response_acc(a: f64, xg: f64) -> f64 {
a + xg
}
#[wasm_bindgen]
pub fn analyze(&self, mut xg: Vec<f64>) -> Vec<f64> {
xg.insert(0, self.init_xg);
let mut result = Vec::<(f64, f64, f64)>::with_capacity(xg.len());
result.push((self.init_x, self.init_v, self.init_a));
(0..xg.len()).for_each(|i| {
let (x, v, a) = result[i];
let xg = xg[i];
let a_1 = self.a_1(xg, a, v, x);
let v_1 = self.v_1(a, a_1, v);
let x_1 = self.x_1(a, a_1, v, x);
result.push((x_1, v_1, a_1));
});
result.into_iter().zip(xg).map(|((_x, _v, a), xg)| {
Self::abs_response_acc(a, xg)
}).collect()
}
}
#[cfg(test)]
mod test {
use close_to::assert_close_to;
use csv::Reader;
use crate::{ResponseAccAnalyzer, ResponseAccAnalyzerParams};
#[test]
fn test() {
let mut csv = Reader::from_path("benches/seismic_acc_waveform.csv").unwrap();
let data = csv.deserialize::<f64>().map(|x| x.unwrap()).collect::<Vec<_>>();
let params = ResponseAccAnalyzerParams {
natural_period_ms: 500,
dt_ms: 10,
damping_h: 0.05,
beta: 0.25,
init_x: 0.0,
init_v: 0.0,
init_a: 0.0,
init_xg: 0.0,
};
let analyzer = ResponseAccAnalyzer::from_params(params);
analyzer.analyze(data);
}
#[test]
fn same_result_change_mass() {
let mut csv = Reader::from_path("benches/seismic_acc_waveform.csv").unwrap();
let data = csv.deserialize::<f64>().map(|x| x.unwrap()).collect::<Vec<_>>();
let params = ResponseAccAnalyzerParams {
natural_period_ms: 500,
dt_ms: 10,
damping_h: 0.05,
beta: 0.25,
init_x: 0.0,
init_v: 0.0,
init_a: 0.0,
init_xg: 0.0,
};
let mut analyzer = ResponseAccAnalyzer::from_params(params);
let result1 = analyzer.analyze(data.clone());
let result2 = analyzer.set_mass(10., 500, 0.05).analyze(data);
result1.into_iter().zip(result2).for_each(|(r1, r2)| {
assert_close_to(r1, r2, 5);
});
}
}