diff --git a/opossum/Cargo.toml b/opossum/Cargo.toml
index 6ec8db8338d8a83df8f771f031b69cb7868b18bc..33f720c6862a90e767521e2b6087d658121cd5ef 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/apodization.rs b/opossum/examples/apodization.rs
index 571f71bee26b36e58e08896b079200adf4aad78f..9f5c810d3b00f4a4c7a88374f6d9e00f6a813ca7 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/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/folded_telescope.rs b/opossum/examples/folded_telescope.rs
index dbce98b7c2052ed93ff91a0f4379b3e041f6b886..62777a1128e4bc7497e9f1a66a89e2b600585513 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 a0883b9c5dc1906cdaa2264ef83d27ace227a56f..9485fdda9229e797562dcf6f63b30f68e790f92c 100644
--- a/opossum/examples/fresnel_coating.rs
+++ b/opossum/examples/fresnel_coating.rs
@@ -10,8 +10,8 @@ use opossum::{
     optic_node::OpticNode,
     optic_ports::PortType,
     position_distributions::Grid,
-    rays::Rays,
     refractive_index::RefrIndexConst,
+    spectral_distribution::LaserLines,
     utils::geom_transformation::Isometry,
     OpmDocument,
 };
@@ -19,12 +19,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(),
+        spect_dist: LaserLines::new(vec![(nanometer!(1000.), 1.0)])?.into(),
+    });
     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 0c3bfbd884892ec47e2fc7332b924259026a8e06..0f9c4b8764f39725e50ca8d8cd0fa5c5d5e13bb2 100644
--- a/opossum/examples/ghost_focus.rs
+++ b/opossum/examples/ghost_focus.rs
@@ -12,7 +12,7 @@ use opossum::{
     optic_ports::PortType,
     position_distributions::HexagonalTiling,
     radian,
-    rays::Rays,
+    spectral_distribution::LaserLines,
     utils::geom_transformation::Isometry,
     OpmDocument,
 };
@@ -21,21 +21,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(),
+        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())?;
     let i_src = scenery.add_node(src)?;
diff --git a/opossum/examples/group_reverse.rs b/opossum/examples/group_reverse.rs
index 332cadcb1f1e3637cf66631b8cd1d67436e7b424..f6a66ae308f407a2270c00c794f33a458706d23a 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/hhts/hhts.rs b/opossum/examples/hhts/hhts.rs
index 42d9f979d783f7230429464971052582d54dd878..62407dd7028c62e65370a7550b04794ab059ad82 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/inverse_beam_splitter_test.rs b/opossum/examples/inverse_beam_splitter_test.rs
index aa609df5cd658424ffedb0c2885fc1932708b9fb..bea165416adb0ee5d9b6e51812a64b17dc375e64 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/laser_system.rs b/opossum/examples/laser_system.rs
index 6c97f96bce7565e33567618abfdd34912a6765b5..8d7786d9618717321deb3ae5b08526f84c90e53d 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/examples/michaelson.rs b/opossum/examples/michaelson.rs
index 9d77f2c81ed57f439e0d30e18e382d14eae967f8..87018a62c9afbbfa66f56c98d1a316a2b6760191 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 9a65649cc94033bdc6ff0332011f70510b148a35..0000000000000000000000000000000000000000
--- 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/prism_dispersion.rs b/opossum/examples/prism_dispersion.rs
index a0e5e96eab9e463aff5d2b709c62ec053dc1b2fd..010d9f62a70fc3f8e3b8dae97d1fc65399287fb2 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/examples/reference_test.rs b/opossum/examples/reference_test.rs
index e87695dc71fc463646556f5de62021cd1bb704ec..d0ba6e19047dc5d23fc9f18d1b9fe8dc6c600c43 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",
diff --git a/opossum/src/coatings/constant_r.rs b/opossum/src/coatings/constant_r.rs
index d412ab4dc36b6649fb0c9e8b0267a08f180011d7..0b3489f3bfc1f39d909c0718009f30c60d2ef6fd 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 3a6da68b4c6b7f38c535071c07b11fb0ac38300c..d14f695207551d68ccb341b5468b14b0f5c6d54f 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 bebd59856fa55df3e67960a4a868b2db124ef246..00cea71b66d01c5e4d5d8de2a52b5f423689219f 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 ceac2daf17db50a9a2f910ad4ec878b53de57ce6..0c3f2dced19c7539256d4bff6d3a26c9a2edfba1 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/console.rs b/opossum/src/console.rs
index 7eeaf45ae233e70bc7e987834022a3d6ab196252..18385dc96ed1630961bb39c4a7459fb1186c0d5e 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/energy_distributions/general_gaussian.rs b/opossum/src/energy_distributions/general_gaussian.rs
index 9489459d8b871f23217b2f62701e524ef6adc0db..cb37cec8458cd97e232608674228fd5ea98789b3 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 d5a904796d7d54c5360a46debeb395b1960f3d63..22c01756a75f5119afd39cfed062584002172aaf 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 5e7f7233aae49e7a255e0a5a7b70af47adc4cd1d..e5640fe07f69081c5890d1c34c581ae3316a72a5 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/energy_spectrum_builder.rs b/opossum/src/lightdata/energy_spectrum_builder.rs
index b8e46f2b72a5c00dc4b05a3c4029bbfe7020e789..68f392f43f86b2ee35506ec389eb1b731bd72615 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/lightdata/ray_data_builder.rs b/opossum/src/lightdata/ray_data_builder.rs
index 5ee4d67b74b2b012be5729795f0356689b4beaf5..f57cb47f21b5b8585bee378cbed639fe94457bd3 100644
--- a/opossum/src/lightdata/ray_data_builder.rs
+++ b/opossum/src/lightdata/ray_data_builder.rs
@@ -4,16 +4,27 @@
 //! This builder allows easier serialization / deserialization in OPM files.
 use std::fmt::Display;
 
-use crate::{error::OpmResult, rays::Rays};
-use serde::{Deserialize, Serialize};
-
 use super::LightData;
+use crate::{
+    energy_distributions::EnergyDistType, error::OpmResult, position_distributions::PosDistType,
+    rays::Rays, spectral_distribution::SpecDistType,
+};
+use serde::{Deserialize, Serialize};
 
 /// Builder for the generation of [`LightData::Geometric`].
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum RayDataBuilder {
     /// Raw [`Rays`] data.
     Raw(Rays),
+    /// 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.
+        spect_dist: SpecDistType,
+    },
 }
 
 impl RayDataBuilder {
@@ -24,6 +35,18 @@ impl RayDataBuilder {
     pub fn build(self) -> OpmResult<LightData> {
         match self {
             Self::Raw(rays) => Ok(LightData::Geometric(rays)),
+            Self::Collimated {
+                pos_dist,
+                energy_dist,
+                spect_dist,
+            } => {
+                let rays = Rays::new_collimated_with_spectrum(
+                    spect_dist.generate(),
+                    energy_dist.generate(),
+                    pos_dist.generate(),
+                )?;
+                Ok(LightData::Geometric(rays))
+            }
         }
     }
 }
@@ -32,6 +55,16 @@ 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,
+                spect_dist,
+            } => {
+                write!(
+                    f,
+                    "Collimated({pos_dist:?}, {energy_dist:?}, {spect_dist:?})"
+                )
+            }
         }
     }
 }
diff --git a/opossum/src/main.rs b/opossum/src/main.rs
index a3c2396c43949e954cc4d28cf55e7a533bcca25c..f88aaca8c6f9ef7ad43e7a3c7d62d325a5d1fe85 100644
--- a/opossum/src/main.rs
+++ b/opossum/src/main.rs
@@ -79,13 +79,13 @@ 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!(
         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 eb72e85290e722cc0e1cbafcf0bdb99ce88cc366..765083f6846cb01de204ed74bae55b358d364b77 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 772e6e9f475cf65ad114aa3874f63b39cd6fb5cc..f10186324083cf642b4659e7f9041f31cb02415e 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/nodes/source_helper.rs b/opossum/src/nodes/source_helper.rs
index 72a5875ef07a8f99015bf09020a9a1f84d346f39..0e585f6777988bc795b2ff578b647708691a223c 100644
--- a/opossum/src/nodes/source_helper.rs
+++ b/opossum/src/nodes/source_helper.rs
@@ -2,12 +2,14 @@
 //! 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,
     optic_node::OpticNode,
     position_distributions::{Grid, Hexapolar},
     rays::Rays,
+    spectral_distribution::LaserLines,
     utils::geom_transformation::Isometry,
 };
 use nalgebra::Point3;
@@ -29,12 +31,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(),
+        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())?;
     Ok(src)
@@ -55,12 +56,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(),
+        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())?;
     Ok(src)
diff --git a/opossum/src/opm_document.rs b/opossum/src/opm_document.rs
index 3eb361ee2e080c51e831ca88ee77335bc8b54ea9..049fa3bb86612fa1fdc1c3f891edf9ec4dc04238 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 70d89d45de6b798289ab588089a671c5bc94ab7f..96a6c68dfac1fdeb4753355fb93691ed32dc8950 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/position_distributions/fibonacci.rs b/opossum/src/position_distributions/fibonacci.rs
index a44b72fe101624ff8406839a6ae27adb0fb40a61..cb45fc9b370835a4227b24d9beaac0d2eadb541b 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, PartialEq)]
 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, PartialEq)]
 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 2bda4bebb712e4733c8e7661f8616b8f8b59e9cf..0ef7445785d46d83e8952245bc6bba2097949587 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, PartialEq)]
 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 a6499eeb5117d7fb606b3ad8e5e75227c3ffc7a7..3fccec786df861d4912c2c0d8894520dcda05e9d 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, PartialEq)]
 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 5b7214b0b32e0aafc9a74d8a9830c2a50bbfa032..95e64928871affad87b20b0efb925203f68d9c94 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, PartialEq)]
 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 4a44bdbf5e06b1df04e2dc813528449b2acbc1cf..097ff23dfca710cd3d927a6f3f8cb4b6291c190f 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,37 @@ 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, PartialEq)]
+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),
+}
+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 96f4163f13a449d621fc13fca536edc8ee53f3a0..b51661eb0ad091dc50e8e994249b122b9299014b 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, PartialEq)]
 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 c164006ddc101011be7aa747a3f2a761f8c81b8a..bbcf8fa385438e50aac1d2f61090d379cd9fb0da 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, PartialEq)]
 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::*;
diff --git a/opossum/src/refractive_index/refr_index_conrady.rs b/opossum/src/refractive_index/refr_index_conrady.rs
index 4c6859e006d4eeda0c815637248b8106147e1268..915ed5a3bef1215a0e5f378eaff12070d36f434a 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 5f999e249ed1380a6d12b417c8a9a5e6e8f5e98e..f20a55730d084a7ee581777bc82c5be17060ac55 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 888fedd2ca0b78ab3c20c57875470148046b6a02..19a6780eca75092e740e153f7df1878f850955d7 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 8f1d995528ba6fa781982526da4ef520643bd754..85f250a4e3c39d1109e00de605cd7b79f2288ef8 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;
diff --git a/opossum/src/spectral_distribution/gaussian.rs b/opossum/src/spectral_distribution/gaussian.rs
index 962ed775b81524a9143d3b10b8aa846a310f3ec8..5fcf12551036462a65612b47c2cfcc9d92c7f021 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 0000000000000000000000000000000000000000..5654173ad1b4bf1878327112bc36df884fedbea6
--- /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 1b85595049428e4f4a900e05739c521ddb296bde..10ad43232a14743118ca4f78b4a5626bfc16aa93 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 c2cc3231d6379d584ac2d784957619c64034dbb7..78d295937f841ee2ec2800705f1795f684aa15ef 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());
-        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());
+        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]
     fn deserialize() {