ground-motion-lib 0.1.3

A performant, modular Rust library for computing and analyzing ground motion predictions using GMPE models.
Documentation
use std::error::Error;

use ground_motion_lib::auxilary::{approx_equal, round_to_places};
use ground_motion_lib::configs::get_mf2013_lib_configs;
use ground_motion_lib::gmm::{Earthquake, GmpePoint, GmpePointKind, Magnitude, Vs30Point};
use ground_motion_lib::readers::read_vs30_points;
use ground_motion_lib::vectorized::calc_gmpe_vec;

const EPSILON: f64 = 1e-6;
const CSV_DELIMETER: u8 = b'\t';

const EQ6: Earthquake = Earthquake {
    lon: 143.04,
    lat: 51.92,
    depth: 13.,
    magnitude: 6.,
    magnitude_kind: Magnitude::Mw,
};
const EQ7: Earthquake = Earthquake {
    lon: 143.04,
    lat: 51.92,
    depth: 13.,
    magnitude: 7.,
    magnitude_kind: Magnitude::Mw,
};
const EQ85: Earthquake = Earthquake {
    lon: 143.04,
    lat: 51.92,
    depth: 13.,
    magnitude: 8.5,
    magnitude_kind: Magnitude::Mw,
};

const GRID_EPICENTER: Vs30Point = Vs30Point {
    lon: 143.04,
    lat: 51.92,
    vs30: 350.,
    dl: None,
    xvf: None,
};

const GRID_SIZE: usize = 17;
const ROUND_PLACES: u32 = 2;
const VS_30_FILE: &str = "tests/data/testvs30.txt";
const VS_30_WITH_DL_FILE: &str = "tests/data/testvs30dl.txt";

fn sum_and_round_values(points: &[GmpePoint]) -> f64 {
    let mut sum = 0.;
    for point in points {
        sum += point.value;
    }
    round_to_places(sum, ROUND_PLACES)
}

#[test]
fn test_mf2013_const_dl() -> Result<(), Box<dyn Error>> {
    let vs_30_grid = read_vs30_points(VS_30_FILE, CSV_DELIMETER)?;
    let configs = get_mf2013_lib_configs();

    let config_ref = configs.get("config_mf2013_crustal_pga").unwrap();
    let epicenter_pga = GRID_EPICENTER.get_gm(config_ref, &EQ7).value;
    assert!(approx_equal(
        round_to_places(epicenter_pga, ROUND_PLACES),
        53.28,
        EPSILON
    ));

    let gmpe_points = calc_gmpe_vec(&vs_30_grid, config_ref, &EQ6);
    assert!(gmpe_points.len() == GRID_SIZE);
    assert!(matches!(gmpe_points[0].kind, GmpePointKind::Pga));
    let value = sum_and_round_values(&gmpe_points);
    assert!(approx_equal(value, 3.4, EPSILON));

    let config_ref = configs.get("config_mf2013_crustal_pgv").unwrap();
    let gmpe_points = calc_gmpe_vec(&vs_30_grid, config_ref, &EQ6);
    assert!(gmpe_points.len() == GRID_SIZE);
    assert!(matches!(gmpe_points[0].kind, GmpePointKind::Pgv));
    let value = sum_and_round_values(&gmpe_points);
    assert!(approx_equal(value, 4.63, EPSILON));

    let config_ref = configs.get("config_mf2013_crustal_psa_10").unwrap();
    let gmpe_points = calc_gmpe_vec(&vs_30_grid, config_ref, &EQ6);
    assert!(gmpe_points.len() == GRID_SIZE);
    assert!(matches!(gmpe_points[0].kind, GmpePointKind::Psa));
    let value = sum_and_round_values(&gmpe_points);
    assert!(approx_equal(value, 5.49, EPSILON));

    let config_ref = configs.get("config_mf2013_crustal_psa_30").unwrap();
    let gmpe_points = calc_gmpe_vec(&vs_30_grid, config_ref, &EQ6);
    assert!(gmpe_points.len() == GRID_SIZE);
    assert!(matches!(gmpe_points[0].kind, GmpePointKind::Psa));
    let value = sum_and_round_values(&gmpe_points);
    assert!(approx_equal(value, 1.42, EPSILON));

    let config_ref = configs.get("config_mf2013_crustal_pga").unwrap();
    let gmpe_points = calc_gmpe_vec(&vs_30_grid, config_ref, &EQ85);
    assert!(gmpe_points.len() == GRID_SIZE);
    assert!(matches!(gmpe_points[0].kind, GmpePointKind::Pga));
    let value = sum_and_round_values(&gmpe_points);
    assert!(approx_equal(value, 37.8, EPSILON));

    let config_ref = configs.get("config_mf2013_intraplate_pga_asid").unwrap();
    let gmpe_points = calc_gmpe_vec(&vs_30_grid, config_ref, &EQ85);
    assert!(gmpe_points.len() == GRID_SIZE);
    assert!(matches!(gmpe_points[0].kind, GmpePointKind::Pga));
    let value = sum_and_round_values(&gmpe_points);
    println!("{value}");
    assert!(approx_equal(value, 74.86, EPSILON));

    Ok(())
}

#[test]
fn test_mf2013_dl_on_grid() -> Result<(), Box<dyn Error>> {
    let vs_30_grid = read_vs30_points(VS_30_WITH_DL_FILE, CSV_DELIMETER)?;
    let configs = get_mf2013_lib_configs();

    let config_ref = configs.get("config_mf2013_crustal_pga").unwrap();
    let gmpe_points = calc_gmpe_vec(&vs_30_grid, config_ref, &EQ6);
    assert!(gmpe_points.len() == GRID_SIZE);
    assert!(matches!(gmpe_points[0].kind, GmpePointKind::Pga));
    let value = sum_and_round_values(&gmpe_points);
    assert!(approx_equal(value, 506.55, EPSILON));

    let config_ref = configs.get("config_mf2013_crustal_pgv").unwrap();
    let gmpe_points = calc_gmpe_vec(&vs_30_grid, config_ref, &EQ85);
    assert!(gmpe_points.len() == GRID_SIZE);
    assert!(matches!(gmpe_points[0].kind, GmpePointKind::Pgv));
    let value = sum_and_round_values(&gmpe_points);
    assert!(approx_equal(value, 2989.47, EPSILON));

    let config_ref = configs.get("config_mf2013_crustal_psa_03").unwrap();
    let gmpe_points = calc_gmpe_vec(&vs_30_grid, config_ref, &EQ85);
    assert!(gmpe_points.len() == GRID_SIZE);
    assert!(matches!(gmpe_points[0].kind, GmpePointKind::Psa));
    let value = sum_and_round_values(&gmpe_points);
    println!("{value}");
    assert!(approx_equal(value, 4177.5, EPSILON));

    Ok(())
}