use crate::config::CONFIG;
use crate::material::Material;
use crate::nuclide::{get_or_load_nuclide, Nuclide};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Materials {
materials: Vec<Material>,
pub nuclide_data: HashMap<String, Arc<Nuclide>>,
}
impl Materials {
pub fn new() -> Self {
Materials {
materials: Vec::new(),
nuclide_data: HashMap::new(),
}
}
pub fn append(&mut self, material: Material) {
self.materials.push(material);
}
pub fn get(&self, index: usize) -> Option<&Material> {
self.materials.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Material> {
self.materials.get_mut(index)
}
pub fn remove(&mut self, index: usize) -> Material {
self.materials.remove(index)
}
pub fn len(&self) -> usize {
self.materials.len()
}
pub fn is_empty(&self) -> bool {
self.materials.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Material> {
self.materials.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Material> {
self.materials.iter_mut()
}
pub fn read_nuclides_from_json(
&mut self,
nuclide_json_map: &HashMap<String, String>,
) -> Result<(), Box<dyn std::error::Error>> {
use std::collections::{HashMap as StdHashMap, HashSet};
let mut merged: HashMap<String, String> = HashMap::new();
let cfg = CONFIG.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
for mat in &self.materials {
for n in mat.nuclides.keys() {
if let Some(p) = cfg.cross_sections.get(n) {
merged.insert(n.clone(), p.clone());
}
}
}
drop(cfg);
for (k, v) in nuclide_json_map {
merged.insert(k.clone(), v.clone());
}
let source_map: &HashMap<String, String> = &merged;
let mut requests: StdHashMap<String, HashSet<String>> = StdHashMap::new();
for mat in &self.materials {
for nuclide in mat.nuclides.keys() {
let entry = requests.entry(nuclide.clone()).or_insert_with(HashSet::new);
entry.insert(mat.temperature.clone());
}
}
let mut request_keys: Vec<String> = requests.keys().cloned().collect();
request_keys.sort();
for nuclide_name in request_keys {
if let Some(temps) = requests.get(&nuclide_name) {
let arc = get_or_load_nuclide(&nuclide_name, source_map, Some(temps))?;
self.nuclide_data
.insert(nuclide_name.clone(), Arc::clone(&arc));
}
}
for mat in &mut self.materials {
mat.nuclide_data.clear();
for nuclide_name in mat.nuclides.keys() {
if let Some(shared_arc) = self.nuclide_data.get::<str>(nuclide_name) {
mat.nuclide_data
.insert(nuclide_name.clone(), Arc::clone(shared_arc));
}
}
}
Ok(())
}
pub fn read_nuclides_from_json_keyword(
&mut self,
keyword: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let mut keyword_map = HashMap::new();
for mat in &self.materials {
for nuclide_name in mat.nuclides.keys() {
keyword_map.insert(nuclide_name.clone(), keyword.to_string());
}
}
self.read_nuclides_from_json(&keyword_map)
}
pub fn load_nuclear_data_from_input(
&mut self,
dict_data: Option<HashMap<String, String>>,
keyword_data: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(map) = dict_data {
self.read_nuclides_from_json(&map)
} else if let Some(keyword) = keyword_data {
self.read_nuclides_from_json_keyword(&keyword)
} else {
let empty_map = HashMap::new();
self.read_nuclides_from_json(&empty_map)
}
}
pub fn read_nuclides_from_string(
&mut self,
keyword: &str,
) -> Result<(), Box<dyn std::error::Error>> {
self.read_nuclides_from_json_keyword(keyword)
}
pub fn read_nuclides_from_map(
&mut self,
map: &HashMap<String, String>,
) -> Result<(), Box<dyn std::error::Error>> {
self.read_nuclides_from_json(map)
}
pub fn read_nuclides_from_none(
&mut self,
) -> Result<(), Box<dyn std::error::Error>> {
let empty_map = HashMap::new();
self.read_nuclides_from_json(&empty_map)
}
pub fn ensure_nuclides_loaded(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let mut needed: Vec<String> = Vec::new();
for mat in &self.materials {
for nuclide_name in mat.nuclides.keys() {
if !self.nuclide_data.contains_key(nuclide_name) && !needed.contains(nuclide_name) {
needed.push(nuclide_name.clone());
}
}
}
if needed.is_empty() {
return Ok(());
}
let config = CONFIG.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
for nuclide_name in &needed {
let nuclide = get_or_load_nuclide(nuclide_name, &config.cross_sections, None)?;
self.nuclide_data
.insert(nuclide_name.clone(), Arc::clone(&nuclide));
}
for mat in &mut self.materials {
for nuclide_name in mat.nuclides.keys() {
if !mat.nuclide_data.contains_key(nuclide_name) {
if let Some(shared_arc) = self.nuclide_data.get::<str>(nuclide_name) {
mat.nuclide_data
.insert(nuclide_name.clone(), Arc::clone(shared_arc));
}
}
}
}
Ok(())
}
}
impl Default for Materials {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_new_materials() {
let materials = Materials::new();
assert!(materials.is_empty());
assert_eq!(materials.len(), 0);
}
#[test]
fn test_append_material() {
let mut materials = Materials::new();
let material = Material::new();
materials.append(material);
assert_eq!(materials.len(), 1);
}
#[test]
fn test_get_material() {
let mut materials = Materials::new();
let mut material = Material::new();
material.set_density("g/cm3", 10.5).unwrap();
materials.append(material);
let retrieved = materials.get(0);
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().density, Some(10.5));
}
#[test]
fn test_remove_material() {
let mut materials = Materials::new();
let material = Material::new();
materials.append(material);
let _removed = materials.remove(0);
assert!(materials.is_empty());
}
#[test]
fn test_get_mut_material() {
let mut materials = Materials::new();
let material = Material::new();
materials.append(material);
let material = materials.get_mut(0).unwrap();
material.set_density("g/cm3", 10.5).unwrap();
assert_eq!(materials.get(0).unwrap().density, Some(10.5));
}
#[test]
fn test_materials_nuclide_arc_sharing() {
use crate::nuclide::Nuclide;
use std::collections::HashMap;
use std::sync::Arc;
let mut nuclide_json_map = HashMap::new();
nuclide_json_map.insert("Li6".to_string(), "tests/Li6.json".to_string());
let mut materials = super::Materials::new();
for _ in 0..100 {
let mut mat = crate::material::Material::new();
mat.add_nuclide("Li6", 1.0).unwrap();
materials.append(mat);
}
materials
.read_nuclides_from_json(&nuclide_json_map)
.unwrap();
let mut arcs: Vec<Arc<Nuclide>> = Vec::new();
for mat in &materials.materials {
if let Some(arc) = mat.nuclide_data.get("Li6") {
arcs.push(Arc::clone(arc));
}
}
for i in 1..arcs.len() {
assert!(
Arc::ptr_eq(&arcs[0], &arcs[i]),
"Nuclide Arc is not shared!"
);
}
}
#[test]
fn test_union_temperature_loading_subset_then_union() {
crate::nuclide::clear_nuclide_cache();
let mut nuclide_json_map = HashMap::new();
nuclide_json_map.insert("Be9".to_string(), "tests/Be9.json".to_string());
let mut mats = Materials::new();
let mut m1 = crate::material::Material::new();
m1.add_nuclide("Be9", 1.0).unwrap();
m1.set_temperature("294");
mats.append(m1);
mats.read_nuclides_from_json(&nuclide_json_map).unwrap();
let arc1 = mats.nuclide_data.get("Be9").unwrap();
assert_eq!(
arc1.loaded_temperatures,
vec!["294".to_string()],
"Should load only the requested temperature (294) on first union load"
);
let mut m2 = crate::material::Material::new();
m2.add_nuclide("Be9", 1.0).unwrap();
m2.set_temperature("300");
mats.append(m2);
mats.read_nuclides_from_json(&nuclide_json_map).unwrap();
let arc2 = mats.nuclide_data.get("Be9").unwrap();
assert!(arc2.loaded_temperatures.iter().any(|t| t == "300"));
}
#[test]
fn test_union_temperature_loading_both_at_once() {
crate::nuclide::clear_nuclide_cache();
let mut nuclide_json_map = HashMap::new();
nuclide_json_map.insert("Be9".to_string(), "tests/Be9.json".to_string());
let mut mats = Materials::new();
let mut m1 = crate::material::Material::new();
m1.add_nuclide("Be9", 0.5).unwrap();
m1.set_temperature("294");
let mut m2 = crate::material::Material::new();
m2.add_nuclide("Be9", 0.5).unwrap();
m2.set_temperature("300");
mats.append(m1);
mats.append(m2);
mats.read_nuclides_from_json(&nuclide_json_map).unwrap();
let arc = mats.nuclide_data.get("Be9").unwrap();
assert_eq!(
arc.loaded_temperatures,
vec!["294".to_string(), "300".to_string()],
"Union load with both temps requested simultaneously should load both temps"
);
}
}