use crate::material::Material;
use js_sys::{Array, Map, JSON};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct WasmMaterial {
inner: Material,
}
#[derive(Serialize, Deserialize)]
struct MacroscopicXsResult {
energy_grid: Vec<f64>,
cross_sections: HashMap<i32, Vec<f64>>,
}
#[wasm_bindgen]
impl WasmMaterial {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
WasmMaterial {
inner: Material::new(),
}
}
#[wasm_bindgen]
pub fn add_nuclide(&mut self, nuclide: &str, fraction: f64) -> Result<(), JsValue> {
self.inner
.add_nuclide(nuclide, fraction)
.map_err(|e| JsValue::from_str(&e))
}
#[wasm_bindgen]
pub fn add_element(&mut self, element: &str, fraction: f64) -> Result<(), JsValue> {
self.inner
.add_element(element, fraction)
.map_err(|e| JsValue::from_str(&e))
}
#[wasm_bindgen]
pub fn set_density(&mut self, unit: &str, value: f64) -> Result<(), JsValue> {
self.inner
.set_density(unit, value)
.map_err(|e| JsValue::from_str(&e))
}
#[wasm_bindgen]
pub fn set_volume(&mut self, value: f64) -> Result<(), JsValue> {
self.inner
.volume(Some(value))
.map_err(|e| JsValue::from_str(&e))
.map(|_| ())
}
#[wasm_bindgen]
pub fn set_temperature(&mut self, temperature: &str) {
self.inner.set_temperature(temperature);
}
#[wasm_bindgen]
pub fn get_nuclides(&self) -> Array {
let nuclides = self.inner.get_nuclides();
nuclides
.into_iter()
.map(|n| JsValue::from_str(&n))
.collect::<Array>()
}
#[wasm_bindgen]
pub fn get_atoms_per_barn_cm(&self) -> Result<JsValue, JsValue> {
if self.inner.density.is_none() {
return Err(JsValue::from_str(
"Cannot calculate atoms per cc: Material has no density defined",
));
}
if self.inner.nuclides.is_empty() {
return Err(JsValue::from_str(
"Cannot calculate atoms per cc: Material has no nuclides defined",
));
}
let atoms_per_barn_cm = self.inner.get_atoms_per_barn_cm();
let map = Map::new();
for (nuclide, density) in atoms_per_barn_cm {
map.set(&JsValue::from_str(&nuclide), &JsValue::from_f64(density));
}
Ok(map.into())
}
#[wasm_bindgen]
pub fn calculate_macroscopic_xs(
&mut self,
mt_filter: Option<Array>,
by_nuclide: Option<bool>,
) -> Result<JsValue, JsValue> {
if self.inner.density.is_none() {
return Err(JsValue::from_str(
"Cannot calculate macroscopic cross sections: Material has no density defined",
));
}
if self.inner.nuclides.is_empty() {
return Err(JsValue::from_str(
"Cannot calculate macroscopic cross sections: Material has no nuclides defined",
));
}
if let Err(e) = self.ensure_nuclides_loaded() {
return Err(JsValue::from_str(&format!("{}", e)));
}
let mt_vec: Vec<i32> = match mt_filter {
Some(arr) => arr
.iter()
.filter_map(|v| v.as_f64().map(|num| num as i32))
.collect(),
None => vec![1],
};
let by_nuclide = by_nuclide.unwrap_or(false);
let (energy_grid, xs) = self
.inner
.calculate_macroscopic_xs(&mt_vec, by_nuclide);
let data = MacroscopicXsResult {
energy_grid,
cross_sections: xs,
};
let serialized = serde_json::to_string(&data)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(JSON::parse(&serialized)
.map_err(|e| JsValue::from_str(&format!("JSON parse error: {:?}", e)))?)
}
#[wasm_bindgen]
pub fn reaction_mts(&mut self) -> Result<Array, JsValue> {
match self.inner.reaction_mts() {
Ok(mts) => Ok(mts.into_iter().map(JsValue::from).collect::<Array>()),
Err(e) => Err(JsValue::from_str(&format!(
"Failed to get MT numbers: {}",
e
))),
}
}
#[wasm_bindgen]
pub fn mean_free_path_neutron(&mut self, energy: f64) -> Option<f64> {
self.inner.mean_free_path_neutron(energy)
}
#[wasm_bindgen]
pub fn sample_distance_to_collision(&mut self, energy: f64) -> Option<f64> {
use rand::rngs::StdRng;
use rand::Rng;
use rand::SeedableRng;
let mut rng = StdRng::from_entropy();
self.inner.sample_distance_to_collision(energy, &mut rng)
}
#[wasm_bindgen]
pub fn load_nuclide_data(
&mut self,
nuclide_name: &str,
json_content: &str,
) -> Result<(), JsValue> {
self.inner
.load_nuclide_from_json_str(nuclide_name, json_content)
.map_err(|e| JsValue::from_str(&format!("Failed to load nuclide data: {}", e)))
}
#[wasm_bindgen]
pub fn to_string(&self) -> String {
format!("{:?}", self.inner)
}
}
impl WasmMaterial {
pub fn ensure_nuclides_loaded(&mut self) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
}
#[wasm_bindgen]
impl WasmMaterial {
#[wasm_bindgen(js_name = sampleInteractingNuclide)]
pub fn sample_interacting_nuclide_wasm(&self, energy: f64, seed: Option<u64>) -> String {
use rand::rngs::StdRng;
use rand::SeedableRng;
let mut rng = match seed {
Some(s) => StdRng::seed_from_u64(s),
None => StdRng::seed_from_u64(12345),
};
self.inner.sample_interacting_nuclide(energy, &mut rng)
}
}