From 6c6fe7ee52593f31932a9a155a98222899653b9d Mon Sep 17 00:00:00 2001
From: lbocklag <lars.bocklage@desy.de>
Date: Fri, 13 Dec 2024 12:54:53 +0100
Subject: [PATCH 1/2] added natural abundance to MoessbauerIsotope for precise
 average mass calculation in material

---
 docs/source/news/news.rst             |  2 +
 src/nexus/clib/classes/material.cpp   | 14 ++++-
 src/nexus/clib/classes/moessbauer.cpp | 21 ++++++-
 src/nexus/clib/classes/moessbauer.h   | 12 +++-
 src/nexus/clib/classes/moessbauer.i   |  7 ++-
 src/nexus/clib/classes/sample.cpp     |  4 +-
 src/nexus/lib/moessbauer.py           | 88 ++++++++++++++++++++-------
 7 files changed, 115 insertions(+), 33 deletions(-)

diff --git a/docs/source/news/news.rst b/docs/source/news/news.rst
index a7ddd4dd..e2d92974 100644
--- a/docs/source/news/news.rst
+++ b/docs/source/news/news.rst
@@ -68,6 +68,8 @@ API changes:
    It enables to use other than least-square prolems, like log-likelihood estimators.
    It does not work with the ceres solver modules, which always solve least-square problems.
 
+#. :attr:`natural_abundance` attribute added to :class:`MoessbauerIsotope`.
+
 
 changes:
 
diff --git a/src/nexus/clib/classes/material.cpp b/src/nexus/clib/classes/material.cpp
index 531b0f68..7d028c20 100644
--- a/src/nexus/clib/classes/material.cpp
+++ b/src/nexus/clib/classes/material.cpp
@@ -85,17 +85,25 @@ void Material::UpdateComposition()
 
   for (const CompositionEntry& element : composition)
   {
-    double mass = element_maps::atomic_mass_map.find(element.first)->second;
+    double mass = element_maps::atomic_mass_map.find(element.first)->second;  // averaged atomic mass over natural abundance
 
+    // correct mass for given abundance of resonant isotope
+    // X * M_resonant_isotope + Y M_other_isotopes = A (averaged atomic mass)
+    // X + Y = 1
+    // M_other_isotopes = (A - X * M_resonant_isotope) / (1 - X)
     if (element.first == isotope->element)
-      mass = abundance->value * isotope->mass + (1.0 - abundance->value) * mass;
+    {
+      const double mass_non_res_isotopes = (mass - isotope->natural_abundance * isotope->mass) / (1.0 - isotope->natural_abundance);
+      
+      mass = abundance->value * isotope->mass + (1.0 - abundance->value) * mass_non_res_isotopes;
+    }
 
     average_mole_mass += element.second / sum * mass;
   }
 
   // density in g/cm^3, convert to g/m^3 (1e6)
   // average_mole_mass is in g/mole
-  total_number_density = constants::kNa * (density->value * 1.0e6) / (average_mole_mass);  // 1/m^3
+  total_number_density = constants::kNa * (density->value * 1.0e6) / average_mole_mass;  // 1/m^3
 
   for (const CompositionEntry& element : composition)
   {
diff --git a/src/nexus/clib/classes/moessbauer.cpp b/src/nexus/clib/classes/moessbauer.cpp
index 7314bec8..ca396133 100644
--- a/src/nexus/clib/classes/moessbauer.cpp
+++ b/src/nexus/clib/classes/moessbauer.cpp
@@ -44,7 +44,8 @@ MoessbauerIsotope::MoessbauerIsotope(
   Var* const gfactor_excited_,
   Var* const quadrupole_ground_,
   Var* const quadrupole_excited_,
-  Var* const interference_term_
+  Var* const interference_term_,
+  const double natural_abundance_
 ) : 
   isotope(isotope_),
   element(element_),
@@ -60,9 +61,23 @@ MoessbauerIsotope::MoessbauerIsotope(
   gfactor_excited(gfactor_excited_),
   quadrupole_ground(quadrupole_ground_),
   quadrupole_excited(quadrupole_excited_),
-  interference_term(interference_term_)
+  interference_term(interference_term_),
+  natural_abundance(natural_abundance_)
 {
   Update();
+
+  if (natural_abundance < 0.0)
+  {
+    errors::ErrorMessage("MoessbauerIsotope", "natural abundance smaller 0. Set to 0.");
+
+    natural_abundance = 0.0;
+  }
+  else if (natural_abundance > 1.0)
+  {
+    errors::ErrorMessage("MoessbauerIsotope", "natural abundance larger 1. Set to 1.");
+
+    natural_abundance = 1.0;
+  }
 }
 
 MoessbauerIsotope MoessbauerIsotope::Copy() const
@@ -172,4 +187,4 @@ void MoessbauerIsotope::PopulateFitHandler(OptimizerHandler* fit_handler)
   fit_handler->PrepareFitVariable(quadrupole_excited);
 
   fit_handler->PrepareFitVariable(interference_term);
-}
\ No newline at end of file
+}
diff --git a/src/nexus/clib/classes/moessbauer.h b/src/nexus/clib/classes/moessbauer.h
index ce52513c..235d2d7e 100644
--- a/src/nexus/clib/classes/moessbauer.h
+++ b/src/nexus/clib/classes/moessbauer.h
@@ -115,6 +115,10 @@ Args:
      .. versionchanged:: 1.2.0
         :class:`Var` input possible now.
 
+   natural_abundance (float): Natural abundance of the isotope.
+
+     .. versionadded:: 2.0.0
+
 
 Attributes:
    isotope (string): User identifier. Should be given in the format "mass number-element", e.g. "57-Fe".
@@ -214,6 +218,9 @@ Attributes:
      It is given by :math:`\mu_g = g_g \mu_N I_g`, where :math:`I_g` is given in units of :math:`\hbar`.
    magnetic_moment_excited (float): Magnetic moment of the excited state (eV/T).
      It is given by :math:`\mu_e = g_e \mu_N I_e`, where :math:`I_e` is given in units of :math:`\hbar`.
+   natural_abundance (float): Natural abundance of the isotope.
+
+     .. versionadded:: 2.0.0
 */
 struct MoessbauerIsotope {
 public:
@@ -232,7 +239,8 @@ public:
     Var* const gfactor_excited,
     Var* const quadrupole_ground,
     Var* const quadrupole_excited,
-    Var* const interference_term
+    Var* const interference_term,
+    const double natural_abundance
   );
   
   /**
@@ -278,6 +286,8 @@ public:
 
   mutable Var* interference_term = nullptr;
 
+  double natural_abundance = 0.0;
+
   // derived parameters
   int atomic_number = 0;
   
diff --git a/src/nexus/clib/classes/moessbauer.i b/src/nexus/clib/classes/moessbauer.i
index 54a134fd..2a5d5ff9 100644
--- a/src/nexus/clib/classes/moessbauer.i
+++ b/src/nexus/clib/classes/moessbauer.i
@@ -43,7 +43,8 @@
                gfactor_excited = 0.0,
                quadrupole_ground = 0.0,
                quadrupole_excited = 0.0,
-               interference_term = 0.0):
+               interference_term = 0.0,
+               natural_abundance = 0.0):
 
     if isinstance(internal_conversion, (int, float)):
       internal_conversion = Var(internal_conversion, 0.0, 1.0e9, False, "")
@@ -88,7 +89,8 @@
                                                                            gfactor_excited,
                                                                            quadrupole_ground,
                                                                            quadrupole_excited,
-                                                                           interference_term)
+                                                                           interference_term,
+                                                                           natural_abundance)
         )
 %}
 
@@ -180,6 +182,7 @@
     output += "  .magnetic_moment_ground (eV/T) = {}\n".format(self.magnetic_moment_ground)
     output += "  .magnetic_moment_excited (eV/T) = {}\n".format(self.magnetic_moment_excited)
     output += "  .nuclear_cross_section (converted to kbarn) = {}\n".format(self.nuclear_cross_section*1e25)
+    output += "  .natural_abundance = {}\n".format(self.natural_abundance)
     return output
   }
 }
diff --git a/src/nexus/clib/classes/sample.cpp b/src/nexus/clib/classes/sample.cpp
index ebe1d3ee..3b2d9699 100644
--- a/src/nexus/clib/classes/sample.cpp
+++ b/src/nexus/clib/classes/sample.cpp
@@ -59,9 +59,9 @@ namespace sample_definitions
   static Composition kAirComposition = { std::make_pair("N", 1.562), std::make_pair("O", 0.42), std::make_pair("C", 0.0003), std::make_pair("Ar", 0.0094) };
   static Var* const kDensity = new Var(0.0012041, 0.0, 0.0012041, false);
   static Var* const kAbundance = new Var(0.0, 0.0, 0.0, false);
-  static Var* const kMoessbuqerDummy = new Var(0.0, 0.0, 0.0, false);
+  static Var* const kMoessbauerDummy = new Var(0.0, 0.0, 0.0, false);
   static Var* const kLambMoessbauer = new Var(0.0, 0.0, 0.0, false);
-  static MoessbauerIsotope* const kNone = new MoessbauerIsotope("none", "", 0.0, 0.0, 0.0, kMoessbuqerDummy, Multipolarity::M1, kMoessbuqerDummy, 0.0, 0.0, kMoessbuqerDummy, kMoessbuqerDummy, kMoessbuqerDummy, kMoessbuqerDummy, kMoessbuqerDummy);
+  static MoessbauerIsotope* const kNone = new MoessbauerIsotope("none", "", 0.0, 0.0, 0.0, kMoessbauerDummy, Multipolarity::M1, kMoessbauerDummy, 0.0, 0.0, kMoessbauerDummy, kMoessbauerDummy, kMoessbauerDummy, kMoessbauerDummy, kMoessbauerDummy, 0.0);
   static Material* const kAirMaterial = new Material(kAirComposition, kDensity, kNone, kAbundance, kLambMoessbauer, {}, "air");
   static Var* const kThickness = new Var(0.0, 0.0, 0.0, false);
   static Var* const kRoughness = new Var(0.0, 0.0, 0.0, false);
diff --git a/src/nexus/lib/moessbauer.py b/src/nexus/lib/moessbauer.py
index e06cc6e9..df8e9c59 100644
--- a/src/nexus/lib/moessbauer.py
+++ b/src/nexus/lib/moessbauer.py
@@ -49,7 +49,9 @@ none = clib.MoessbauerIsotope(
   quadrupole_ground = 0.0,
   quadrupole_excited = 0.0,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.0
   )
 r"""
 none
@@ -79,7 +81,9 @@ K40 = clib.MoessbauerIsotope(
   quadrupole_ground = -0.061,
   quadrupole_excited = 1.0e-200,  # unkwonwn
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 1.17e-4
   )
 r"""
 K-40
@@ -110,7 +114,9 @@ Sc45 = clib.MoessbauerIsotope(
   quadrupole_ground = -0.22,
   quadrupole_excited = 0.318,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 1.0
   )
 r"""
 Sc-45
@@ -142,7 +148,9 @@ Fe57 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.0,
   quadrupole_excited = 0.187,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.02119
   )
 r"""
 Fe-57
@@ -174,7 +182,9 @@ Ni61 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.162,
   quadrupole_excited = -0.2,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.011399
   )
 r"""
 Ni-61
@@ -205,7 +215,9 @@ Zn67 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.15,
   quadrupole_excited = 0.0,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.0404
   )
 r"""
 Zn-67
@@ -238,7 +250,9 @@ Ge73 = clib.MoessbauerIsotope(
   quadrupole_ground = -0.173,
   quadrupole_excited = -0.4,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.0776
   )
 r"""
 Ge-73
@@ -268,7 +282,9 @@ Kr83 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.253,
   quadrupole_excited = 0.495,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.115
   )
 r"""
 Kr-83
@@ -299,7 +315,9 @@ Ru99 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.079,
   quadrupole_excited = 0.231,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.1276
   )
 r"""
 Ru-99
@@ -330,7 +348,9 @@ Sn119 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.0,
   quadrupole_excited = -0.094,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.0859
   )
 r"""
 Sn-119
@@ -360,7 +380,9 @@ Sb121 = clib.MoessbauerIsotope(
   quadrupole_ground = -0.36,
   quadrupole_excited = -0.48,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.5721
   )
 r"""
 Sb-121
@@ -392,7 +414,9 @@ Te125 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.0,
   quadrupole_excited = -0.31,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.0707
   )
 r"""
 Te-125
@@ -425,7 +449,9 @@ I127 = clib.MoessbauerIsotope(
   quadrupole_ground = -0.79,
   quadrupole_excited = -0.71,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 1.0
   )
 r"""
 I-127
@@ -457,7 +483,9 @@ Sm149 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.075,
   quadrupole_excited = 1.01,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.1382
   )
 r"""
 Sm-149
@@ -488,7 +516,9 @@ Eu151 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.903,
   quadrupole_excited = 1.28,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.4781
   )
 r"""
 Eu-151
@@ -518,7 +548,9 @@ Gd157 = clib.MoessbauerIsotope(
   quadrupole_ground = 1.38,
   quadrupole_excited = 2.46,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.1565
   )
 r"""
 Gd-157
@@ -549,7 +581,9 @@ Dy161 = clib.MoessbauerIsotope(
   quadrupole_ground = 2.507,
   quadrupole_excited = 2.506,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.18889
   )
 r"""
 Dy-161
@@ -580,7 +614,9 @@ Tm169 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.0,
   quadrupole_excited = -1.3,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 1.0
   )
 r"""
 Tm-169
@@ -609,7 +645,9 @@ Yb172 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.0,
   quadrupole_excited = 2.16,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.21686
 )
 r"""
 Yb-172
@@ -641,7 +679,9 @@ Yb174 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.0,
   quadrupole_excited = 2.12,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.32025
 )
 r"""
 Yb-174
@@ -673,7 +713,9 @@ Ta181 = clib.MoessbauerIsotope(
   quadrupole_ground = 3.17,
   quadrupole_excited = 3.71,
 
-  interference_term = -0.08  # Tramell and Hannon, Phys.Rev. 180, 337 (1969)
+  interference_term = -0.08,  # Tramell and Hannon, Phys.Rev. 180, 337 (1969)
+
+  natural_abundance = 0.9998799
   )
 r"""
 Ta-181
@@ -703,7 +745,9 @@ Ir193 = clib.MoessbauerIsotope(
   quadrupole_ground = 0.78,
   quadrupole_excited = 0.0,
 
-  interference_term = 0.0
+  interference_term = 0.0,
+
+  natural_abundance = 0.627
   )
 r"""
 Ir-193
-- 
GitLab


From fd2e6e6c18b0185e9c078665ade6607c6b48f0ba Mon Sep 17 00:00:00 2001
From: lbocklag <lars.bocklage@desy.de>
Date: Fri, 13 Dec 2024 12:56:25 +0100
Subject: [PATCH 2/2] updated Swig file

---
 src/nexus/clib/classes/moessbauer.i | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/nexus/clib/classes/moessbauer.i b/src/nexus/clib/classes/moessbauer.i
index 2a5d5ff9..a98d1403 100644
--- a/src/nexus/clib/classes/moessbauer.i
+++ b/src/nexus/clib/classes/moessbauer.i
@@ -107,7 +107,7 @@
     if attr not in ("this", "_internal_conversion", "_mixing_ratio_E2M1", "_gfactor_ground", "_gfactor_excited", "_quadrupole_ground", "_quadrupole_excited", "_interference_term") and not hasattr(self, attr):
       raise Exception("MoessbauerIsotope has no attribute {}".format(attr))
 
-    if attr in ("internal_conversion", "mixing_ratio_E2M1", "gfactor_ground", "gfactor_excited", "quadrupole_ground", "quadrupole_excited", "interference_term") and isinstance(val, (int, float)):
+    if attr in ("internal_conversion", "mixing_ratio_E2M1", "gfactor_ground", "gfactor_excited", "quadrupole_ground", "quadrupole_excited", "interference_term", "natural_abundance") and isinstance(val, (int, float)):
       setattr(getattr(self, attr), "value", val)
       return
 
-- 
GitLab