From dde1c687a2d6558a37bf401ee228826fb776bdee Mon Sep 17 00:00:00 2001
From: Udo Eisenbarth <u.eisenbarth@gsi.de>
Date: Fri, 28 Mar 2025 11:14:29 +0100
Subject: [PATCH 1/7] Fix newline issue while saving OPM files

---
 opossum/src/lightdata/energy_spectrum_builder.rs | 2 +-
 opossum/src/main.rs                              | 2 +-
 opossum/src/nodes/node_group/mod.rs              | 5 ++++-
 opossum/src/nodes/node_group/optic_graph.rs      | 3 ++-
 opossum/src/opm_document.rs                      | 6 +++---
 opossum/src/optic_ref.rs                         | 4 +++-
 opossum/src/spectrum.rs                          | 4 ++--
 7 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/opossum/src/lightdata/energy_spectrum_builder.rs b/opossum/src/lightdata/energy_spectrum_builder.rs
index b8e46f2b..68f392f4 100644
--- a/opossum/src/lightdata/energy_spectrum_builder.rs
+++ b/opossum/src/lightdata/energy_spectrum_builder.rs
@@ -49,4 +49,4 @@ impl Display for EnergyDataBuilder {
             Self::LaserLines(l, r) => write!(f, "LaserLines({:?}, {})", l, r.value),
         }
     }
-}
\ No newline at end of file
+}
diff --git a/opossum/src/main.rs b/opossum/src/main.rs
index a3c2396c..7cdc7176 100644
--- a/opossum/src/main.rs
+++ b/opossum/src/main.rs
@@ -85,7 +85,7 @@ fn create_report_and_data_files(
     write!(
         output,
         "{}",
-        ron::ser::to_string_pretty(&report, ron::ser::PrettyConfig::default()).unwrap()
+        ron::ser::to_string_pretty(&report, ron::ser::PrettyConfig::new().new_line("\n")).unwrap()
     )
     .map_err(|e| OpossumError::Other(format!("writing report file failed: {e}")))?;
     let mut report_path = report_directory.to_path_buf();
diff --git a/opossum/src/nodes/node_group/mod.rs b/opossum/src/nodes/node_group/mod.rs
index eb72e852..765083f6 100644
--- a/opossum/src/nodes/node_group/mod.rs
+++ b/opossum/src/nodes/node_group/mod.rs
@@ -733,7 +733,10 @@ mod test {
         let mut scenery = NodeGroup::default();
         scenery.add_node(Dummy::default()).unwrap();
         let report = scenery.toplevel_report().unwrap();
-        assert!(ron::ser::to_string_pretty(&report, ron::ser::PrettyConfig::default()).is_ok());
+        assert!(
+            ron::ser::to_string_pretty(&report, ron::ser::PrettyConfig::new().new_line("\n"))
+                .is_ok()
+        );
         // How shall we further parse the output?
     }
     #[test]
diff --git a/opossum/src/nodes/node_group/optic_graph.rs b/opossum/src/nodes/node_group/optic_graph.rs
index 772e6e9f..f1018632 100644
--- a/opossum/src/nodes/node_group/optic_graph.rs
+++ b/opossum/src/nodes/node_group/optic_graph.rs
@@ -1648,7 +1648,8 @@ mod test {
             vec!["input_1", "input_2"]
         );
         let serialized =
-            ron::ser::to_string_pretty(&graph, ron::ser::PrettyConfig::default()).unwrap();
+            ron::ser::to_string_pretty(&graph, ron::ser::PrettyConfig::new().new_line("\n"))
+                .unwrap();
         let deserialized: OpticGraph = ron::from_str(&serialized).unwrap();
         assert_eq!(
             deserialized.port_map(&PortType::Input).port_names(),
diff --git a/opossum/src/opm_document.rs b/opossum/src/opm_document.rs
index 3eb361ee..049fa3bb 100644
--- a/opossum/src/opm_document.rs
+++ b/opossum/src/opm_document.rs
@@ -126,9 +126,9 @@ impl OpmDocument {
     ///
     /// This function will return an error if the serialization of the internal structures fail.
     pub fn to_opm_file_string(&self) -> OpmResult<String> {
-        ron::ser::to_string_pretty(&self, ron::ser::PrettyConfig::default()).map_err(|e| {
-            OpossumError::OpticScenery(format!("serialization of OpmDocument failed: {e}"))
-        })
+        ron::ser::to_string_pretty(&self, ron::ser::PrettyConfig::new().new_line("\n")).map_err(
+            |e| OpossumError::OpticScenery(format!("serialization of OpmDocument failed: {e}")),
+        )
     }
     /// Returns the list of analyzers of this [`OpmDocument`].
     #[must_use]
diff --git a/opossum/src/optic_ref.rs b/opossum/src/optic_ref.rs
index 70d89d45..96a6c68d 100644
--- a/opossum/src/optic_ref.rs
+++ b/opossum/src/optic_ref.rs
@@ -224,7 +224,9 @@ mod test {
     #[test]
     fn serialize() {
         let optic_ref = OpticRef::new(Arc::new(Mutex::new(Dummy::default())), None);
-        let _ = ron::ser::to_string_pretty(&optic_ref, ron::ser::PrettyConfig::default()).unwrap();
+        let _ =
+            ron::ser::to_string_pretty(&optic_ref, ron::ser::PrettyConfig::new().new_line("\n"))
+                .unwrap();
     }
     #[test]
     fn deserialize() {
diff --git a/opossum/src/spectrum.rs b/opossum/src/spectrum.rs
index c2cc3231..bbb66adc 100644
--- a/opossum/src/spectrum.rs
+++ b/opossum/src/spectrum.rs
@@ -999,10 +999,10 @@ mod test {
     #[test]
     fn serialize() {
         let s = prep();
-        let s_yaml = ron::ser::to_string_pretty(&s, ron::ser::PrettyConfig::default());
+        let s_yaml = ron::ser::to_string_pretty(&s, ron::ser::PrettyConfig::new().new_line("\n"));
         assert!(s_yaml.is_ok());
         assert_eq!(s_yaml.unwrap(),
-        "(\r\n    data: [\r\n        (1.0, 0.0),\r\n        (1.5, 0.0),\r\n        (2.0, 0.0),\r\n        (2.5, 0.0),\r\n        (3.0, 0.0),\r\n        (3.5, 0.0),\r\n    ],\r\n)".to_string());
+        "(\n    data: [\n        (1.0, 0.0),\n        (1.5, 0.0),\n        (2.0, 0.0),\n        (2.5, 0.0),\n        (3.0, 0.0),\n        (3.5, 0.0),\n    ],\n)".to_string());
     }
     #[test]
     fn deserialize() {
-- 
GitLab


From 5134821ea654e879d0be89bd61ca033fe53ad599 Mon Sep 17 00:00:00 2001
From: Udo Eisenbarth <u.eisenbarth@gsi.de>
Date: Fri, 28 Mar 2025 11:26:21 +0100
Subject: [PATCH 2/7] Update examples using EnergyDataBuilder

---
 .../{opm_file.rs => empty_opm_file.rs}        |   0
 opossum/examples/group_reverse.rs             |   9 +-
 .../examples/inverse_beam_splitter_test.rs    |   9 +-
 opossum/examples/michaelson.rs                |   9 +-
 opossum/examples/nalgebra_matrix_testing.rs   | 212 ------------------
 opossum/examples/reference_test.rs            |   9 +-
 6 files changed, 24 insertions(+), 224 deletions(-)
 rename opossum/examples/{opm_file.rs => empty_opm_file.rs} (100%)
 delete mode 100644 opossum/examples/nalgebra_matrix_testing.rs

diff --git a/opossum/examples/opm_file.rs b/opossum/examples/empty_opm_file.rs
similarity index 100%
rename from opossum/examples/opm_file.rs
rename to opossum/examples/empty_opm_file.rs
diff --git a/opossum/examples/group_reverse.rs b/opossum/examples/group_reverse.rs
index 332cadcb..f6a66ae3 100644
--- a/opossum/examples/group_reverse.rs
+++ b/opossum/examples/group_reverse.rs
@@ -4,18 +4,21 @@ use num::Zero;
 use opossum::{
     analyzers::AnalyzerType,
     error::OpmResult,
+    joule,
     lightdata::{energy_spectrum_builder::EnergyDataBuilder, light_data_builder::LightDataBuilder},
+    nanometer,
     nodes::{Dummy, EnergyMeter, NodeGroup, Source},
     optic_node::OpticNode,
-    spectrum_helper::create_he_ne_spec,
     OpmDocument,
 };
 use uom::si::f64::Length;
 
 fn main() -> OpmResult<()> {
     let mut scenery = NodeGroup::new("Inverse Group test");
-    let light_data_builder =
-        LightDataBuilder::Energy(EnergyDataBuilder::Raw(create_he_ne_spec(1.0)?));
+    let light_data_builder = LightDataBuilder::Energy(EnergyDataBuilder::LaserLines(
+        vec![(nanometer!(633.0), joule!(1.0))],
+        nanometer!(1.0),
+    ));
     let i_s = scenery.add_node(Source::new("Source", light_data_builder))?;
 
     let mut group = NodeGroup::default();
diff --git a/opossum/examples/inverse_beam_splitter_test.rs b/opossum/examples/inverse_beam_splitter_test.rs
index aa609df5..bea16541 100644
--- a/opossum/examples/inverse_beam_splitter_test.rs
+++ b/opossum/examples/inverse_beam_splitter_test.rs
@@ -2,11 +2,12 @@ use num::Zero;
 use opossum::{
     analyzers::AnalyzerType,
     error::OpmResult,
+    joule,
     lightdata::{energy_spectrum_builder::EnergyDataBuilder, light_data_builder::LightDataBuilder},
+    nanometer,
     nodes::{BeamSplitter, EnergyMeter, NodeGroup, Source},
     optic_node::OpticNode,
     ray::SplittingConfig,
-    spectrum_helper::create_he_ne_spec,
     OpmDocument,
 };
 use std::path::Path;
@@ -14,8 +15,10 @@ use uom::si::f64::Length;
 
 fn main() -> OpmResult<()> {
     let mut scenery = NodeGroup::new("inverse beam splitter test");
-    let light_data_builder =
-        LightDataBuilder::Energy(EnergyDataBuilder::Raw(create_he_ne_spec(1.0)?));
+    let light_data_builder = LightDataBuilder::Energy(EnergyDataBuilder::LaserLines(
+        vec![(nanometer!(633.0), joule!(1.0))],
+        nanometer!(1.0),
+    ));
     let i_s = scenery.add_node(Source::new("Source", light_data_builder))?;
     let mut bs = BeamSplitter::new("bs", &SplittingConfig::Ratio(0.6)).unwrap();
     bs.set_inverted(true)?;
diff --git a/opossum/examples/michaelson.rs b/opossum/examples/michaelson.rs
index 9d77f2c8..87018a62 100644
--- a/opossum/examples/michaelson.rs
+++ b/opossum/examples/michaelson.rs
@@ -2,9 +2,10 @@ use num::Zero;
 use opossum::{
     analyzers::AnalyzerType,
     error::OpmResult,
+    joule,
     lightdata::{energy_spectrum_builder::EnergyDataBuilder, light_data_builder::LightDataBuilder},
+    nanometer,
     nodes::{BeamSplitter, Dummy, NodeGroup, NodeReference, Source},
-    spectrum_helper::create_he_ne_spec,
     OpmDocument,
 };
 use std::path::Path;
@@ -12,8 +13,10 @@ use uom::si::f64::Length;
 
 fn main() -> OpmResult<()> {
     let mut scenery = NodeGroup::new("Michaelson interferomater");
-    let light_data_builder =
-        LightDataBuilder::Energy(EnergyDataBuilder::Raw(create_he_ne_spec(1.0)?));
+    let light_data_builder = LightDataBuilder::Energy(EnergyDataBuilder::LaserLines(
+        vec![(nanometer!(633.0), joule!(1.0))],
+        nanometer!(1.0),
+    ));
     let src = scenery.add_node(Source::new("Source", light_data_builder))?;
     let bs = scenery.add_node(BeamSplitter::default())?;
     let sample = scenery.add_node(Dummy::new("Sample"))?;
diff --git a/opossum/examples/nalgebra_matrix_testing.rs b/opossum/examples/nalgebra_matrix_testing.rs
deleted file mode 100644
index 9a65649c..00000000
--- a/opossum/examples/nalgebra_matrix_testing.rs
+++ /dev/null
@@ -1,212 +0,0 @@
-use itertools::Itertools;
-use nalgebra::DVector;
-use std::time::Instant;
-
-fn pairwise_sumation(input: &[f64]) -> f64 {
-    let sub_array_size = 32;
-    let vec_len = input.len();
-    if vec_len < sub_array_size {
-        let mut sum = 0.;
-        for val in input {
-            sum += *val;
-        }
-        sum
-    } else {
-        let new_size = vec_len / 2;
-        pairwise_sumation(&input[..new_size]) + pairwise_sumation(&input[new_size..vec_len])
-    }
-}
-
-// fn pairwise_sum_matrix(input: &DVectorView<f64>) -> f64 {
-//     let vec_len = input.len();
-//     if vec_len < 64 {
-//         input.sum()
-//     } else {
-//         let new_size = vec_len / 2;
-//         pairwise_sum_matrix(&input.rows(0, new_size))
-//             + pairwise_sum_matrix(&input.rows(new_size, vec_len - new_size))
-//     }
-// }
-
-fn kahansum2(input: Vec<f64>) -> f64 {
-    // Prepare the accumulator.
-    // Prepare the accumulator.
-    let mut sum = 0.0;
-    // A running compensation for lost low-order bits.
-    let mut c = 0.0;
-    // The array input has elements indexed input[1] to input[input.length].
-    for i in input.iter() {
-        // c is zero the first time around.
-        let y = i - c;
-        // Alas, sum is big, y small, so low-order digits of y are lost.
-        let t = sum + y;
-        // (t - sum) cancels the high-order part of y;
-        // subtracting y recovers negative (low part of y)
-        c = (t - sum) - y;
-        // Algebraically, c should always be zero. Beware
-        // overly-aggressive optimizing compilers!
-        sum = t;
-        // Next time around, the lost low part will be added to y in a fresh attempt.
-    }
-    return sum;
-}
-
-fn kahansum_vector(input: &DVector<f64>) -> f64 {
-    // Prepare the accumulator.
-    // Prepare the accumulator.
-    let mut sum = 0.0;
-    // A running compensation for lost low-order bits.
-    let mut c = 0.0;
-    // The array input has elements indexed input[1] to input[input.length].
-    for i in input {
-        // c is zero the first time around.
-        let y = i - &c;
-        // Alas, sum is big, y small, so low-order digits of y are lost.
-        let t = &sum + &y;
-        // (t - sum) cancels the high-order part of y;
-        // subtracting y recovers negative (low part of y)
-        c = (&t - &sum) - &y;
-        // Algebraically, c should always be zero. Beware
-        // overly-aggressive optimizing compilers!
-        sum = t;
-        // Next time around, the lost low part will be added to y in a fresh attempt.
-    }
-    return sum;
-}
-
-fn main() {
-    // let mat = DVector::from(vec![-3., -2., -1., 0., 1., 2., 3.]);
-    // let mat2 = MatrixXx1::from(vec![-3., f64::NAN, -2., -1., 1., f64::INFINITY, 3.]);
-    // let mat22 = MatrixXx1::from(vec![f64::NAN, f64::INFINITY]);
-    // let mat3 = MatrixXx1::from(vec![f64::INFINITY, -2., -1., 0., 1., 2., 3.]);
-    // let mat4 = MatrixXx1::from(vec![f64::NAN, f64::NAN]);
-    // let infinite_plus = f64::INFINITY;
-    // let infinite_minus = -f64::INFINITY;
-    // let mut vec = Vec::<f64>::new();
-
-    // let test = mat.rows(0, 5);
-    // let test = mat.fixed_columns
-
-    // let mat_wo_inf =  mat2.iter().filter_map(|x| {
-    //     if !x.is_nan() & x.is_finite(){
-    //         Some(x.clone())
-    //     }
-    //     else{
-    //         None
-    //     }
-    // }).collect::<Vec<f64>>();
-
-    // let mat_wo_inf = MatrixXx1::from(
-    //     mat22
-    //         .iter()
-    //         .cloned()
-    //         .filter(|x| !x.is_nan() & x.is_finite())
-    //         .collect::<Vec<f64>>(),
-    // );
-    // let bla: Vec<&f64> = mat_wo_inf.to_vec().clone();
-    // let test = MatrixXx1::from_vec(bla);
-    // let mat_new = MatrixXx1::from_vec(mat_wo_inf.clone());
-
-    // println!("{mat2}");
-    // println!("{mat_wo_inf}");
-    // println!("{}", mat_wo_inf.len());
-
-    // // println!("min:\t{}\nmax:\t{}\n", mat.min(), mat.max());
-    // println!("min:\t{}\nmax:\t{}\n", mat2.min(), mat2.max());
-    // println!("min:\t{}\nmax:\t{}\n", mat3.min(), mat3.max());
-    // println!("min:\t{}\nmax:\t{}\n", mat4.min(), mat4.max());
-
-    // println!("min:\t{}\nmax:\t{}\n", mat_wo_inf.min(), mat_wo_inf.max());
-    let s1 = 1000.100;
-    let s2 = 999.;
-    let test = (s1 - s2 - 1.1) / s1;
-    let test = format!("{:+e}", test);
-    println!("{test}");
-
-    let energy = 1. + f64::EPSILON;
-    let num_rays = 1000001;
-
-    let energy_per_ray = energy * 1. / num_rays as f64;
-
-    let mut rays_vec2 = Vec::<f64>::with_capacity(num_rays);
-    for _ in 0..(num_rays as usize) {
-        rays_vec2.push(energy_per_ray)
-    }
-
-    let mat = DVector::from_vec(rays_vec2.clone());
-    let rays_vec1 = rays_vec2.clone();
-    let rays_vec3 = rays_vec2.clone();
-    let energy_vec = rays_vec2.iter().fold(0., |a, b| a + b);
-
-    let start = Instant::now();
-    let energy_vec0 = kahansum_vector(&mat); //sum_with_accumulator::<NaiveSum<f64>>();
-    let duration = start.elapsed();
-    println!(
-        "Time elapsed in kahan matrix summation() is: {:?}",
-        duration
-    );
-
-    let start = Instant::now();
-    let energy_vec1 = rays_vec2.iter().cloned().tree_reduce(|a, b| a + b); //sum_with_accumulator::<NaiveSum<f64>>();
-    let duration = start.elapsed();
-    println!(
-        "Time elapsed in pariwise itertools summation() is: {:?}",
-        duration
-    );
-
-    let start = Instant::now();
-    let energy_vec2 = kahansum2(rays_vec1);
-    let duration = start.elapsed();
-    println!("Time elapsed in kahan summation() is: {:?}", duration);
-
-    let start = Instant::now();
-    let energy_vec3 = pairwise_sumation(&rays_vec3[..]);
-    let duration = start.elapsed();
-    println!(
-        "Time elapsed in pairwise self written summation() is: {:?}",
-        duration
-    );
-
-    println!("energy:{}", energy_vec0);
-    println!("energy:{}", energy_vec);
-    println!("energy:{}", energy_vec1.unwrap());
-    println!("energy:{}", energy_vec2);
-    println!("energy:{}", energy_vec3);
-
-    // println!("energy:{}", energy_vec);
-    // println!("energy:{}", energy_vec2);
-    // println!("energy:{}", energy_vec3);
-    // let mut summed_time = Duration::new(0, 0);
-    // for i in 1..1000{
-    //     let energy = (i as f64)+f64::EPSILON;
-    //     let num_rays = 1000 * i;
-    //     let energy_per_ray = energy/num_rays as f64;
-    //     let mut rays_vec = Vec::<f64>::with_capacity(num_rays);
-    //     for i in 0..(num_rays as usize){
-    //         rays_vec.push(energy_per_ray)
-    //     }
-    //     let start = Instant::now();
-    //     let _ = kahansum2(rays_vec);
-    //     let duration = start.elapsed();
-    //     summed_time += duration;
-
-    // }
-    // println!("Time elapsed in kahan summation() is: {:?}", summed_time);
-
-    // let mut summed_time = Duration::new(0, 0);
-    // for i in 1..1000{
-    //     let energy = (i as f64)+f64::EPSILON;
-    //     let num_rays = 1000 * i;
-    //     let energy_per_ray = energy/num_rays as f64;
-    //     let mut rays_vec = Vec::<f64>::with_capacity(num_rays);
-    //     for i in 0..(num_rays as usize){
-    //         rays_vec.push(energy_per_ray)
-    //     }
-    //     let start = Instant::now();
-    //     let _ = pairwise_sumation(&rays_vec);
-    //     let duration = start.elapsed();
-    //     summed_time += duration;
-
-    // }
-    // println!("Time elapsed in pairwise summation() is: {:?}", summed_time);
-}
diff --git a/opossum/examples/reference_test.rs b/opossum/examples/reference_test.rs
index e87695dc..d0ba6e19 100644
--- a/opossum/examples/reference_test.rs
+++ b/opossum/examples/reference_test.rs
@@ -2,9 +2,10 @@ use num::Zero;
 use opossum::{
     analyzers::AnalyzerType,
     error::OpmResult,
+    joule,
     lightdata::{energy_spectrum_builder::EnergyDataBuilder, light_data_builder::LightDataBuilder},
+    nanometer,
     nodes::{EnergyMeter, IdealFilter, NodeGroup, NodeReference, Source},
-    spectrum_helper::create_he_ne_spec,
     OpmDocument,
 };
 use std::path::Path;
@@ -12,8 +13,10 @@ use uom::si::f64::Length;
 
 fn main() -> OpmResult<()> {
     let mut scenery = NodeGroup::new("Reference node demo");
-    let light_data_builder =
-        LightDataBuilder::Energy(EnergyDataBuilder::Raw(create_he_ne_spec(1.0)?));
+    let light_data_builder = LightDataBuilder::Energy(EnergyDataBuilder::LaserLines(
+        vec![(nanometer!(633.0), joule!(1.0))],
+        nanometer!(1.0),
+    ));
     let src = scenery.add_node(Source::new("source", light_data_builder))?;
     let filt = scenery.add_node(IdealFilter::new(
         "50 % filter",
-- 
GitLab


From 0bd5ae29e8f6277401fa0ca87b7e7badecb025b8 Mon Sep 17 00:00:00 2001
From: Udo Eisenbarth <u.eisenbarth@gsi.de>
Date: Fri, 28 Mar 2025 13:16:28 +0100
Subject: [PATCH 3/7] MAke PositionDistribution serializable

---
 opossum/src/coatings/constant_r.rs            | 14 +++++-----
 opossum/src/coatings/fresnel.rs               | 11 ++++----
 opossum/src/coatings/ideal_ar.rs              | 11 ++++----
 opossum/src/coatings/mod.rs                   |  4 ---
 .../src/position_distributions/fibonacci.rs   | 16 +++++++++--
 opossum/src/position_distributions/grid.rs    |  8 +++++-
 .../hexagonal_tiling.rs                       | 28 +++++--------------
 .../src/position_distributions/hexapolar.rs   |  8 +++++-
 opossum/src/position_distributions/mod.rs     | 20 +++++++++++++
 opossum/src/position_distributions/random.rs  |  7 +++++
 opossum/src/position_distributions/sobol.rs   |  7 +++++
 11 files changed, 87 insertions(+), 47 deletions(-)

diff --git a/opossum/src/coatings/constant_r.rs b/opossum/src/coatings/constant_r.rs
index d412ab4d..0b3489f3 100644
--- a/opossum/src/coatings/constant_r.rs
+++ b/opossum/src/coatings/constant_r.rs
@@ -38,14 +38,14 @@ impl Coating for ConstantR {
     ) -> f64 {
         self.reflectivity
     }
-
-    fn to_enum(&self) -> super::CoatingType {
-        CoatingType::ConstantR {
-            reflectivity: self.reflectivity,
+}
+impl From<ConstantR> for CoatingType {
+    fn from(coating: ConstantR) -> Self {
+        Self::ConstantR {
+            reflectivity: coating.reflectivity,
         }
     }
 }
-
 #[cfg(test)]
 mod test {
     use super::*;
@@ -64,10 +64,10 @@ mod test {
         assert!(ConstantR::new(1.1).is_err());
     }
     #[test]
-    fn to_enum() {
+    fn from() {
         let coating = ConstantR::new(0.5).unwrap();
         assert!(matches!(
-            coating.to_enum(),
+            coating.into(),
             CoatingType::ConstantR { reflectivity: 0.5 }
         ));
     }
diff --git a/opossum/src/coatings/fresnel.rs b/opossum/src/coatings/fresnel.rs
index 3a6da68b..d14f6952 100644
--- a/opossum/src/coatings/fresnel.rs
+++ b/opossum/src/coatings/fresnel.rs
@@ -32,11 +32,12 @@ impl Coating for Fresnel {
         // so far, we assume unpolarized (50/50) rays -> take average
         r_p.mul_add(r_p, r_s.powi(2)) / 2.
     }
-    fn to_enum(&self) -> super::CoatingType {
-        CoatingType::Fresnel
+}
+impl From<Fresnel> for CoatingType {
+    fn from(_coating: Fresnel) -> Self {
+        Self::Fresnel
     }
 }
-
 #[cfg(test)]
 mod test {
     use super::*;
@@ -45,9 +46,9 @@ mod test {
     use nalgebra::vector;
 
     #[test]
-    fn to_enum() {
+    fn from() {
         let coating = Fresnel;
-        assert!(matches!(coating.to_enum(), CoatingType::Fresnel));
+        assert!(matches!(coating.into(), CoatingType::Fresnel));
     }
     #[test]
     fn calc_refl_same_index() {
diff --git a/opossum/src/coatings/ideal_ar.rs b/opossum/src/coatings/ideal_ar.rs
index bebd5985..00cea71b 100644
--- a/opossum/src/coatings/ideal_ar.rs
+++ b/opossum/src/coatings/ideal_ar.rs
@@ -19,11 +19,12 @@ impl Coating for IdealAR {
     ) -> f64 {
         0.0
     }
-    fn to_enum(&self) -> super::CoatingType {
-        CoatingType::IdealAR
+}
+impl From<IdealAR> for CoatingType {
+    fn from(_coating: IdealAR) -> Self {
+        Self::IdealAR
     }
 }
-
 #[cfg(test)]
 mod test {
     use super::*;
@@ -31,9 +32,9 @@ mod test {
     use nalgebra::vector;
 
     #[test]
-    fn to_enum() {
+    fn from() {
         let coating = IdealAR;
-        assert!(matches!(coating.to_enum(), CoatingType::IdealAR));
+        assert!(matches!(coating.into(), CoatingType::IdealAR));
     }
     #[test]
     fn calc_refl() {
diff --git a/opossum/src/coatings/mod.rs b/opossum/src/coatings/mod.rs
index ceac2daf..0c3f2dce 100644
--- a/opossum/src/coatings/mod.rs
+++ b/opossum/src/coatings/mod.rs
@@ -63,8 +63,4 @@ pub trait Coating {
     /// Calculate the reflectivity based on the concrete model for an incoming [`Ray`] on a surface with
     /// a given `surface_normal` at the intersection point and the refractive index of the following medium.
     fn calc_reflectivity(&self, incoming_ray: &Ray, surface_normal: Vector3<f64>, n2: f64) -> f64;
-    /// Return the corresponding [`CoatingType`] for a given [`Coating`].
-    ///
-    /// This function is mainly used for serialization / deserialization.
-    fn to_enum(&self) -> CoatingType;
 }
diff --git a/opossum/src/position_distributions/fibonacci.rs b/opossum/src/position_distributions/fibonacci.rs
index a44b72fe..9df84e12 100644
--- a/opossum/src/position_distributions/fibonacci.rs
+++ b/opossum/src/position_distributions/fibonacci.rs
@@ -7,11 +7,13 @@ use crate::error::{OpmResult, OpossumError};
 use super::PositionDistribution;
 use nalgebra::{point, Point3};
 use num::{ToPrimitive, Zero};
+use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 /// Rectangular Fibonacci distribution
 ///
 /// For further details see [here](https://en.wikipedia.org/wiki/Fibonacci_sequence)
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct FibonacciRectangle {
     nr_of_rays: usize,
     side_length_x: Length,
@@ -62,11 +64,15 @@ impl PositionDistribution for FibonacciRectangle {
         points
     }
 }
-
+impl From<FibonacciRectangle> for super::PosDistType {
+    fn from(f: FibonacciRectangle) -> Self {
+        Self::FibonacciRectangle(f)
+    }
+}
 /// Rectangular Fibbonacci distribution
 ///
 /// For further details see [here](https://en.wikipedia.org/wiki/Fibonacci_sequence)
-#[derive(Clone)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct FibonacciEllipse {
     nr_of_rays: usize,
     radius_x: Length,
@@ -119,7 +125,11 @@ impl PositionDistribution for FibonacciEllipse {
         points
     }
 }
-
+impl From<FibonacciEllipse> for super::PosDistType {
+    fn from(f: FibonacciEllipse) -> Self {
+        Self::FibonacciEllipse(f)
+    }
+}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/position_distributions/grid.rs b/opossum/src/position_distributions/grid.rs
index 2bda4beb..33b9d8bb 100644
--- a/opossum/src/position_distributions/grid.rs
+++ b/opossum/src/position_distributions/grid.rs
@@ -7,10 +7,11 @@ use crate::{
 };
 use nalgebra::Point3;
 use num::Zero;
+use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 /// Rectangular, evenly-sized grid distribution
-#[derive(Clone)]
+#[derive(Clone, Serialize, Deserialize, Debug)]
 pub struct Grid {
     nr_of_points: (usize, usize),
     side_length: (Length, Length),
@@ -91,6 +92,11 @@ impl PositionDistribution for Grid {
     }
 }
 
+impl From<Grid> for super::PosDistType {
+    fn from(grid: Grid) -> Self {
+        Self::Grid(grid)
+    }
+}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/position_distributions/hexagonal_tiling.rs b/opossum/src/position_distributions/hexagonal_tiling.rs
index a6499eeb..388a578e 100644
--- a/opossum/src/position_distributions/hexagonal_tiling.rs
+++ b/opossum/src/position_distributions/hexagonal_tiling.rs
@@ -9,10 +9,11 @@ use crate::{
 use super::PositionDistribution;
 use nalgebra::{Point2, Point3, Vector3};
 use num::{ToPrimitive, Zero};
+use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 /// Circular, hexapolar distribution
-#[derive(Clone)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct HexagonalTiling {
     nr_of_hex_along_radius: u8,
     radius: Length,
@@ -93,25 +94,10 @@ impl PositionDistribution for HexagonalTiling {
         }
         points
     }
-    // fn generate(&self) -> Vec<Point3<Length>> {
-    //     let mut points: Vec<Point3<Length>> = Vec::new();
-    //     // Add center point
-    //     points.push(Point3::origin());
+}
 
-    //     let radius_step = self.radius/self.nr_of_hex_along_radius.to_f64().unwrap();
-    //     for i in 1_u8..self.nr_of_hex_along_radius+1{
-    //         // let mut last_point = points.last().unwrap().clone();
-    //         // last_point.x += radius_step;
-    //         let mut hex = Point3::origin();
-    //         hex.x += radius_step*i.to_f64().unwrap();
-    //         for j in 0_u8..6{
-    //             for k in 0_u8..i{
-    //                 points.push(hex);
-    //                 let angle = PI/3.*(2.+j.to_f64().unwrap());
-    //                 hex = hex + Vector3::new(f64::cos(angle)*radius_step, f64::sin(angle)*radius_step,Length::zero());
-    //             }
-    //         }
-    //     }
-    //     points
-    // }
+impl From<HexagonalTiling> for super::PosDistType {
+    fn from(hexagonal_tiling: HexagonalTiling) -> Self {
+        Self::HexagonalTiling(hexagonal_tiling)
+    }
 }
diff --git a/opossum/src/position_distributions/hexapolar.rs b/opossum/src/position_distributions/hexapolar.rs
index 5b7214b0..160101ad 100644
--- a/opossum/src/position_distributions/hexapolar.rs
+++ b/opossum/src/position_distributions/hexapolar.rs
@@ -4,10 +4,11 @@ use crate::error::{OpmResult, OpossumError};
 use super::PositionDistribution;
 use nalgebra::{point, Point3};
 use num::Zero;
+use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 /// Circular, hexapolar distribution
-#[derive(Clone)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct Hexapolar {
     nr_of_rings: u8,
     radius: Length,
@@ -54,6 +55,11 @@ impl PositionDistribution for Hexapolar {
         points
     }
 }
+impl From<Hexapolar> for super::PosDistType {
+    fn from(dist: Hexapolar) -> Self {
+        Self::Hexapolar(dist)
+    }
+}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/position_distributions/mod.rs b/opossum/src/position_distributions/mod.rs
index 4a44bdbf..252044fe 100644
--- a/opossum/src/position_distributions/mod.rs
+++ b/opossum/src/position_distributions/mod.rs
@@ -17,6 +17,7 @@
 //! ```
 //! `points` now contains a vector of 10 randomly-placed 3D points within the rectangle (-0.5 mm .. 0.5 mm) x (-1.0 mm .. 1.0 mm).
 use nalgebra::Point3;
+use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 mod fibonacci;
@@ -40,3 +41,22 @@ pub trait PositionDistribution {
     /// This function generates a vector of 3D points (of dimension [`Length`]) with the given parameters defined earlier.
     fn generate(&self) -> Vec<Point3<Length>>;
 }
+
+/// Enum for the different types of position distributions
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum PosDistType {
+    /// Rectangular, uniform random distribution
+    Random(random::Random),
+    /// Rectangular, evenly-sized grid distribution
+    Grid(grid::Grid),
+    /// Hexagonal tiling distribution
+    HexagonalTiling(hexagonal_tiling::HexagonalTiling),
+    /// Hexapolar distribution
+    Hexapolar(hexapolar::Hexapolar),
+    /// Fibonacci rectangle distribution
+    FibonacciRectangle(fibonacci::FibonacciRectangle),
+    /// Fibonacci ellipse distribution
+    FibonacciEllipse(fibonacci::FibonacciEllipse),
+    /// Pseudo random Sobol distribution
+    Sobol(sobol::SobolDist),
+}
diff --git a/opossum/src/position_distributions/random.rs b/opossum/src/position_distributions/random.rs
index 96f4163f..b88c350b 100644
--- a/opossum/src/position_distributions/random.rs
+++ b/opossum/src/position_distributions/random.rs
@@ -5,9 +5,11 @@ use crate::error::{OpmResult, OpossumError};
 use nalgebra::{point, Point3};
 use num::Zero;
 use rand::Rng;
+use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 /// Rectangular, uniform random distribution
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct Random {
     nr_of_points: usize,
     side_length_x: Length,
@@ -64,6 +66,11 @@ impl PositionDistribution for Random {
         points
     }
 }
+impl From<Random> for super::PosDistType {
+    fn from(random: Random) -> Self {
+        Self::Random(random)
+    }
+}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/position_distributions/sobol.rs b/opossum/src/position_distributions/sobol.rs
index c164006d..23f966d4 100644
--- a/opossum/src/position_distributions/sobol.rs
+++ b/opossum/src/position_distributions/sobol.rs
@@ -3,12 +3,14 @@ use super::PositionDistribution;
 use crate::error::{OpmResult, OpossumError};
 use nalgebra::{point, Point3};
 use num::Zero;
+use serde::{Deserialize, Serialize};
 use sobol::{params::JoeKuoD6, Sobol};
 use uom::si::f64::Length;
 
 /// Rectangluar, low-discrepancy quasirandom distribution
 ///
 /// For further details see [here](https://en.wikipedia.org/wiki/Sobol_sequence)
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct SobolDist {
     nr_of_points: usize,
     side_length_x: Length,
@@ -68,6 +70,11 @@ impl PositionDistribution for SobolDist {
         points
     }
 }
+impl From<SobolDist> for super::PosDistType {
+    fn from(f: SobolDist) -> Self {
+        Self::Sobol(f)
+    }
+}
 #[cfg(test)]
 mod test {
     use super::*;
-- 
GitLab


From e8930cdf17c1b127b21cff0ee120df7eddab8ad7 Mon Sep 17 00:00:00 2001
From: Udo Eisenbarth <u.eisenbarth@gsi.de>
Date: Fri, 28 Mar 2025 13:34:09 +0100
Subject: [PATCH 4/7] Add From traits for refractive index

---
 opossum/src/refractive_index/mod.rs                   | 2 +-
 opossum/src/refractive_index/refr_index_conrady.rs    | 5 +++++
 opossum/src/refractive_index/refr_index_const.rs      | 6 +++++-
 opossum/src/refractive_index/refr_index_schott.rs     | 5 +++++
 opossum/src/refractive_index/refr_index_sellmeier1.rs | 5 +++++
 5 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/opossum/src/refractive_index/mod.rs b/opossum/src/refractive_index/mod.rs
index 326934a4..e25ec90d 100644
--- a/opossum/src/refractive_index/mod.rs
+++ b/opossum/src/refractive_index/mod.rs
@@ -66,7 +66,7 @@ impl From<RefractiveIndexType> for Proptype {
     }
 }
 /// All refractive index models must implement this trait.
-pub trait RefractiveIndex {
+pub trait RefractiveIndex: {
     /// Get the refractive index value of the current model for the given wavelength.
     ///
     /// # Errors
diff --git a/opossum/src/refractive_index/refr_index_conrady.rs b/opossum/src/refractive_index/refr_index_conrady.rs
index 4c6859e0..915ed5a3 100644
--- a/opossum/src/refractive_index/refr_index_conrady.rs
+++ b/opossum/src/refractive_index/refr_index_conrady.rs
@@ -65,6 +65,11 @@ impl RefractiveIndex for RefrIndexConrady {
         RefractiveIndexType::Conrady(self.clone())
     }
 }
+impl From<RefrIndexConrady> for RefractiveIndexType {
+    fn from(refr: RefrIndexConrady) -> Self {
+        Self::Conrady(refr)
+    }
+}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/refractive_index/refr_index_const.rs b/opossum/src/refractive_index/refr_index_const.rs
index 5f999e24..f20a5573 100644
--- a/opossum/src/refractive_index/refr_index_const.rs
+++ b/opossum/src/refractive_index/refr_index_const.rs
@@ -46,7 +46,11 @@ impl RefractiveIndex for RefrIndexConst {
         RefractiveIndexType::Const(self.clone())
     }
 }
-
+impl From<RefrIndexConst> for RefractiveIndexType {
+    fn from(i: RefrIndexConst) -> Self {
+        Self::Const(i)
+    }
+}
 #[cfg(test)]
 mod test {
     use num::Zero;
diff --git a/opossum/src/refractive_index/refr_index_schott.rs b/opossum/src/refractive_index/refr_index_schott.rs
index 888fedd2..19a6780e 100644
--- a/opossum/src/refractive_index/refr_index_schott.rs
+++ b/opossum/src/refractive_index/refr_index_schott.rs
@@ -93,6 +93,11 @@ impl RefractiveIndex for RefrIndexSchott {
         RefractiveIndexType::Schott(self.clone())
     }
 }
+impl From<RefrIndexSchott> for RefractiveIndexType {
+    fn from(refr: RefrIndexSchott) -> Self {
+        Self::Schott(refr)
+    }
+}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/refractive_index/refr_index_sellmeier1.rs b/opossum/src/refractive_index/refr_index_sellmeier1.rs
index 8f1d9955..85f250a4 100644
--- a/opossum/src/refractive_index/refr_index_sellmeier1.rs
+++ b/opossum/src/refractive_index/refr_index_sellmeier1.rs
@@ -91,6 +91,11 @@ impl RefractiveIndex for RefrIndexSellmeier1 {
         RefractiveIndexType::Sellmeier1(self.clone())
     }
 }
+impl From<RefrIndexSellmeier1> for RefractiveIndexType {
+    fn from(refr: RefrIndexSellmeier1) -> Self {
+        Self::Sellmeier1(refr)
+    }
+}
 #[cfg(test)]
 mod test {
     use crate::nanometer;
-- 
GitLab


From 41753f5f276e9d9259f2d114c2e0fdf87e38dc9a Mon Sep 17 00:00:00 2001
From: Udo Eisenbarth <u.eisenbarth@gsi.de>
Date: Fri, 28 Mar 2025 14:17:53 +0100
Subject: [PATCH 5/7] Add RayDataBuilder::Collimated

---
 .../energy_distributions/general_gaussian.rs  |  9 +++++-
 opossum/src/energy_distributions/mod.rs       | 30 +++++++++++++------
 opossum/src/energy_distributions/uniform.rs   | 11 +++++--
 opossum/src/lightdata/ray_data_builder.rs     | 19 +++++++++++-
 .../src/position_distributions/fibonacci.rs   |  4 +--
 opossum/src/position_distributions/grid.rs    |  2 +-
 .../hexagonal_tiling.rs                       |  2 +-
 .../src/position_distributions/hexapolar.rs   |  2 +-
 opossum/src/position_distributions/mod.rs     | 17 ++++++++++-
 opossum/src/position_distributions/random.rs  |  2 +-
 opossum/src/position_distributions/sobol.rs   |  2 +-
 opossum/src/refractive_index/mod.rs           |  2 +-
 12 files changed, 79 insertions(+), 23 deletions(-)

diff --git a/opossum/src/energy_distributions/general_gaussian.rs b/opossum/src/energy_distributions/general_gaussian.rs
index 9489459d..cb37cec8 100644
--- a/opossum/src/energy_distributions/general_gaussian.rs
+++ b/opossum/src/energy_distributions/general_gaussian.rs
@@ -8,11 +8,14 @@ use crate::{
 };
 use kahan::KahanSummator;
 use nalgebra::Point2;
+use serde::{Deserialize, Serialize};
 use uom::si::{
     angle::radian,
     energy::joule,
     f64::{Angle, Energy, Length},
 };
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct General2DGaussian {
     total_energy: Energy,
     mu_xy: Point2<Length>,
@@ -122,7 +125,11 @@ impl EnergyDistribution for General2DGaussian {
         self.total_energy
     }
 }
-
+impl From<General2DGaussian> for super::EnergyDistType {
+    fn from(g: General2DGaussian) -> Self {
+        Self::General2DGaussian(g)
+    }
+}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/energy_distributions/mod.rs b/opossum/src/energy_distributions/mod.rs
index d5a90479..22c01756 100644
--- a/opossum/src/energy_distributions/mod.rs
+++ b/opossum/src/energy_distributions/mod.rs
@@ -1,10 +1,14 @@
 //! Module for handling energy distributions
-use nalgebra::Point2;
-use uom::si::f64::{Energy, Length};
-
 pub mod general_gaussian;
 pub mod uniform;
+pub use general_gaussian::General2DGaussian;
+use serde::{Deserialize, Serialize};
+pub use uniform::UniformDist;
+
+use crate::joule;
 use kahan::KahanSummator;
+use nalgebra::Point2;
+use uom::si::f64::{Energy, Length};
 
 pub trait EnergyDistribution {
     fn apply(&self, input: &[Point2<Length>]) -> Vec<Energy>;
@@ -31,10 +35,18 @@ pub trait EnergyDistribution {
     }
 }
 
-pub use general_gaussian::General2DGaussian;
-pub use uniform::UniformDist;
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub enum EnergyDistType {
+    Uniform(UniformDist),
+    General2DGaussian(general_gaussian::General2DGaussian),
+}
 
-use crate::joule;
-// pub use hexapolar::Hexapolar;
-// pub use random::Random;
-// pub use sobol::SobolDist;
+impl EnergyDistType {
+    #[must_use]
+    pub fn generate(&self) -> &dyn EnergyDistribution {
+        match self {
+            Self::Uniform(dist) => dist,
+            Self::General2DGaussian(dist) => dist,
+        }
+    }
+}
diff --git a/opossum/src/energy_distributions/uniform.rs b/opossum/src/energy_distributions/uniform.rs
index 5e7f7233..e5640fe0 100644
--- a/opossum/src/energy_distributions/uniform.rs
+++ b/opossum/src/energy_distributions/uniform.rs
@@ -2,14 +2,15 @@
 
 use nalgebra::Point2;
 use num::ToPrimitive;
+use serde::{Deserialize, Serialize};
 use uom::si::{
     energy::joule,
     f64::{Energy, Length},
 };
 
-use crate::error::{OpmResult, OpossumError};
-
 use super::EnergyDistribution;
+use crate::error::{OpmResult, OpossumError};
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct UniformDist {
     total_energy: Energy,
 }
@@ -43,7 +44,11 @@ impl EnergyDistribution for UniformDist {
         self.total_energy
     }
 }
-
+impl From<UniformDist> for super::EnergyDistType {
+    fn from(ud: UniformDist) -> Self {
+        Self::Uniform(ud)
+    }
+}
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/opossum/src/lightdata/ray_data_builder.rs b/opossum/src/lightdata/ray_data_builder.rs
index 5ee4d67b..46d7c082 100644
--- a/opossum/src/lightdata/ray_data_builder.rs
+++ b/opossum/src/lightdata/ray_data_builder.rs
@@ -4,8 +4,12 @@
 //! This builder allows easier serialization / deserialization in OPM files.
 use std::fmt::Display;
 
-use crate::{error::OpmResult, rays::Rays};
+use crate::{
+    energy_distributions::EnergyDistType, error::OpmResult, position_distributions::PosDistType,
+    rays::Rays,
+};
 use serde::{Deserialize, Serialize};
+use uom::si::f64::Length;
 
 use super::LightData;
 
@@ -14,6 +18,8 @@ use super::LightData;
 pub enum RayDataBuilder {
     /// Raw [`Rays`] data.
     Raw(Rays),
+    /// Collimated [`Rays`] data with a given [`PosDistType`] and [`EnergyDistType`] as well as a given wavelength.
+    Collimated(PosDistType, EnergyDistType, Length),
 }
 
 impl RayDataBuilder {
@@ -24,6 +30,11 @@ impl RayDataBuilder {
     pub fn build(self) -> OpmResult<LightData> {
         match self {
             Self::Raw(rays) => Ok(LightData::Geometric(rays)),
+            Self::Collimated(pos_dist, energy_dist, wave_length) => {
+                let rays =
+                    Rays::new_collimated(wave_length, energy_dist.generate(), pos_dist.generate())?;
+                Ok(LightData::Geometric(rays))
+            }
         }
     }
 }
@@ -32,6 +43,12 @@ impl Display for RayDataBuilder {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
             Self::Raw(r) => write!(f, "Raw({r})"),
+            Self::Collimated(pos_dist, energy_dist, wave_length) => {
+                write!(
+                    f,
+                    "Collimated({pos_dist:?}, {energy_dist:?}, {wave_length:?})"
+                )
+            }
         }
     }
 }
diff --git a/opossum/src/position_distributions/fibonacci.rs b/opossum/src/position_distributions/fibonacci.rs
index 9df84e12..cb45fc9b 100644
--- a/opossum/src/position_distributions/fibonacci.rs
+++ b/opossum/src/position_distributions/fibonacci.rs
@@ -13,7 +13,7 @@ use uom::si::f64::Length;
 /// Rectangular Fibonacci distribution
 ///
 /// For further details see [here](https://en.wikipedia.org/wiki/Fibonacci_sequence)
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct FibonacciRectangle {
     nr_of_rays: usize,
     side_length_x: Length,
@@ -72,7 +72,7 @@ impl From<FibonacciRectangle> for super::PosDistType {
 /// Rectangular Fibbonacci distribution
 ///
 /// For further details see [here](https://en.wikipedia.org/wiki/Fibonacci_sequence)
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct FibonacciEllipse {
     nr_of_rays: usize,
     radius_x: Length,
diff --git a/opossum/src/position_distributions/grid.rs b/opossum/src/position_distributions/grid.rs
index 33b9d8bb..0ef74457 100644
--- a/opossum/src/position_distributions/grid.rs
+++ b/opossum/src/position_distributions/grid.rs
@@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 /// Rectangular, evenly-sized grid distribution
-#[derive(Clone, Serialize, Deserialize, Debug)]
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
 pub struct Grid {
     nr_of_points: (usize, usize),
     side_length: (Length, Length),
diff --git a/opossum/src/position_distributions/hexagonal_tiling.rs b/opossum/src/position_distributions/hexagonal_tiling.rs
index 388a578e..3fccec78 100644
--- a/opossum/src/position_distributions/hexagonal_tiling.rs
+++ b/opossum/src/position_distributions/hexagonal_tiling.rs
@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 /// Circular, hexapolar distribution
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct HexagonalTiling {
     nr_of_hex_along_radius: u8,
     radius: Length,
diff --git a/opossum/src/position_distributions/hexapolar.rs b/opossum/src/position_distributions/hexapolar.rs
index 160101ad..95e64928 100644
--- a/opossum/src/position_distributions/hexapolar.rs
+++ b/opossum/src/position_distributions/hexapolar.rs
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 /// Circular, hexapolar distribution
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct Hexapolar {
     nr_of_rings: u8,
     radius: Length,
diff --git a/opossum/src/position_distributions/mod.rs b/opossum/src/position_distributions/mod.rs
index 252044fe..097ff23d 100644
--- a/opossum/src/position_distributions/mod.rs
+++ b/opossum/src/position_distributions/mod.rs
@@ -43,7 +43,7 @@ pub trait PositionDistribution {
 }
 
 /// Enum for the different types of position distributions
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum PosDistType {
     /// Rectangular, uniform random distribution
     Random(random::Random),
@@ -60,3 +60,18 @@ pub enum PosDistType {
     /// Pseudo random Sobol distribution
     Sobol(sobol::SobolDist),
 }
+impl PosDistType {
+    /// Generate the point distribution.
+    #[must_use]
+    pub fn generate(&self) -> &dyn PositionDistribution {
+        match self {
+            Self::Random(dist) => dist,
+            Self::Grid(dist) => dist,
+            Self::HexagonalTiling(dist) => dist,
+            Self::Hexapolar(dist) => dist,
+            Self::FibonacciRectangle(dist) => dist,
+            Self::FibonacciEllipse(dist) => dist,
+            Self::Sobol(dist) => dist,
+        }
+    }
+}
diff --git a/opossum/src/position_distributions/random.rs b/opossum/src/position_distributions/random.rs
index b88c350b..b51661eb 100644
--- a/opossum/src/position_distributions/random.rs
+++ b/opossum/src/position_distributions/random.rs
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 /// Rectangular, uniform random distribution
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct Random {
     nr_of_points: usize,
     side_length_x: Length,
diff --git a/opossum/src/position_distributions/sobol.rs b/opossum/src/position_distributions/sobol.rs
index 23f966d4..bbcf8fa3 100644
--- a/opossum/src/position_distributions/sobol.rs
+++ b/opossum/src/position_distributions/sobol.rs
@@ -10,7 +10,7 @@ use uom::si::f64::Length;
 /// Rectangluar, low-discrepancy quasirandom distribution
 ///
 /// For further details see [here](https://en.wikipedia.org/wiki/Sobol_sequence)
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct SobolDist {
     nr_of_points: usize,
     side_length_x: Length,
diff --git a/opossum/src/refractive_index/mod.rs b/opossum/src/refractive_index/mod.rs
index e25ec90d..326934a4 100644
--- a/opossum/src/refractive_index/mod.rs
+++ b/opossum/src/refractive_index/mod.rs
@@ -66,7 +66,7 @@ impl From<RefractiveIndexType> for Proptype {
     }
 }
 /// All refractive index models must implement this trait.
-pub trait RefractiveIndex: {
+pub trait RefractiveIndex {
     /// Get the refractive index value of the current model for the given wavelength.
     ///
     /// # Errors
-- 
GitLab


From da2cbc70def4990102165a88a81d1e4bb964db4d Mon Sep 17 00:00:00 2001
From: Udo Eisenbarth <u.eisenbarth@gsi.de>
Date: Fri, 28 Mar 2025 15:24:59 +0100
Subject: [PATCH 6/7] Update examples and source_helpers.

---
 opossum/examples/apodization.rs           |  2 +-
 opossum/examples/fresnel_coating.rs       | 12 +++++-------
 opossum/examples/ghost_focus.rs           | 17 +++++++----------
 opossum/examples/laser_system.rs          |  7 -------
 opossum/src/lightdata/ray_data_builder.rs | 23 +++++++++++++++++++----
 opossum/src/nodes/source_helper.rs        | 23 +++++++++++------------
 6 files changed, 43 insertions(+), 41 deletions(-)

diff --git a/opossum/examples/apodization.rs b/opossum/examples/apodization.rs
index 571f71be..9f5c810d 100644
--- a/opossum/examples/apodization.rs
+++ b/opossum/examples/apodization.rs
@@ -25,7 +25,7 @@ fn main() -> OpmResult<()> {
         RectangleConfig::new(millimeter!(15.), millimeter!(15.), millimeter!(0.0, 0.0))?;
     let aperture = Aperture::BinaryRectangle(rect_config);
 
-    dummy.set_aperture(&PortType::Input, "front", &aperture)?;
+    dummy.set_aperture(&PortType::Input, "input_1", &aperture)?;
     let dummy = dummy.with_decenter(millimeter!(-5.0, 5.0, 0.0))?;
 
     let i_d = scenery.add_node(dummy)?;
diff --git a/opossum/examples/fresnel_coating.rs b/opossum/examples/fresnel_coating.rs
index a0883b9c..19715ec3 100644
--- a/opossum/examples/fresnel_coating.rs
+++ b/opossum/examples/fresnel_coating.rs
@@ -10,7 +10,6 @@ use opossum::{
     optic_node::OpticNode,
     optic_ports::PortType,
     position_distributions::Grid,
-    rays::Rays,
     refractive_index::RefrIndexConst,
     utils::geom_transformation::Isometry,
     OpmDocument,
@@ -19,12 +18,11 @@ use std::path::Path;
 
 fn main() -> OpmResult<()> {
     let mut scenery = NodeGroup::new("Fresnel coating example");
-    let rays = Rays::new_collimated(
-        nanometer!(1000.),
-        &UniformDist::new(joule!(1.))?,
-        &Grid::new((millimeter!(9.), millimeter!(9.)), (100, 100))?,
-    )?;
-    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Raw(rays));
+    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
+        pos_dist: Grid::new((millimeter!(9.), millimeter!(9.)), (100, 100))?.into(),
+        energy_dist: UniformDist::new(joule!(1.))?.into(),
+        wave_length: nanometer!(1000.),
+    });
     let mut source = Source::new("src", light_data_builder);
     source.set_isometry(Isometry::identity())?;
     let src = scenery.add_node(source)?;
diff --git a/opossum/examples/ghost_focus.rs b/opossum/examples/ghost_focus.rs
index 0c3bfbd8..4896e59b 100644
--- a/opossum/examples/ghost_focus.rs
+++ b/opossum/examples/ghost_focus.rs
@@ -12,7 +12,6 @@ use opossum::{
     optic_ports::PortType,
     position_distributions::HexagonalTiling,
     radian,
-    rays::Rays,
     utils::geom_transformation::Isometry,
     OpmDocument,
 };
@@ -21,21 +20,19 @@ use std::path::Path;
 fn main() -> OpmResult<()> {
     let mut scenery = NodeGroup::default();
     scenery.node_attr_mut().set_name("Folded Telescope");
-
-    let rays = Rays::new_collimated(
-        nanometer!(1000.0),
-        &General2DGaussian::new(
+    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
+        pos_dist: HexagonalTiling::new(millimeter!(15.0), 25, millimeter!(0.0, 0.))?.into(),
+        energy_dist: General2DGaussian::new(
             joule!(2.),
             millimeter!(0., 0.),
             millimeter!(8., 8.),
             5.,
             radian!(0.),
             false,
-        )?,
-        &HexagonalTiling::new(millimeter!(15.0), 25, millimeter!(0.0, 0.))?,
-    )?;
-
-    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Raw(rays));
+        )?
+        .into(),
+        wave_length: nanometer!(1000.0),
+    });
     let mut src = Source::new("collimated ray source", light_data_builder);
     src.set_isometry(Isometry::identity())?;
     let i_src = scenery.add_node(src)?;
diff --git a/opossum/examples/laser_system.rs b/opossum/examples/laser_system.rs
index 6c97f96b..8d7786d9 100644
--- a/opossum/examples/laser_system.rs
+++ b/opossum/examples/laser_system.rs
@@ -16,13 +16,6 @@ use uom::si::f64::Length;
 fn main() -> OpmResult<()> {
     let mut scenery = NodeGroup::new("laser system");
     // Main beam line
-
-    // let source = Source::new(
-    //     "Source",
-    //     &LightData::Energy(DataEnergy {
-    //         spectrum: create_he_ne_spec(1.0)?,
-    //     }),
-    // );
     let source = round_collimated_ray_source(millimeter!(1.0), joule!(1.0), 3)?;
     let i_src = scenery.add_node(source)?;
     let i_l1 = scenery.add_node(ParaxialSurface::new("f=100", millimeter!(100.0))?)?;
diff --git a/opossum/src/lightdata/ray_data_builder.rs b/opossum/src/lightdata/ray_data_builder.rs
index 46d7c082..65d80058 100644
--- a/opossum/src/lightdata/ray_data_builder.rs
+++ b/opossum/src/lightdata/ray_data_builder.rs
@@ -18,8 +18,15 @@ use super::LightData;
 pub enum RayDataBuilder {
     /// Raw [`Rays`] data.
     Raw(Rays),
-    /// Collimated [`Rays`] data with a given [`PosDistType`] and [`EnergyDistType`] as well as a given wavelength.
-    Collimated(PosDistType, EnergyDistType, Length),
+    /// Collimated [`Rays`] data with a given [`PosDistType`] and [`EnergyDistType`] as well as a given single wavelength.
+    Collimated {
+        /// Position distribution.
+        pos_dist: PosDistType,
+        /// Energy distribution.
+        energy_dist: EnergyDistType,
+        /// Wavelength of the rays.
+        wave_length: Length,
+    },
 }
 
 impl RayDataBuilder {
@@ -30,7 +37,11 @@ impl RayDataBuilder {
     pub fn build(self) -> OpmResult<LightData> {
         match self {
             Self::Raw(rays) => Ok(LightData::Geometric(rays)),
-            Self::Collimated(pos_dist, energy_dist, wave_length) => {
+            Self::Collimated {
+                pos_dist,
+                energy_dist,
+                wave_length,
+            } => {
                 let rays =
                     Rays::new_collimated(wave_length, energy_dist.generate(), pos_dist.generate())?;
                 Ok(LightData::Geometric(rays))
@@ -43,7 +54,11 @@ impl Display for RayDataBuilder {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
             Self::Raw(r) => write!(f, "Raw({r})"),
-            Self::Collimated(pos_dist, energy_dist, wave_length) => {
+            Self::Collimated {
+                pos_dist,
+                energy_dist,
+                wave_length,
+            } => {
                 write!(
                     f,
                     "Collimated({pos_dist:?}, {energy_dist:?}, {wave_length:?})"
diff --git a/opossum/src/nodes/source_helper.rs b/opossum/src/nodes/source_helper.rs
index 72a5875e..6fb1017a 100644
--- a/opossum/src/nodes/source_helper.rs
+++ b/opossum/src/nodes/source_helper.rs
@@ -2,6 +2,7 @@
 //! Helper functions for easier creation of `standard` ray [`Source`]s.
 use super::Source;
 use crate::{
+    energy_distributions::UniformDist,
     error::OpmResult,
     lightdata::{light_data_builder::LightDataBuilder, ray_data_builder::RayDataBuilder},
     nanometer,
@@ -29,12 +30,11 @@ pub fn round_collimated_ray_source(
     energy: Energy,
     nr_of_rings: u8,
 ) -> OpmResult<Source> {
-    let rays = Rays::new_uniform_collimated(
-        nanometer!(1000.0),
-        energy,
-        &Hexapolar::new(radius, nr_of_rings)?,
-    )?;
-    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Raw(rays));
+    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
+        pos_dist: Hexapolar::new(radius, nr_of_rings)?.into(),
+        energy_dist: UniformDist::new(energy)?.into(),
+        wave_length: nanometer!(1000.0),
+    });
     let mut src = Source::new("collimated line ray source", light_data_builder);
     src.set_isometry(Isometry::identity())?;
     Ok(src)
@@ -55,12 +55,11 @@ pub fn collimated_line_ray_source(
     energy: Energy,
     nr_of_points_y: usize,
 ) -> OpmResult<Source> {
-    let rays = Rays::new_uniform_collimated(
-        nanometer!(1000.0),
-        energy,
-        &Grid::new((Length::zero(), size_y), (1, nr_of_points_y))?,
-    )?;
-    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Raw(rays));
+    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
+        pos_dist: Grid::new((Length::zero(), size_y), (1, nr_of_points_y))?.into(),
+        energy_dist: UniformDist::new(energy)?.into(),
+        wave_length: nanometer!(1000.0),
+    });
     let mut src = Source::new("collimated line ray source", light_data_builder);
     src.set_isometry(Isometry::identity())?;
     Ok(src)
-- 
GitLab


From 452b4ee6e061eb615614f0643a2ce0aac7f6c143 Mon Sep 17 00:00:00 2001
From: Udo Eisenbarth <u.eisenbarth@gsi.de>
Date: Fri, 28 Mar 2025 16:44:15 +0100
Subject: [PATCH 7/7] Serialize SpectrumDistribution for
 RayDataBuilder::Collimated

---
 opossum/Cargo.toml                            |  1 -
 opossum/examples/folded_telescope.rs          | 15 ++--
 opossum/examples/fresnel_coating.rs           |  3 +-
 opossum/examples/ghost_focus.rs               |  3 +-
 opossum/examples/hhts/hhts.rs                 | 75 +++----------------
 opossum/examples/prism_dispersion.rs          | 23 ++----
 opossum/src/console.rs                        | 10 +--
 opossum/src/lightdata/ray_data_builder.rs     | 21 +++---
 opossum/src/main.rs                           |  2 +-
 opossum/src/nodes/source_helper.rs            |  5 +-
 opossum/src/spectral_distribution/gaussian.rs |  7 ++
 .../src/spectral_distribution/laser_lines.rs  | 74 ++++++++++++++++++
 opossum/src/spectral_distribution/mod.rs      | 22 ++++++
 opossum/src/spectrum.rs                       |  6 +-
 14 files changed, 155 insertions(+), 112 deletions(-)
 create mode 100644 opossum/src/spectral_distribution/laser_lines.rs

diff --git a/opossum/Cargo.toml b/opossum/Cargo.toml
index 6ec8db83..33f720c6 100644
--- a/opossum/Cargo.toml
+++ b/opossum/Cargo.toml
@@ -30,7 +30,6 @@ opm_macros_lib = { path = "opm_macros_lib" }
 petgraph = { version = "0.7", features = ["serde-1"] } # the graph library
 uom = {version="0.36", features = ["serde"] }
 serde = { version = "1", features = ['rc'] }
-#serde_yaml = "0.9"
 ron="0.9"
 
 csv = "1"
diff --git a/opossum/examples/folded_telescope.rs b/opossum/examples/folded_telescope.rs
index dbce98b7..62777a11 100644
--- a/opossum/examples/folded_telescope.rs
+++ b/opossum/examples/folded_telescope.rs
@@ -13,7 +13,6 @@ use opossum::{
     nodes::{Lens, NodeGroup, NodeReference, RayPropagationVisualizer, Source, ThinMirror},
     optic_node::{Alignable, OpticNode},
     position_distributions::Hexapolar,
-    rays::Rays,
     refractive_index::RefrIndexSellmeier1,
     spectral_distribution::Gaussian,
     utils::geom_transformation::Isometry,
@@ -32,18 +31,18 @@ pub fn main() -> OpmResult<()> {
         nanometer!(300.)..nanometer!(1200.),
     )?;
     let mut scenery = NodeGroup::default();
-    let rays = Rays::new_collimated_with_spectrum(
-        &Gaussian::new(
+    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
+        pos_dist: Hexapolar::new(millimeter!(10.), 10)?.into(),
+        energy_dist: UniformDist::new(joule!(1.))?.into(),
+        spect_dist: Gaussian::new(
             (nanometer!(1054.), nanometer!(1068.)),
             1,
             nanometer!(1054.),
             nanometer!(8.),
             1.,
-        )?,
-        &UniformDist::new(joule!(1.))?,
-        &Hexapolar::new(millimeter!(10.), 10)?,
-    )?;
-    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Raw(rays));
+        )?
+        .into(),
+    });
     let mut src = Source::new("collimated ray source", light_data_builder);
     src.set_alignment_wavelength(alignment_wvl)?;
     src.set_isometry(Isometry::identity())?;
diff --git a/opossum/examples/fresnel_coating.rs b/opossum/examples/fresnel_coating.rs
index 19715ec3..9485fdda 100644
--- a/opossum/examples/fresnel_coating.rs
+++ b/opossum/examples/fresnel_coating.rs
@@ -11,6 +11,7 @@ use opossum::{
     optic_ports::PortType,
     position_distributions::Grid,
     refractive_index::RefrIndexConst,
+    spectral_distribution::LaserLines,
     utils::geom_transformation::Isometry,
     OpmDocument,
 };
@@ -21,7 +22,7 @@ fn main() -> OpmResult<()> {
     let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
         pos_dist: Grid::new((millimeter!(9.), millimeter!(9.)), (100, 100))?.into(),
         energy_dist: UniformDist::new(joule!(1.))?.into(),
-        wave_length: nanometer!(1000.),
+        spect_dist: LaserLines::new(vec![(nanometer!(1000.), 1.0)])?.into(),
     });
     let mut source = Source::new("src", light_data_builder);
     source.set_isometry(Isometry::identity())?;
diff --git a/opossum/examples/ghost_focus.rs b/opossum/examples/ghost_focus.rs
index 4896e59b..0f9c4b87 100644
--- a/opossum/examples/ghost_focus.rs
+++ b/opossum/examples/ghost_focus.rs
@@ -12,6 +12,7 @@ use opossum::{
     optic_ports::PortType,
     position_distributions::HexagonalTiling,
     radian,
+    spectral_distribution::LaserLines,
     utils::geom_transformation::Isometry,
     OpmDocument,
 };
@@ -31,7 +32,7 @@ fn main() -> OpmResult<()> {
             false,
         )?
         .into(),
-        wave_length: nanometer!(1000.0),
+        spect_dist: LaserLines::new(vec![(nanometer!(1000.0), 1.0)])?.into(),
     });
     let mut src = Source::new("collimated ray source", light_data_builder);
     src.set_isometry(Isometry::identity())?;
diff --git a/opossum/examples/hhts/hhts.rs b/opossum/examples/hhts/hhts.rs
index 42d9f979..62407dd7 100644
--- a/opossum/examples/hhts/hhts.rs
+++ b/opossum/examples/hhts/hhts.rs
@@ -25,8 +25,8 @@ use opossum::{
     position_distributions::HexagonalTiling,
     radian,
     ray::SplittingConfig,
-    rays::Rays,
     refractive_index::{refr_index_schott::RefrIndexSchott, RefrIndexSellmeier1},
+    spectral_distribution::LaserLines,
     spectrum::Spectrum,
     spectrum_helper::generate_filter_spectrum,
     utils::geom_transformation::Isometry,
@@ -35,17 +35,6 @@ use opossum::{
 use uom::si::f64::Length;
 
 fn main() -> OpmResult<()> {
-    let wvl_1w = nanometer!(1054.0);
-    let wvl_2w = wvl_1w / 2.0;
-
-    let energy_1w = joule!(100.0);
-    let energy_2w = joule!(50.0);
-
-    // let beam_dist_1w = Hexapolar::new(millimeter!(76.05493), 10)?;
-    let beam_dist_1w = HexagonalTiling::new(millimeter!(100.), 10, millimeter!(0., 0.))?;
-    let beam_dist_2w = HexagonalTiling::new(millimeter!(100.), 10, millimeter!(1., 1.))?;
-    // let beam_dist_2w = beam_dist_1w.clone();
-
     let refr_index_hk9l = RefrIndexSellmeier1::new(
         6.14555251E-1,
         6.56775017E-1,
@@ -75,70 +64,26 @@ fn main() -> OpmResult<()> {
     )?;
 
     // apertures
-    // let circle_config = CircleConfig::new(millimeter!(25.4), millimeter!(0., 0.))?;
-    // let a_2inch = Aperture::BinaryCircle(circle_config);
     let circle_config = CircleConfig::new(millimeter!(12.7), millimeter!(0., 0.))?;
     let a_1inch = Aperture::BinaryCircle(circle_config);
 
     // collimated source
-
-    let rays_1w = Rays::new_collimated(
-        wvl_1w,
-        &General2DGaussian::new(
-            energy_1w,
+    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
+        pos_dist: HexagonalTiling::new(millimeter!(100.), 10, millimeter!(0., 0.))?.into(),
+        energy_dist: General2DGaussian::new(
+            joule!(150.0),
             millimeter!(0., 0.),
             millimeter!(60.6389113608, 60.6389113608),
             5.,
             radian!(0.),
             false,
-        )?,
-        &beam_dist_1w,
-    )?;
-    let mut rays_2w = Rays::new_collimated(
-        wvl_2w,
-        &General2DGaussian::new(
-            energy_2w,
-            millimeter!(0., 0.),
-            millimeter!(60.6389113608, 60.6389113608),
-            5.,
-            radian!(0.),
-            false,
-        )?,
-        &beam_dist_2w,
-    )?;
-    // let rays_1w = Rays::new_uniform_collimated(wvl_1w, energy_1w, &beam_dist_1w)?;
-    // let mut rays_2w = Rays::new_uniform_collimated(wvl_2w, energy_2w, &beam_dist_2w)?;
-
-    // point source
-
-    // let rays_1w = Rays::new_hexapolar_point_source(
-    //     millimeter!(
-    //         0.,
-    //         75.0,
-    //         0.,
-    //     ),
-    //     degree!(0.183346572),
-    //     6,
-    //     wvl_1w,
-    //     energy_1w,
-    // )?;
-    // let mut rays_2w = Rays::new_hexapolar_point_source(
-    //     millimeter!(
-    //         0.,
-    //         75.0,
-    //         0.,
-    //     ),
-    //     degree!(0.183346572),
-    //     6,
-    //     wvl_2w,
-    //     energy_2w,
-    // )?;
-
-    let mut rays = rays_1w;
-    rays.add_rays(&mut rays_2w);
+        )?
+        .into(),
+        spect_dist: LaserLines::new(vec![(nanometer!(1053.0), 1.0), (nanometer!(527.0), 0.5)])?
+            .into(),
+    });
 
     let mut scenery = NodeGroup::new("HHT Sensor");
-    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Raw(rays));
     let mut src = Source::new("Source", light_data_builder);
     src.set_isometry(Isometry::identity())?;
     let src = scenery.add_node(src)?;
diff --git a/opossum/examples/prism_dispersion.rs b/opossum/examples/prism_dispersion.rs
index a0e5e96e..010d9f62 100644
--- a/opossum/examples/prism_dispersion.rs
+++ b/opossum/examples/prism_dispersion.rs
@@ -4,6 +4,7 @@ use num::Zero;
 use opossum::{
     analyzers::{AnalyzerType, RayTraceConfig},
     degree,
+    energy_distributions::UniformDist,
     error::OpmResult,
     joule,
     lightdata::{light_data_builder::LightDataBuilder, ray_data_builder::RayDataBuilder},
@@ -11,8 +12,8 @@ use opossum::{
     nodes::{NodeGroup, RayPropagationVisualizer, Source, SpotDiagram, Wedge},
     optic_node::{Alignable, OpticNode},
     position_distributions::Grid,
-    rays::Rays,
     refractive_index::RefrIndexSellmeier1,
+    spectral_distribution::LaserLines,
     utils::geom_transformation::Isometry,
     OpmDocument,
 };
@@ -31,22 +32,14 @@ fn main() -> OpmResult<()> {
     let beam_size_y = millimeter!(10.0);
     let nr_of_rays = 5;
     let wedge_angle_in_degree = 10.0;
-    let mut rays_1w = Rays::new_uniform_collimated(
-        nanometer!(1053.),
-        joule!(1.),
-        &Grid::new((Length::zero(), beam_size_y), (1, nr_of_rays))?,
-    )?;
-
-    let mut rays_2w = Rays::new_uniform_collimated(
-        nanometer!(527.),
-        joule!(1.),
-        &Grid::new((Length::zero(), beam_size_y), (1, nr_of_rays))?,
-    )?;
-
-    rays_1w.add_rays(&mut rays_2w);
 
     let mut scenery = NodeGroup::default();
-    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Raw(rays_1w));
+    let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
+        pos_dist: Grid::new((Length::zero(), beam_size_y), (1, nr_of_rays))?.into(),
+        energy_dist: UniformDist::new(joule!(1.0))?.into(),
+        spect_dist: LaserLines::new(vec![(nanometer!(1053.0), 1.0), (nanometer!(527.0), 1.0)])?
+            .into(),
+    });
     let mut light_src = Source::new("collimated ray source", light_data_builder);
     light_src.set_isometry(Isometry::identity())?;
     let src = scenery.add_node(light_src)?;
diff --git a/opossum/src/console.rs b/opossum/src/console.rs
index 7eeaf45a..18385dc9 100644
--- a/opossum/src/console.rs
+++ b/opossum/src/console.rs
@@ -253,13 +253,13 @@ mod test {
         let path_inexistent_file = Path::new("./files_for_testing/opm/nonexistent.opm");
         let path_inexistent_dir =
             Path::new("./files_for_testing/this_dir_does_not_exist/empty.opm");
-        let path_not_yaml = Path::new("./files_for_testing/opm/is_not_a_opm.txt");
+        let path_not_opm = Path::new("./files_for_testing/opm/is_not_a_opm.txt");
         let path_is_dir = Path::new("./files_for_testing/opm/");
 
         assert_eq!(file_path_is_valid(path_valid), true);
         assert_eq!(file_path_is_valid(path_inexistent_file), false);
         assert_eq!(file_path_is_valid(path_inexistent_dir), false);
-        assert_eq!(file_path_is_valid(path_not_yaml), false);
+        assert_eq!(file_path_is_valid(path_not_opm), false);
         assert_eq!(file_path_is_valid(path_is_dir), false);
     }
     #[test]
@@ -267,7 +267,7 @@ mod test {
         let path_valid = "./files_for_testing/opm/opticscenery.opm";
         let path_inexistent_file = "./files_for_testing/opm/nonexistent.opm";
         let path_inexistent_dir = "./files_for_testing/this_dir_does_not_exist/empty.opm";
-        let path_not_yaml = "./files_for_testing/opm/is_not_an_opm.txt";
+        let path_not_opm = "./files_for_testing/opm/is_not_an_opm.txt";
         let path_is_dir = "./files_for_testing/opm/";
 
         assert_eq!(
@@ -276,7 +276,7 @@ mod test {
         );
         assert_eq!(eval_file_path_input(path_inexistent_file), None);
         assert_eq!(eval_file_path_input(path_inexistent_dir), None);
-        assert_eq!(eval_file_path_input(path_not_yaml), None);
+        assert_eq!(eval_file_path_input(path_not_opm), None);
         assert_eq!(eval_file_path_input(path_is_dir), None);
     }
     #[test]
@@ -295,7 +295,7 @@ mod test {
     }
     #[test]
     fn get_parent_dir_test() {
-        let path_valid = "./files_for_testing/opm/empty_yaml.yaml".to_owned();
+        let path_valid = "./files_for_testing/opm/my_file.opm".to_owned();
         assert_eq!(
             get_parent_dir(&PathBuf::from(path_valid)),
             PathBuf::from("./files_for_testing/opm")
diff --git a/opossum/src/lightdata/ray_data_builder.rs b/opossum/src/lightdata/ray_data_builder.rs
index 65d80058..f57cb47f 100644
--- a/opossum/src/lightdata/ray_data_builder.rs
+++ b/opossum/src/lightdata/ray_data_builder.rs
@@ -4,14 +4,12 @@
 //! This builder allows easier serialization / deserialization in OPM files.
 use std::fmt::Display;
 
+use super::LightData;
 use crate::{
     energy_distributions::EnergyDistType, error::OpmResult, position_distributions::PosDistType,
-    rays::Rays,
+    rays::Rays, spectral_distribution::SpecDistType,
 };
 use serde::{Deserialize, Serialize};
-use uom::si::f64::Length;
-
-use super::LightData;
 
 /// Builder for the generation of [`LightData::Geometric`].
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@@ -25,7 +23,7 @@ pub enum RayDataBuilder {
         /// Energy distribution.
         energy_dist: EnergyDistType,
         /// Wavelength of the rays.
-        wave_length: Length,
+        spect_dist: SpecDistType,
     },
 }
 
@@ -40,10 +38,13 @@ impl RayDataBuilder {
             Self::Collimated {
                 pos_dist,
                 energy_dist,
-                wave_length,
+                spect_dist,
             } => {
-                let rays =
-                    Rays::new_collimated(wave_length, energy_dist.generate(), pos_dist.generate())?;
+                let rays = Rays::new_collimated_with_spectrum(
+                    spect_dist.generate(),
+                    energy_dist.generate(),
+                    pos_dist.generate(),
+                )?;
                 Ok(LightData::Geometric(rays))
             }
         }
@@ -57,11 +58,11 @@ impl Display for RayDataBuilder {
             Self::Collimated {
                 pos_dist,
                 energy_dist,
-                wave_length,
+                spect_dist,
             } => {
                 write!(
                     f,
-                    "Collimated({pos_dist:?}, {energy_dist:?}, {wave_length:?})"
+                    "Collimated({pos_dist:?}, {energy_dist:?}, {spect_dist:?})"
                 )
             }
         }
diff --git a/opossum/src/main.rs b/opossum/src/main.rs
index 7cdc7176..f88aaca8 100644
--- a/opossum/src/main.rs
+++ b/opossum/src/main.rs
@@ -79,7 +79,7 @@ fn create_report_and_data_files(
     let mut output = create_dot_or_report_file_instance(
         report_directory,
         &format!("report_{report_number}"),
-        "yaml",
+        "ron",
         "analysis report",
     )?;
     write!(
diff --git a/opossum/src/nodes/source_helper.rs b/opossum/src/nodes/source_helper.rs
index 6fb1017a..0e585f67 100644
--- a/opossum/src/nodes/source_helper.rs
+++ b/opossum/src/nodes/source_helper.rs
@@ -9,6 +9,7 @@ use crate::{
     optic_node::OpticNode,
     position_distributions::{Grid, Hexapolar},
     rays::Rays,
+    spectral_distribution::LaserLines,
     utils::geom_transformation::Isometry,
 };
 use nalgebra::Point3;
@@ -33,7 +34,7 @@ pub fn round_collimated_ray_source(
     let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
         pos_dist: Hexapolar::new(radius, nr_of_rings)?.into(),
         energy_dist: UniformDist::new(energy)?.into(),
-        wave_length: nanometer!(1000.0),
+        spect_dist: LaserLines::new(vec![(nanometer!(1000.0), 1.0)])?.into(),
     });
     let mut src = Source::new("collimated line ray source", light_data_builder);
     src.set_isometry(Isometry::identity())?;
@@ -58,7 +59,7 @@ pub fn collimated_line_ray_source(
     let light_data_builder = LightDataBuilder::Geometric(RayDataBuilder::Collimated {
         pos_dist: Grid::new((Length::zero(), size_y), (1, nr_of_points_y))?.into(),
         energy_dist: UniformDist::new(energy)?.into(),
-        wave_length: nanometer!(1000.0),
+        spect_dist: LaserLines::new(vec![(nanometer!(1000.0), 1.0)])?.into(),
     });
     let mut src = Source::new("collimated line ray source", light_data_builder);
     src.set_isometry(Isometry::identity())?;
diff --git a/opossum/src/spectral_distribution/gaussian.rs b/opossum/src/spectral_distribution/gaussian.rs
index 962ed775..5fcf1255 100644
--- a/opossum/src/spectral_distribution/gaussian.rs
+++ b/opossum/src/spectral_distribution/gaussian.rs
@@ -1,3 +1,4 @@
+use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 use super::SpectralDistribution;
@@ -8,6 +9,7 @@ use crate::utils::math_distribution_functions::gaussian;
 use itertools::Itertools;
 use kahan::KahanSummator;
 
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Gaussian {
     wvl_range: (Length, Length),
     num_points: usize,
@@ -94,6 +96,11 @@ impl SpectralDistribution for Gaussian {
             .collect_vec())
     }
 }
+impl From<Gaussian> for super::SpecDistType {
+    fn from(g: Gaussian) -> Self {
+        Self::Gaussian(g)
+    }
+}
 #[cfg(test)]
 mod test {
     use crate::{
diff --git a/opossum/src/spectral_distribution/laser_lines.rs b/opossum/src/spectral_distribution/laser_lines.rs
new file mode 100644
index 00000000..5654173a
--- /dev/null
+++ b/opossum/src/spectral_distribution/laser_lines.rs
@@ -0,0 +1,74 @@
+use serde::{Deserialize, Serialize};
+use uom::si::f64::Length;
+
+use crate::error::{OpmResult, OpossumError};
+
+use super::SpectralDistribution;
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+/// A struct representing a collection of laser lines with their respective wavelengths and relative intensities.
+pub struct LaserLines {
+    lines: Vec<(Length, f64)>,
+}
+impl LaserLines {
+    /// Creates a new `LaserLines` instance with the given laser lines.
+    ///
+    /// The given intensities are normalized to sum to 1.0.
+    ///
+    /// # Arguments
+    ///
+    /// * `lines` - A vector of tuples containing the wavelength and intensity of each laser line.
+    ///
+    /// # Errors
+    ///
+    /// This function returns an error if
+    /// * the vector is empty,
+    /// * any wavelength is negative or infinite,
+    /// * any intensity is negative or infinite,
+    /// * the sum of intensities is zero.
+    pub fn new(lines: Vec<(Length, f64)>) -> OpmResult<Self> {
+        // Check if the lines are non-empty and contain valid data
+        if lines.is_empty() {
+            return Err(OpossumError::Other("Laser lines cannot be empty".into()));
+        }
+        for (wavelength, intensity) in &lines {
+            if !wavelength.is_normal() || wavelength.is_sign_negative() {
+                return Err(OpossumError::Other(
+                    "Wavelength must be positive and finite".into(),
+                ));
+            }
+            if !intensity.is_normal() || intensity.is_sign_negative() {
+                return Err(OpossumError::Other(
+                    "Intensity must be positive and finite".into(),
+                ));
+            }
+        }
+        // Normalize the intensities to sum to 1.0
+        let sum_intensity: f64 = lines.iter().map(|(_, intensity)| *intensity).sum();
+        if sum_intensity == 0.0 {
+            return Err(OpossumError::Other(
+                "Sum of intensities cannot be zero".into(),
+            ));
+        }
+        let lines: Vec<(Length, f64)> = lines
+            .into_iter()
+            .map(|(wavelength, intensity)| (wavelength, intensity / sum_intensity))
+            .collect();
+        Ok(Self { lines })
+    }
+}
+impl SpectralDistribution for LaserLines {
+    /// Generates the laser lines.
+    ///
+    /// # Returns
+    ///
+    /// A vector of tuples containing the wavelength and intensity of each laser line.
+    fn generate(&self) -> OpmResult<Vec<(Length, f64)>> {
+        Ok(self.lines.clone())
+    }
+}
+impl From<LaserLines> for super::SpecDistType {
+    fn from(laser_lines: LaserLines) -> Self {
+        Self::LaserLines(laser_lines)
+    }
+}
diff --git a/opossum/src/spectral_distribution/mod.rs b/opossum/src/spectral_distribution/mod.rs
index 1b855950..10ad4323 100644
--- a/opossum/src/spectral_distribution/mod.rs
+++ b/opossum/src/spectral_distribution/mod.rs
@@ -1,9 +1,12 @@
 //! Module for handling spectral distributions
 use crate::error::OpmResult;
+use serde::{Deserialize, Serialize};
 use uom::si::f64::Length;
 
 pub mod gaussian;
+pub mod laser_lines;
 pub use gaussian::Gaussian;
+pub use laser_lines::LaserLines;
 
 pub trait SpectralDistribution {
     /// Creates a Gaussian spectral distribution
@@ -11,3 +14,22 @@ pub trait SpectralDistribution {
     /// This function only propagates errors of the contained functions
     fn generate(&self) -> OpmResult<Vec<(Length, f64)>>;
 }
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+/// Enum representing different types of spectral distributions
+pub enum SpecDistType {
+    Gaussian(gaussian::Gaussian),
+    LaserLines(laser_lines::LaserLines),
+}
+impl SpecDistType {
+    /// Generates the spectral distribution
+    /// # Errors
+    /// This function only propagates errors of the contained functions
+    #[must_use]
+    pub fn generate(&self) -> &dyn SpectralDistribution {
+        match self {
+            Self::Gaussian(g) => g,
+            Self::LaserLines(l) => l,
+        }
+    }
+}
diff --git a/opossum/src/spectrum.rs b/opossum/src/spectrum.rs
index bbb66adc..78d29593 100644
--- a/opossum/src/spectrum.rs
+++ b/opossum/src/spectrum.rs
@@ -999,9 +999,9 @@ mod test {
     #[test]
     fn serialize() {
         let s = prep();
-        let s_yaml = ron::ser::to_string_pretty(&s, ron::ser::PrettyConfig::new().new_line("\n"));
-        assert!(s_yaml.is_ok());
-        assert_eq!(s_yaml.unwrap(),
+        let s_ron =
+            ron::ser::to_string_pretty(&s, ron::ser::PrettyConfig::new().new_line("\n")).unwrap();
+        assert_eq!(s_ron,
         "(\n    data: [\n        (1.0, 0.0),\n        (1.5, 0.0),\n        (2.0, 0.0),\n        (2.5, 0.0),\n        (3.0, 0.0),\n        (3.5, 0.0),\n    ],\n)".to_string());
     }
     #[test]
-- 
GitLab