diff --git a/.gitignore b/.gitignore
index 25de861a1db6009fcd0c62652d0e745b4547d292..fc4b5eaa0e905f92701264354a082eb56ebfe503 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,9 @@ lib/
 bin/
 obj/
 binarycpython_snippets
+-1.log
+
+
 # Standard template
 *.org~
 *.so
diff --git a/HOW_TO_CONTRIBUTE b/HOW_TO_CONTRIBUTE
index d7597eef602513e45d1c3ed6e7691acf613b3143..d9f7329c6704825e4c7ae154b9a89d628a84a7bb 100644
--- a/HOW_TO_CONTRIBUTE
+++ b/HOW_TO_CONTRIBUTE
@@ -1 +1,6 @@
-You can contribute by contacting me (David) or rob and asking what needs to be done
+You can contribute by contacting me (David) or Rob and asking what needs to be done, 
+
+Or resolve one of the many TODOs that are scattered throughout the code.
+
+Probably best to discuss with us first before making pull requests though.
+
diff --git a/binarycpython/utils/custom_logging_functions.py b/binarycpython/utils/custom_logging_functions.py
index e3be2e16ba91c5bfbcaae024c8fe48fdf0aa5156..a17fcbb7adf42efcb1d0e34f233fc5eb5e1c3a40 100644
--- a/binarycpython/utils/custom_logging_functions.py
+++ b/binarycpython/utils/custom_logging_functions.py
@@ -203,7 +203,7 @@ def from_binary_c_config(config_file: str, flag: str) -> str:
 
     # convert and chop off newline
     res = res.decode("utf-8").rstrip()
-    print(res)
+    # print(res)
 
     return res
 
@@ -360,7 +360,7 @@ def compile_shared_lib(
             )
         )
     res = subprocess.check_output("{command}".format(command=command), shell=True)
-    print(res)
+    # print(res)
     if verbose > 0:
         if res:
             print("Output of compilation command:\n{}".format(res))
diff --git a/binarycpython/utils/distribution_functions.py b/binarycpython/utils/distribution_functions.py
index 3d0ea4ca3df13df4cb93a118f2591254ffc81713..65880d59e658c57742f69d8ceeafc77c2462ed2d 100644
--- a/binarycpython/utils/distribution_functions.py
+++ b/binarycpython/utils/distribution_functions.py
@@ -945,15 +945,11 @@ def cosmic_SFH_madau_dickinson2014(z):
 # known value if we try to interpolate outside of the tables.
 # There are still some open tasks and improvements that can be made:
 #
-# TODO: check all the raise ValueErrors to make them more appropriate
-# TODO: Put the json checking stuff in a different function
-# TODO: Clean up the logging
 # TODO: Solve the memory issues that are present. 
 #    Are the interpolators not cleaned? 
 # TODO: Parallellize the setting up of the interpolators
 # TODO: Generalize the code such that we can input other/newer tables
 
-
 ########################################################################
 
 import py_rinterpolate
@@ -1128,7 +1124,7 @@ def Moe_de_Stefano_2017_multiplicity_fractions(options, verbosity=0):
 
     if options["multiplicity_model"] == "Poisson":
         multiplicity = Moecache["rinterpolator_multiplicity"].interpolate(
-            [np.log10(options["M1"])]
+            [np.log10(options["M_1"])]
         )[0]
 
         # Fill the multiplicity array
@@ -1154,7 +1150,7 @@ def Moe_de_Stefano_2017_multiplicity_fractions(options, verbosity=0):
         for n in range(3):
             full_fractions_array[n] = (
                 Moecache["rinterpolator_multiplicity"].interpolate(
-                    [np.log10(options["M1"])]
+                    [np.log10(options["M_1"])]
                 )[n + 1])
 
         # Set last value
@@ -1264,9 +1260,6 @@ def build_q_table(options, m, p, verbosity=0):
                     verbosity,
                     _MS_VERBOSITY_LEVEL,
                 )
-                Moecache["rinterpolator_q_given_{}_log10{}".format(m, p)].destroy()
-                gc.collect()
-
 
     #
     if not incache:
@@ -1278,7 +1271,7 @@ def build_q_table(options, m, p, verbosity=0):
         #     0
         # ]  # TODO: this lower range must not be lower than Mmin.
         # print("build_q_table qmin: {}".format(qmin))
-        qmin = options['Mmin']/options['M1']
+        qmin = options['Mmin']/options['M_1']
         print("build_q_table qmin: {}".format(qmin))
 
         # qmax = maximum_mass_ratio_for_RLOF(options[m], options[p])
@@ -1823,7 +1816,7 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
     prob_dict['multiplicity'] = multiplicity_probability
     verbose_print(
         "\tM&S: Moe_de_Stefano_2017_pdf: Appended multiplicity (mass1 = {}) probability ({}) to the prob dict ({})".format(
-            options["M1"], prob_dict['multiplicity'], prob_dict
+            options["M_1"], prob_dict['multiplicity'], prob_dict
         ),
         verbosity,
         _MS_VERBOSITY_LEVEL,
@@ -1837,11 +1830,11 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
 
     # TODO: Create an n-part-powerlaw method that can have breakpoints and slopes. I'm using a three-part powerlaw now.
     # TODO: is this actually the correct way? putting the M1 in there? Do we sample in logspace?
-    M1_probability = Kroupa2001(options["M1"]) * options["M1"]
-    prob_dict['M1'] = M1_probability
+    M1_probability = Kroupa2001(options["M_1"]) * options["M_1"]
+    prob_dict['M_1'] = M1_probability
     verbose_print(
         "\tM&S: Moe_de_Stefano_2017_pdf: Appended Mass (m={}) probability ({}) to the prob dict ({})".format(
-            options["M1"], prob_dict['M1'], prob_dict
+            options["M_1"], prob_dict['M_1'], prob_dict
         ),
         verbosity,
         _MS_VERBOSITY_LEVEL,
@@ -1876,7 +1869,7 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
             )
 
         # Make a table storing Moe's data for q distributions
-        if options.get("M2", None) or options.get("M3", None) or options.get("M4", None):
+        if options.get("M_2", None) or options.get("M_3", None) or options.get("M_4", None):
             if not Moecache.get("rinterpolator_q", None):
                 Moecache["rinterpolator_q"] = py_rinterpolate.Rinterpolate(
                     table=Moecache["q_distributions"],  # Contains the table of data
@@ -1913,30 +1906,30 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
         ###############
         # Calculation for period of the binary
 
-        if options.get("M2", None):
+        if options.get("M_2", None):
             # Separation of the inner binary
             options["sep"] = calc_sep_from_period(
-                options["M1"], options["M2"], options["P"]
+                options["M_1"], options["M_2"], options["P"]
             )
             # TODO: add check for min_P with instant RLOF?
             # TODO: Actually use the value above.
             # Total mass inner binary:
-            options["M1+M2"] = options["M1"] + options["M2"]
+            options["M_1+M_2"] = options["M_1"] + options["M_2"]
 
         # Calculate P integral or use cached value
         calc_P_integral(
-            options, 1, "P_integrals", "rinterpolator_log10P", "M1", verbosity
+            options, 1, "P_integrals", "rinterpolator_log10P", "M_1", verbosity
         )
 
         # Set probabilty for P1
         p_val = Moecache["rinterpolator_log10P"].interpolate(
-            [np.log10(options["M1"]), np.log10(options["P"])]
+            [np.log10(options["M_1"]), np.log10(options["P"])]
         )[0]
-        p_val = p_val / Moecache["P_integrals"][options["M1"]]
+        p_val = p_val / Moecache["P_integrals"][options["M_1"]]
         prob_dict['P'] = p_val
         verbose_print(
             "\tM&S: Moe_de_Stefano_2017_pdf: Appended period (m={}, P={}) probability ({}) to the prob list ({})".format(
-                options["M1"], options["P"], prob_dict['P'], prob_dict
+                options["M_1"], options["P"], prob_dict['P'], prob_dict
             ),
             verbosity,
             _MS_VERBOSITY_LEVEL,
@@ -1951,11 +1944,11 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
         # we need to construct the q table for the given M1
         # subject to qmin = Mmin/M1
 
-        if options.get("M2", None):
+        if options.get("M_2", None):
             # Build the table for q
-            primary_mass = options["M1"]
-            secondary_mass = options["M2"]
-            m_label = "M1"
+            primary_mass = options["M_1"]
+            secondary_mass = options["M_2"]
+            m_label = "M_1"
             p_label = "P"
 
             # Construct the q table
@@ -1975,9 +1968,9 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
             prob_dict['q'] = q_prob
             verbose_print(
                 "\tM&S: Moe_de_Stefano_2017_pdf: appended mass ratio (M={} P={} q={}) probability ({}) to the prob list ({}) ".format(
-                    options["M1"],
+                    options["M_1"],
                     options["P"],
-                    options["M2"] / options["M1"],
+                    options["M_2"] / options["M_1"],
                     prob_dict['q'],
                     prob_dict,
                 ),
@@ -1997,19 +1990,19 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
             if not options['ecc']==None:
                 # Calculate ecc integral or use cached value
                 calc_e_integral(
-                    options, "ecc_integrals", "rinterpolator_e", "M1", "P", verbosity
+                    options, "ecc_integrals", "rinterpolator_e", "M_1", "P", verbosity
                 )
-                mass_period_string = "{}_{}".format(options["M1"], options["P"])
+                mass_period_string = "{}_{}".format(options["M_1"], options["P"])
 
                 # Set probabilty for ecc
                 ecc_val = Moecache["rinterpolator_e"].interpolate(
-                    [np.log10(options["M1"]), np.log10(options["P"]), options["ecc"]]
+                    [np.log10(options["M_1"]), np.log10(options["P"]), options["ecc"]]
                 )[0]
                 ecc_val = ecc_val / Moecache["ecc_integrals"][mass_period_string]
                 prob_dict['ecc'] = ecc_val
                 verbose_print(
                     "\tM&S: Moe_de_Stefano_2017_pdf: Appended eccentricity (m={}, P={}, ecc={}) probability ({}) to the prob list ({})".format(
-                        options["M1"], options["P"], options["ecc"], prob_dict['ecc'], prob_dict
+                        options["M_1"], options["P"], options["ecc"], prob_dict['ecc'], prob_dict
                     ),
                     verbosity,
                     _MS_VERBOSITY_LEVEL,
@@ -2035,7 +2028,7 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
 
             # TODO: Is this a correct assumption?
             max_sep = 10.0 * options["sep"] * (1.0 + options["ecc"])
-            min_P2 = calc_period_from_sep(options["M1+M2"], options["mmin"], max_sep)
+            min_P2 = calc_period_from_sep(options["M_1+M_2"], options["mmin"], max_sep)
 
             if options["P2"] < min_P2:
                 # period is too short : system is not hierarchical
@@ -2056,7 +2049,7 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
                 # hence the separation between the outer star
                 # and inner binary
                 options["sep2"] = calc_sep_from_period(
-                    options["M3"], options["M1+M2"], options["P2"]
+                    options["M_3"], options["M_1+M_2"], options["P2"]
                 )
 
                 # Check for cached value of P integral or calculate
@@ -2065,19 +2058,19 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
                     min_P2,
                     "P2_integrals",
                     "rinterpolator_log10P",
-                    "M1+M2",
+                    "M_1+M_2",
                     verbosity,
                 )
 
                 # Add the probability
                 p_val = Moecache["rinterpolator_log10P"].interpolate(
-                    [np.log10(options["M1+M2"]), np.log10(options["P2"])]
+                    [np.log10(options["M_1+M_2"]), np.log10(options["P2"])]
                 )[0]
-                p_val = p_val / Moecache["P2_integrals"][options["M1+M2"]]
+                p_val = p_val / Moecache["P2_integrals"][options["M_1+M_2"]]
                 prob_dict['P2'] = p_val
                 verbose_print(
                     "\tM&S: Moe_de_Stefano_2017_pdf: Appended period2 (m1={} m2={}, P2={}) probability ({}) to the prob list ({})".format(
-                        options["M1"], options["M2"], options["P2"], prob_dict['P2'], prob_dict
+                        options["M_1"], options["M_2"], options["P2"], prob_dict['P2'], prob_dict
                     ),
                     verbosity,
                     _MS_VERBOSITY_LEVEL,
@@ -2093,9 +2086,9 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
                 # subject to qmin = Mmin/(M1+M2)
 
                 # Set the variables for the masses and their names
-                primary_mass = options["M1+M2"]
-                secondary_mass = options["M3"]
-                m_label = "M1+M2"
+                primary_mass = options["M_1+M_2"]
+                secondary_mass = options["M_3"]
+                m_label = "M_1+M_2"
                 p_label = "P2"
 
                 # Build q table
@@ -2112,9 +2105,9 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
                 ].interpolate([secondary_mass / primary_mass])[0]
                 prob_dict['q2'] = q2_val
                 verbose_print(
-                    "\tM&S: Moe_de_Stefano_2017_pdf: appended mass ratio (M1+M2={} M3={} P={} q={}) probability ({}) to the prob list ({}) ".format(
-                        options["M1+M2"],
-                        options["M3"],
+                    "\tM&S: Moe_de_Stefano_2017_pdf: appended mass ratio (M_1+M_2={} M_3={} P={} q={}) probability ({}) to the prob list ({}) ".format(
+                        options["M_1+M_2"],
+                        options["M_3"],
                         options["P"],
                         secondary_mass / primary_mass,
                         prob_dict['q2'],
@@ -2142,7 +2135,7 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
                     # TODO: fix this here
                     max_sep3 = 0.2 * options["sep2"] * (1.0 + options["ecc2"])
                     max_per3 = calc_period_from_sep(
-                        options["M1+M2"], options["mmin"], max_sep3
+                        options["M_1+M_2"], options["mmin"], max_sep3
                     )
 
                     # Calculate P integral or use the cached value
@@ -2152,19 +2145,19 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
                         min_P2,
                         "P2_integrals",
                         "rinterpolator_log10P",
-                        "M1+M2",
+                        "M_1+M_2",
                         verbosity,
                     )
 
                     # Set probability
                     p_val = Moecache["rinterpolator_log10P"].interpolate(
-                        [np.log10(options["M1+M2"]), np.log10(options["P2"])]
+                        [np.log10(options["M_1+M_2"]), np.log10(options["P2"])]
                     )[0]
-                    p_val = p_val / Moecache["P2_integrals"][options["M1+M2"]]
+                    p_val = p_val / Moecache["P2_integrals"][options["M_1+M_2"]]
                     prob_dict['P3'] = p_val
                     verbose_print(
-                        "\tM&S: Moe_de_Stefano_2017_pdf: Appended period2 (M=4) (m1={} m2={}, P2={}) probability ({}) to the prob list ({})".format(
-                            options["M1"], options["M2"], options["P2"], prob_dict['P3'], prob_dict
+                        "\tM&S: Moe_de_Stefano_2017_pdf: Appended period2 (M=4) (M_1={} M_2={}, P2={}) probability ({}) to the prob list ({})".format(
+                            options["M_1"], options["M_2"], options["P2"], prob_dict['P3'], prob_dict
                         ),
                         verbosity,
                         _MS_VERBOSITY_LEVEL,
@@ -2181,9 +2174,9 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
                     # Make a table storing Moe's data for q distributions
 
                     # Build the table for q2
-                    primary_mass = options["M1+M2"]
-                    secondary_mass = options["M3"]
-                    m_label = "M1+M2"
+                    primary_mass = options["M_1+M_2"]
+                    secondary_mass = options["M_3"]
+                    m_label = "M_1+M_2"
                     p_label = "P2"
 
                     # Calculate new q table
@@ -2200,9 +2193,9 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
                     ].interpolate([secondary_mass / primary_mass])[0]
                     prob_dict['q3'] = q3_prob
                     verbose_print(
-                        "\tM&S: Moe_de_Stefano_2017_pdf: appended mass ratio (M1+M2={} M3={} P={} q={}) probability ({}) to the prob list ({}) ".format(
-                            options["M1+M2"],
-                            options["M3"],
+                        "\tM&S: Moe_de_Stefano_2017_pdf: appended mass ratio (M_1+M_2={} M_3={} P={} q={}) probability ({}) to the prob list ({}) ".format(
+                            options["M_1+M_2"],
+                            options["M_3"],
                             options["P"],
                             secondary_mass / primary_mass,
                             prob_dict['q3'],
@@ -2235,8 +2228,8 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
     # Some info
     if multiplicity == 1:
         verbose_print(
-            "\tM&S: Moe_de_Stefano_2017_pdf: M1={} q=N/A log10P=N/A ({}): {} -> {}\n".format(
-                options["M1"],
+            "\tM&S: Moe_de_Stefano_2017_pdf: M_1={} q=N/A log10P=N/A ({}): {} -> {}\n".format(
+                options["M_1"],
                 len(prob_dict),
                 str(prob_dict),
                 prob_dict['total_probdens']
@@ -2246,9 +2239,9 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
         )
     elif multiplicity == 2:
         verbose_print(
-            "\tM&S: Moe_de_Stefano_2017_pdf: M1={} q={} log10P={} ecc={} ({}): {} -> {}\n".format(
-                options["M1"],
-                options["M2"] / options["M1"] if options.get("M2", None) else "N/A",
+            "\tM&S: Moe_de_Stefano_2017_pdf: M_1={} q={} log10P={} ecc={} ({}): {} -> {}\n".format(
+                options["M_1"],
+                options["M_2"] / options["M_1"] if options.get("M_2", None) else "N/A",
                 np.log10(options["P"]),
                 options['ecc'] if options.get("ecc", None) else "N/A",
                 len(prob_dict),
@@ -2260,12 +2253,12 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
         )
     elif multiplicity == 3:
         verbose_print(
-            "\tM&S: Moe_de_Stefano_2017_pdf: M1={} q={} log10P={} ecc={} M3={} log10P2={} ecc2={} ({}): {} -> {}".format(
-                options["M1"],
-                options["M2"] / options["M1"] if options.get("M2", None) else "N/A",
+            "\tM&S: Moe_de_Stefano_2017_pdf: M_1={} q={} log10P={} ecc={} M_3={} log10P2={} ecc2={} ({}): {} -> {}".format(
+                options["M_1"],
+                options["M_2"] / options["M_1"] if options.get("M_2", None) else "N/A",
                 np.log10(options["P"]),
                 options["ecc"] if options.get("ecc", None) else "N/A",
-                options["M3"],
+                options["M_3"],
                 np.log10(options["P2"]),
                 options["ecc2"] if options.get("ecc2", None) else "N/A",
                 len(prob_dict),
@@ -2277,15 +2270,15 @@ def Moe_de_Stefano_2017_pdf(options, verbosity=0):
         )
     elif multiplicity == 4:
         verbose_print(
-            "M&S: Moe_de_Stefano_2017_pdf: M1={} q={} log10P={} ecc={} M3={} log10P2={} ecc2={} M4={} log10P3={} ecc3={} ({}) : {} -> {}".format(
-                options["M1"],
-                options["M2"] / options["M1"] if options.get("M2", None) else "N/A",
+            "M&S: Moe_de_Stefano_2017_pdf: M_1={} q={} log10P={} ecc={} M_3={} log10P2={} ecc2={} M_4={} log10P3={} ecc3={} ({}) : {} -> {}".format(
+                options["M_1"],
+                options["M_2"] / options["M_1"] if options.get("M_2", None) else "N/A",
                 np.log10(options["P"]),
                 options['ecc'] if options.get("ecc", None) else "N/A",
-                options["M3"],
+                options["M_3"],
                 np.log10(options["P2"]),
                 options["ecc2"] if options.get("ecc2", None) else "N/A",
-                options["M4"],
+                options["M_4"],
                 np.log10(options["P3"]),
                 options["ecc3"] if options.get("ecc3", None) else "N/A",
                 len(prob_dict), str(prob_dict), prob_dict['total_probdens']
diff --git a/binarycpython/utils/functions.py b/binarycpython/utils/functions.py
index 8b958a4b9d7dccd1f5e37f2d1d4acf03b35800bf..4eceb3b62774c2839215f9a9f266b71e4b7718c1 100644
--- a/binarycpython/utils/functions.py
+++ b/binarycpython/utils/functions.py
@@ -20,7 +20,10 @@ import types
 
 from io import StringIO
 from typing import Union, Any
-from collections import defaultdict
+from collections import (
+    defaultdict,
+    OrderedDict,
+)
 
 import h5py
 import numpy as np
@@ -169,17 +172,17 @@ def subtract_dicts(dict_1: dict, dict_2: dict) -> dict:
                     del new_dict[key]
 
             else:
-                print(
-                    "Error key: {} value: {} type: {} and key: {} value: {} type: {} are not of the same type and cannot be merged".format(
-                        key,
-                        dict_1[key],
-                        type(dict_1[key]),
-                        key,
-                        dict_2[key],
-                        type(dict_2[key]),
-                    )
+                msg = "Error key: {} value: {} type: {} and key: {} value: {} type: {} are not of the same type and cannot be merged".format(
+                    key,
+                    dict_1[key],
+                    type(dict_1[key]),
+                    key,
+                    dict_2[key],
+                    type(dict_2[key]),
                 )
-                raise ValueError
+
+                print(msg)
+                raise ValueError(msg)
 
         # This is where the keys are the same
         else:
@@ -210,26 +213,37 @@ def subtract_dicts(dict_1: dict, dict_2: dict) -> dict:
     return new_dict
 
 
-def get_moe_distefano_dataset(options):
+def get_moe_distefano_dataset(options, verbosity=0):
     """
     Function to get the default moe and Distefano dataset or accept a userinput.
     """
 
     if not options.get("file", None):
-        print("Using the default Moe and de Stefano 2017 datafile")
+        verbose_print(
+            "Using the default Moe and de Stefano 2017 datafile",
+            verbosity,
+            1,
+        )
 
         json_data = copy.deepcopy(moe_distefano_data.moe_distefano_2017_data)
 
     else:
         if not os.path.isfile(options["file"]):
-            print(
+            verbose_print(
                 "The provided 'file' Moe and de Stefano JSON file does not seem to exist at {}".format(
                     options["file"]
-                )
+                ),
+                verbosity,
+                1,
             )
+
             raise ValueError
         if not options["file"].endswith(".json"):
-            print("Provided filename does not end with .json")
+            verbose_print(
+                "Provided filename is not a json file",
+                verbosity,
+                1,
+            )
 
         else:
             # Read input data and Clean up the data if there are whitespaces around the keys
@@ -1447,7 +1461,7 @@ def inspect_dict(
             type(input_dict[key]) (except if the value is a dict)
     """
 
-    structure_dict = {}
+    structure_dict = OrderedDict() # TODO: check if this still works
 
     #
     for key, value in input_dict.items():
@@ -1489,7 +1503,7 @@ def merge_dicts(dict_1: dict, dict_2: dict) -> dict:
     """
 
     # Set up new dict
-    new_dict = {}
+    new_dict = OrderedDict() # TODO: check if this still works
 
     #
     keys_1 = dict_1.keys()
@@ -1531,6 +1545,11 @@ def merge_dicts(dict_1: dict, dict_2: dict) -> dict:
                 type(dict_2[key]) in [int, float, np.float64]
             ):
                 new_dict[key] = dict_1[key] + dict_2[key]
+            # Exceptions:
+            elif (type(dict_1[key]) in [dict, OrderedDict]) and (
+                type(dict_2[key]) in [dict, OrderedDict]
+            ):
+                new_dict[key] = merge_dicts(dict_1[key], dict_2[key])
 
             else:
                 print(
@@ -1584,7 +1603,6 @@ def merge_dicts(dict_1: dict, dict_2: dict) -> dict:
     #
     return new_dict
 
-
 def update_dicts(dict_1: dict, dict_2: dict) -> dict:
     """
     Function to update dict_1 with values of dict_2 in a recursive way.
@@ -1607,7 +1625,7 @@ def update_dicts(dict_1: dict, dict_2: dict) -> dict:
     """
 
     # Set up new dict
-    new_dict = {}
+    new_dict = OrderedDict() # TODO: check if this still works
 
     #
     keys_1 = dict_1.keys()
@@ -1677,6 +1695,141 @@ def update_dicts(dict_1: dict, dict_2: dict) -> dict:
     return new_dict
 
 
+def count_keys_recursive(input_dict):
+    """
+    Function to count the total amount of keys in a dictionary
+    """
+
+    local_count = 0
+    for key in input_dict.keys():
+        local_count += 1
+        if isinstance(input_dict[key], (dict, OrderedDict)):
+            local_count += count_keys_recursive(input_dict[key])
+    return local_count
+
+
+def recursive_change_key_to_float(input_dict):
+    """
+    Function to recursively change the key to float
+
+    This only works if the dict contains just sub-dicts or numbers/strings.
+    Does not work with lists as values
+    """
+
+    new_dict = OrderedDict() # TODO: check if this still works
+
+    for key in input_dict:
+        if isinstance(input_dict[key], (dict, OrderedDict)):
+            try:
+                num_key = float(key)
+                new_dict[num_key] = recursive_change_key_to_float(copy.deepcopy(input_dict[key]))
+            except ValueError:
+                new_dict[key] = recursive_change_key_to_float(copy.deepcopy(input_dict[key]))
+        else:
+            try:
+                num_key = float(key)
+                new_dict[num_key] = input_dict[key]
+            except ValueError:
+                new_dict[key] = input_dict[key]
+
+    return new_dict
+
+def recursive_change_key_to_string(input_dict):
+    """
+    Function to recursively change the key back to a string but this time in a format that we decide
+    """
+
+    new_dict = OrderedDict() # TODO: check if this still works
+
+    for key in input_dict:
+        if isinstance(input_dict[key], (dict, OrderedDict)):
+            if isinstance(key, (int, float)):
+                string_key = "{:g}".format(key)
+                new_dict[string_key] = recursive_change_key_to_string(copy.deepcopy(input_dict[key]))
+            else:
+                new_dict[key] = recursive_change_key_to_string(copy.deepcopy(input_dict[key]))
+        else:
+            if isinstance(key, (int, float)):
+                string_key = "{:g}".format(key)
+                new_dict[string_key] = input_dict[key]
+            else:
+                new_dict[key] = input_dict[key]
+
+    return new_dict
+
+
+# New method to create a ordered dictionary
+def custom_sort_dict(input_dict):
+    """
+    Returns a dictionary that is ordered, but can handle numbers better than normal OrderedDict
+
+    When the keys of the current dictionary are of mixed type, we first find all the unique types.
+    Sort that list of type names. Then find the values that fit that type.
+    Sort those and append them to the sorted keys list. 
+    This is done until all the keys are sorted.
+
+    All objects other than dictionary types are directly return as they are
+    """
+
+    # If the new input is a dictionary, then try to sort it
+    if isinstance(input_dict, (dict, OrderedDict)):
+        new_dict = OrderedDict()
+
+        keys = input_dict.keys()
+
+        # Check if types are the same
+        all_types_keys = []
+        for key in keys:
+            if not type(key) in all_types_keys:
+                all_types_keys.append(type(key))
+
+        # If there are multiple types, then we loop over them and do a piecewise sort
+        if len(all_types_keys) > 1:
+            msg = "Different types in the same dictionary key set"
+            
+            # Create a string repr of the type name to sort them afterwards
+            str_types = {repr(el):el for el in all_types_keys}
+
+            # Set up sorted keys list
+            sorted_keys = []
+
+            for key_str_type in sorted(str_types.keys()):
+                cur_type = str_types[key_str_type]
+
+                cur_list = [key for key in keys if isinstance(key, cur_type)]
+                cur_sorted_list = sorted(cur_list)
+
+                sorted_keys = sorted_keys + cur_sorted_list
+        else:
+            sorted_keys = sorted(keys)
+
+        for key in sorted_keys:
+            new_dict[key] = custom_sort_dict(copy.deepcopy(input_dict[key]))
+
+        return new_dict
+    return input_dict
+
+
+def multiply_values_dict(input_dict, factor):
+    """
+    Function that goes over dictionary recursively and multiplies the value if possible by a factor
+
+    If the key equals "general_info", the multiplication gets skipped
+    """
+
+    for key in input_dict:
+        if not key == 'general_info':
+            if isinstance(input_dict[key], (dict, OrderedDict)):
+                input_dict[key] = multiply_values_dict(input_dict[key], factor)
+            else:
+                if isinstance(input_dict[key], (int, float)):
+                    input_dict[key] = input_dict[key] * factor
+
+    return input_dict
+
+
+#####
+
 def extract_ensemble_json_from_string(binary_c_output: str) -> dict:
     """
     Function to extract the ensemble_json information from a raw binary_c output string
@@ -1788,7 +1941,7 @@ def binaryc_json_serializer(obj: Any) -> Any:
     Function objects will be turned into str representations of themselves
 
     Args:
-        obj: obj being process
+        obj: The object that might not be serializable
 
     Returns:
         Either string representation of object if the object is a function, or the object itself
diff --git a/binarycpython/utils/grid.py b/binarycpython/utils/grid.py
index 5e5c9498304cf6172c05a74aa2fe1c078596df46..27d32cc9fcba345b64548731a2ea69247c0ae56e 100644
--- a/binarycpython/utils/grid.py
+++ b/binarycpython/utils/grid.py
@@ -37,10 +37,12 @@ import argparse
 import importlib.util
 import multiprocessing
 from typing import Union, Any
+from collections import (
+    OrderedDict,
+)
 import setproctitle
 import py_rinterpolate
 
-
 from binarycpython.utils.grid_options_defaults import (
     grid_options_defaults_dict,
     moe_distefano_default_options,
@@ -64,8 +66,13 @@ from binarycpython.utils.functions import (
     update_dicts,
     extract_ensemble_json_from_string,
     get_moe_distefano_dataset,
+    recursive_change_key_to_float,
+    custom_sort_dict,
+    recursive_change_key_to_string,
+    multiply_values_dict,
 )
 
+
 # from binarycpython.utils.hpc_functions import (
 #     get_condor_version,
 #     get_slurm_version,
@@ -79,6 +86,9 @@ from binarycpython.utils.distribution_functions import (
     LOG_LN_CONVERTER,
     fill_data,
     get_max_multiplicity,
+    Arenou2010_binary_fraction,
+    raghavan2010_binary_fraction,
+    Moe_de_Stefano_2017_multiplicity_fractions,
 )
 
 from binarycpython import _binary_c_bindings
@@ -115,6 +125,14 @@ class Population:
         # Custom options
         self.custom_options = {}
 
+        # Load M&s options
+        self.grid_options['m&s_options'] = copy.deepcopy(moe_distefano_default_options)
+
+        # Write M&S options to a file
+        os.makedirs(os.path.join(self.grid_options["tmp_dir"], "moe_distefano"), exist_ok=True)
+        with open(os.path.join(os.path.join(self.grid_options["tmp_dir"], "moe_distefano"), "moeopts.dat"), "w") as f:
+            f.write(json.dumps(self.grid_options['m&s_options'], indent=4))
+
         # Argline dict
         self.argline_dict = {}
 
@@ -930,6 +948,33 @@ class Population:
         for _ in range(amt_cores):
             job_queue.put("STOP")
 
+    def format_ensemble_results(self, ensemble_dictionary):
+        """
+        Function to handle all the steps of formatting the ensemble output again.
+
+        Input:
+            ensemble_dictionary: dictionary containing all the ensemble results
+        """
+
+        original_ensemble_results = ensemble_dictionary
+
+        float_format_ensemble_results = recursive_change_key_to_float(original_ensemble_results)
+        del original_ensemble_results
+        gc.collect()
+
+        # Then sort the dictionary
+        sorted_ensemble_results = custom_sort_dict(float_format_ensemble_results)
+        del float_format_ensemble_results
+        gc.collect()
+
+        # Then Change the keys back to a string but with a %g format.
+        reformatted_ensemble_results = recursive_change_key_to_string(sorted_ensemble_results)
+        del sorted_ensemble_results
+        gc.collect()
+
+        # Put back in the dictionary
+        return reformatted_ensemble_results
+
     def _evolve_population_grid(self):
         """
         Function that handles running the population using multiprocessing.
@@ -982,7 +1027,7 @@ class Population:
 
         # Handle the results by merging all the dictionaries. How that merging happens exactly is
         # described in the merge_dicts description.
-        combined_output_dict = {}
+        combined_output_dict = OrderedDict()
 
         sentinel = object()
         for output_dict in iter(result_queue.get, sentinel):
@@ -990,17 +1035,46 @@ class Population:
             if result_queue.empty():
                 break
 
+        # Extra ensemble result manipulation:
+        combined_output_dict['ensemble_results']['ensemble'] = self.format_ensemble_results(combined_output_dict['ensemble_results'].get('ensemble', {}))
+        gc.collect()
+
+        # Take into account that we run this on multiple cores
+        combined_output_dict["_total_probability_weighted_mass_run"] = combined_output_dict["_total_probability_weighted_mass_run"]
+
         # Put the values back as object properties
         self.grid_results = combined_output_dict["results"]
+
+        #################################
+        # Put Ensemble results
         self.grid_ensemble_results = combined_output_dict[
             "ensemble_results"
         ]  # Ensemble results are also passed as output from that dictionary
 
-        # Add some metadata
-        self.grid_ensemble_results["population_id"] = self.grid_options[
+        # Add metadata
+        self.grid_ensemble_results["metadata"] = {}
+        self.grid_ensemble_results["metadata"]["population_id"] = self.grid_options[
             "_population_id"
         ]
+        self.grid_ensemble_results["metadata"][
+            "total_probability_weighted_mass"
+        ] = combined_output_dict["_total_probability_weighted_mass_run"]
+        self.grid_ensemble_results["metadata"]["factored_in_probability_weighted_mass"] = False
+        if self.grid_options['ensemble_factor_in_probability_weighted_mass']:
+            multiply_values_dict(self.grid_ensemble_results['ensemble'], 1/self.grid_ensemble_results["metadata"]['total_probability_weighted_mass'])
+            self.grid_ensemble_results["metadata"]["factored_in_probability_weighted_mass"] = True
+
+        # Add settings of the populations
+        all_info = self.return_all_info(
+            include_population_settings=True,
+            include_binary_c_defaults=True,
+            include_binary_c_version_info=True,
+            include_binary_c_help_all=True,
+        )
+        self.grid_ensemble_results["metadata"]["settings"] = json.loads(json.dumps(all_info, default=binaryc_json_serializer))
 
+        ##############################
+        # Update grid options
         self.grid_options["_failed_count"] = combined_output_dict["_failed_count"]
         self.grid_options["_failed_prob"] = combined_output_dict["_failed_prob"]
         self.grid_options["_failed_systems_error_codes"] = list(
@@ -1319,6 +1393,10 @@ class Population:
                     ensemble_raw_output
                 )  # Load this into a dict so that we can combine it later
 
+                # Extra ensemble result manipulation:
+                # We need to reformat and multiply by a factor
+                ensemble_json["ensemble"] = self.format_ensemble_results(ensemble_json["ensemble"])
+                gc.collect()
             else:
                 # If we do not allow this, automatically we will export this to the data_dir, in
                 # some formatted way
@@ -1421,14 +1499,9 @@ class Population:
         stream_logger.debug(f"Process-{self.process_ID} is finished.")
 
         # Clean up the interpolators if they exist
+
         # TODO: make a cleanup function for the individual threads
         # TODO: make sure this is necessary. Actually its probably not, because we have a centralized queue
-        verbose_print(
-            "Process {}: Cleaning up interpolators".format(ID),
-            self.grid_options["verbosity"],
-            1,
-        )
-        self._clean_interpolators()
 
         return
 
@@ -1665,9 +1738,6 @@ class Population:
         # Unload/free custom_logging_code
         # TODO: cleanup custom logging code.
 
-        # Clean up the interpolator functions
-        self._clean_interpolators()
-
         # Also remove the rest of the contents
         keys_moecache = list(Moecache.keys())
         for key in keys_moecache:
@@ -1714,6 +1784,7 @@ class Population:
         # Import packages
         code_string += "import math\n"
         code_string += "import numpy as np\n"
+        code_string += "from collections import OrderedDict\n"
         code_string += "from binarycpython.utils.distribution_functions import *\n"
         code_string += "from binarycpython.utils.spacing_functions import *\n"
         code_string += "from binarycpython.utils.useful_funcs import *\n"
@@ -2031,7 +2102,7 @@ class Population:
             code_string += indent * (depth + 1) + "#" * 40 + "\n"
             code_string += (
                 indent * (depth + 1)
-                + "# Code below is for finalising the handling of this iteration of the parameter\n"
+                + "# Code below is for finalising the handling of this iteration of the parameter {}\n".format(grid_variable["name"])
             )
 
             # Set phasevol
@@ -2146,7 +2217,15 @@ class Population:
                 + "# Code below will get evaluated for every generated system\n"
             )
 
-        # Calculate value
+        # Factor in the custom weight input
+        code_string += (
+            indent * (depth + 1)
+            + "\n"
+        )
+        code_string += (
+            indent * (depth + 1)
+            + "# Weigh the probability by a custom weighting factor\n"
+        )
         code_string += (
             indent * (depth + 1)
             + 'probability = self.grid_options["weight"] * probabilities_list[{}]'.format(
@@ -2154,22 +2233,54 @@ class Population:
             )
             + "\n"
         )
-        # TODO: ask rob if just replacing this with probability is enough
+
+        # Take into account the multiplicity fraction: 
+        code_string += (
+            indent * (depth + 1)
+            + "\n"
+        )
+        code_string += (
+            indent * (depth + 1)
+            + "# Factor the multiplicity fraction into the probability\n"
+        )
+        code_string += (
+            indent * (depth + 1)
+            + 'probability = probability * self._calculate_multiplicity_fraction(parameter_dict)'
+            + "\n"
+        )
+
+        # Add division by amount of repeats
+        code_string += (
+            indent * (depth + 1)
+            + "\n"
+        )
+        code_string += (
+            indent * (depth + 1)
+            + "# Divide the probability by the amount of repeats\n"
+        )
+
         code_string += (
             indent * (depth + 1)
-            # + 'repeat_probability = probability / self.grid_options["repeat"]'
             + 'probability = probability / self.grid_options["repeat"]'
             + "\n"
         )
 
-        # For each repeat of the system this has to be done yes.
+        # Now we yield the system self.grid_options["repeat"] times.
+        code_string += (
+            indent * (depth + 1)
+            + "\n"
+        )
+        code_string += (
+            indent * (depth + 1)
+            + "# Loop over the repeats\n"
+        )
         code_string += (
             indent * (depth + 1) + 'for _ in range(self.grid_options["repeat"]):' + "\n"
         )
 
         code_string += indent * (depth + 2) + "_total_starcount += 1\n"
 
-        # set probability and phasevol values
+        # set probability and phasevol values into the system dict
         code_string += (
             indent * (depth + 2)
             + 'parameter_dict["{}"] = {}'.format("probability", "probability")
@@ -2196,16 +2307,11 @@ class Population:
             # Handling of what is returned, or what is not.
             # TODO: think of whether this is a good method
             # code_string += indent * (depth + 2) + "if (self.grid_options['multiplicity'] >= 2): print('phasevol_q: ',phasevol_q); print('phasevol_log10per: ',phasevol_log10per);\n"
-            # code_string += indent * (depth + 2) + "print('phasevol_lnm1: ',phasevol_lnm1); print('phasevol_multiplicity: ',phasevol_multiplicity);\n"
-            # code_string += indent * (depth + 2) + "print(probabilities_list)\n"
-            # code_string += indent * (depth + 2) + "print(parameter_dict)\n"
-            # code_string += indent * (depth + 2) + "print('YOO IK GA LEKKER NOG EEN RONDJE')\n"
             code_string += indent * (depth + 2) + "yield(parameter_dict)\n"
 
         # If its a dry run, dont do anything with it
         else:
             # code_string += indent * (depth + 2) + "if (self.grid_options['multiplicity'] >= 2): print(phasevol_q)\n"
-            # code_string += indent * (depth + 2) + "print(parameter_dict)\n"
             code_string += indent * (depth + 2) + "pass\n"
 
         code_string += indent * (depth + 1) + "#" * 40 + "\n"
@@ -3066,11 +3172,6 @@ class Population:
         self.logger.addHandler(handler_file)
         self.logger.addHandler(handler_stdout)
 
-    # def join_result_dicts(self):
-    #     """
-    #     Function to join the result dictionaries
-    #     """
-
     def _check_binary_c_error(self, binary_c_output, system_dict):
         """
         Function to check whether binary_c throws an error and handle accordingly.
@@ -3145,71 +3246,45 @@ class Population:
                         f.write(argstring + "\n")
         else:
             verbose_print(
-                "binary_c had 0 output. Which is strange",
+                "binary_c had 0 output. Which is strange. Make sure you mean to do this. If there is actually ensemble output being generated then its fine.",
                 self.grid_options["verbosity"],
                 2,
             )
-    ################################################################################################
-    def Moe_de_Stefano_2017(self, options=None):
+
+    def set_moe_distefano_settings(self, options=None):
         """
+        Function to set user input configurations for the moe & distefano methods
+    
+        If nothing is passed then we just use the default options
         """
 
-        # Require input options
-        if not options:
-            msg = "Please provide a options dictionary."
-            verbose_print(
-                "\tMoe_de_Stefano_2017: {}".format(msg),
-                self.grid_options["verbosity"],
-                0,
-            )
-            raise ValueError(msg)
-
-        # Create the tmp dir
-        ms_tmp_dir = os.path.join(self.grid_options["tmp_dir"], "moe_distefano")
-        os.makedirs(ms_tmp_dir, exist_ok=True)
-
-        # TODO: put this in defaults
-        default_options = copy.deepcopy(moe_distefano_default_options)
-
-        # Take the option dictionary that was given and override.
-        options = update_dicts(default_options, options)
+        if options:
+            # Take the option dictionary that was given and override.
+            options = update_dicts(self.grid_options['m&s_options'], options)
+            self.grid_options['m&s_options'] = copy.deepcopy(dict(options))
 
-        # Write options to a file
-        with open(os.path.join(ms_tmp_dir, "moeopts.dat"), "w") as f:
-            f.write(json.dumps(options, indent=4))
-
-        json_data = get_moe_distefano_dataset(options)
-
-        # entry of log10M1 is a list containing 1 dict. We can take the dict out of the list
-        
-        json_data["log10M1"] = json_data["log10M1"][0]
-
-        # Get all the masses
-        logmasses = sorted(json_data["log10M1"].keys())
-        if not logmasses:
-            msg = "The table does not contain masses."
-            verbose_print(
-                "\tMoe_de_Stefano_2017: {}".format(msg),
-                self.grid_options["verbosity"],
-                0,
-            )
-            raise ValueError(msg)
+            # Write options to a file
+            os.makedirs(os.path.join(self.grid_options["tmp_dir"], "moe_distefano"), exist_ok=True)
+            with open(os.path.join(os.path.join(self.grid_options["tmp_dir"], "moe_distefano"), "moeopts.dat"), "w") as f:
+                f.write(json.dumps(self.grid_options['m&s_options'], indent=4))
 
-        # Write to file
-        with open(os.path.join(ms_tmp_dir, "moe.log"), "w") as logfile:
-            logfile.write("log₁₀Masses(M☉) {}\n".format(logmasses))
+    def _load_moe_distefano_data(self):
+        """
+        Function to load the moe & distefano data
+        """
 
-        # Get all the periods and see if they are all consistently present
-        logperiods = []
-        for logmass in logmasses:
-            if not logperiods:
-                logperiods = sorted(json_data["log10M1"][logmass]["logP"])
-                dlog10P = float(logperiods[1]) - float(logperiods[0])
+        # Only if the grid is loaded and moecache contains information
+        if (not self.grid_options['_loaded_ms_data']) and not Moecache:
+            # Load the json data
+            json_data = get_moe_distefano_dataset(self.grid_options['m&s_options'], verbosity=self.grid_options['verbosity'])
 
-            current_logperiods = sorted(json_data["log10M1"][logmass]["logP"])
+            # entry of log10M1 is a list containing 1 dict. We can take the dict out of the list
+            json_data["log10M1"] = json_data["log10M1"][0]
 
-            if not (logperiods == current_logperiods):
-                msg = "Period values are not consistent throughout the dataset"
+            # Get all the masses
+            logmasses = sorted(json_data["log10M1"].keys())
+            if not logmasses:
+                msg = "The table does not contain masses."
                 verbose_print(
                     "\tMoe_de_Stefano_2017: {}".format(msg),
                     self.grid_options["verbosity"],
@@ -3217,15 +3292,22 @@ class Population:
                 )
                 raise ValueError(msg)
 
-            ############################################################
-            # log10period binwidth : of course this assumes a fixed
-            # binwidth, so we check for this too.
+            # Write to file
+            os.makedirs(os.path.join(self.grid_options["tmp_dir"], "moe_distefano"), exist_ok=True)
+            with open(os.path.join(os.path.join(self.grid_options["tmp_dir"], "moe_distefano"), "moe.log"), "w") as logfile:
+                logfile.write("log₁₀Masses(M☉) {}\n".format(logmasses))
 
-            for i in range(len(current_logperiods) - 1):
-                if not dlog10P == (
-                    float(current_logperiods[i + 1]) - float(current_logperiods[i])
-                ):
-                    msg = "Period spacing is not consistent throughout the dataset"
+            # Get all the periods and see if they are all consistently present
+            logperiods = []
+            for logmass in logmasses:
+                if not logperiods:
+                    logperiods = sorted(json_data["log10M1"][logmass]["logP"])
+                    dlog10P = float(logperiods[1]) - float(logperiods[0])
+
+                current_logperiods = sorted(json_data["log10M1"][logmass]["logP"])
+
+                if not (logperiods == current_logperiods):
+                    msg = "Period values are not consistent throughout the dataset"
                     verbose_print(
                         "\tMoe_de_Stefano_2017: {}".format(msg),
                         self.grid_options["verbosity"],
@@ -3233,69 +3315,100 @@ class Population:
                     )
                     raise ValueError(msg)
 
-        # Write to file
-        with open(
-            os.path.join(self.grid_options["tmp_dir"], "moe_distefano", "moe.log"), "a"
-        ) as logfile:
-            logfile.write("log₁₀Periods(days) {}\n".format(logperiods))
-
-        # Fill the global dict
-        for logmass in logmasses:
-            # Create the multiplicity table
-            if not Moecache.get("multiplicity_table", None):
-                Moecache["multiplicity_table"] = []
-
-            # multiplicity as a function of primary mass
-            Moecache["multiplicity_table"].append(
-                [
-                    float(logmass),
-                    json_data["log10M1"][logmass]["f_multi"],
-                    json_data["log10M1"][logmass]["single star fraction"],
-                    json_data["log10M1"][logmass]["binary star fraction"],
-                    json_data["log10M1"][logmass]["triple/quad star fraction"],
-                ]
-            )
+                ############################################################
+                # log10period binwidth : of course this assumes a fixed
+                # binwidth, so we check for this too.
 
-            ############################################################
-            # a small log10period which we can shift just outside the
-            # table to force integration out there to zero
-            epslog10P = 1e-8 * dlog10P
+                for i in range(len(current_logperiods) - 1):
+                    if not dlog10P == (
+                        float(current_logperiods[i + 1]) - float(current_logperiods[i])
+                    ):
+                        msg = "Period spacing is not consistent throughout the dataset"
+                        verbose_print(
+                            "\tMoe_de_Stefano_2017: {}".format(msg),
+                            self.grid_options["verbosity"],
+                            0,
+                        )
+                        raise ValueError(msg)
 
-            ############################################################
-            # loop over either binary or triple-outer periods
-            first = 1
+            # Write to file
+            os.makedirs(os.path.join(self.grid_options["tmp_dir"], "moe_distefano"), exist_ok=True)
+            with open(
+                os.path.join(self.grid_options["tmp_dir"], "moe_distefano", "moe.log"), "a"
+            ) as logfile:
+                logfile.write("log₁₀Periods(days) {}\n".format(logperiods))
 
-            # Go over the periods
-            for logperiod in logperiods:
-                # print("logperiod: {}".format(logperiod))
-                ############################################################
-                # distributions of binary and triple star fractions
-                # as a function of mass, period.
-                #
-                # Note: these should be per unit log10P, hence we
-                # divide by $dlog10P
+            # Fill the global dict
+            for logmass in logmasses:
+                # Create the multiplicity table
+                if not Moecache.get("multiplicity_table", None):
+                    Moecache["multiplicity_table"] = []
 
-                if first:
-                    first = 0
+                # multiplicity as a function of primary mass
+                Moecache["multiplicity_table"].append(
+                    [
+                        float(logmass),
+                        json_data["log10M1"][logmass]["f_multi"],
+                        json_data["log10M1"][logmass]["single star fraction"],
+                        json_data["log10M1"][logmass]["binary star fraction"],
+                        json_data["log10M1"][logmass]["triple/quad star fraction"],
+                    ]
+                )
 
-                    # Create the multiplicity table
-                    if not Moecache.get("period_distributions", None):
-                        Moecache["period_distributions"] = []
+                ############################################################
+                # a small log10period which we can shift just outside the
+                # table to force integration out there to zero
+                epslog10P = 1e-8 * dlog10P
+
+                ############################################################
+                # loop over either binary or triple-outer periods
+                first = 1
 
+                # Go over the periods
+                for logperiod in logperiods:
+                    # print("logperiod: {}".format(logperiod))
                     ############################################################
-                    # lower bound the period distributions to zero probability
-                    Moecache["period_distributions"].append(
-                        [
-                            float(logmass),
-                            float(logperiod) - 0.5 * dlog10P - epslog10P,
-                            0.0,
-                            0.0,
-                        ]
-                    )
+                    # distributions of binary and triple star fractions
+                    # as a function of mass, period.
+                    #
+                    # Note: these should be per unit log10P, hence we
+                    # divide by $dlog10P
+
+                    if first:
+                        first = 0
+
+                        # Create the multiplicity table
+                        if not Moecache.get("period_distributions", None):
+                            Moecache["period_distributions"] = []
+
+                        ############################################################
+                        # lower bound the period distributions to zero probability
+                        Moecache["period_distributions"].append(
+                            [
+                                float(logmass),
+                                float(logperiod) - 0.5 * dlog10P - epslog10P,
+                                0.0,
+                                0.0,
+                            ]
+                        )
+                        Moecache["period_distributions"].append(
+                            [
+                                float(logmass),
+                                float(logperiod) - 0.5 * dlog10P,
+                                json_data["log10M1"][logmass]["logP"][logperiod][
+                                    "normed_bin_frac_p_dist"
+                                ]
+                                / dlog10P,
+                                json_data["log10M1"][logmass]["logP"][logperiod][
+                                    "normed_tripquad_frac_p_dist"
+                                ]
+                                / dlog10P,
+                            ]
+                        )
                     Moecache["period_distributions"].append(
                         [
                             float(logmass),
-                            float(logperiod) - 0.5 * dlog10P,
+                            float(logperiod),
                             json_data["log10M1"][logmass]["logP"][logperiod][
                                 "normed_bin_frac_p_dist"
                             ]
@@ -3306,132 +3419,118 @@ class Population:
                             / dlog10P,
                         ]
                     )
+
+                    ############################################################
+                    # distributions as a function of mass, period, q
+                    #
+                    # First, get a list of the qs given by Moe
+                    #
+                    qs = sorted(json_data["log10M1"][logmass]["logP"][logperiod]["q"])
+
+                    # Fill the data and 'normalize'
+                    qdata = fill_data(
+                        qs, json_data["log10M1"][logmass]["logP"][logperiod]["q"]
+                    )
+
+                    # Create the multiplicity table
+                    if not Moecache.get("q_distributions", None):
+                        Moecache["q_distributions"] = []
+
+                    for q in qs:
+                        Moecache["q_distributions"].append(
+                            [float(logmass), float(logperiod), float(q), qdata[q]]
+                        )
+
+                    ############################################################
+                    # eccentricity distributions as a function of mass, period, ecc
+                    eccs = sorted(json_data["log10M1"][logmass]["logP"][logperiod]["e"])
+
+                    # Fill the data and 'normalize'
+                    ecc_data = fill_data(
+                        eccs, json_data["log10M1"][logmass]["logP"][logperiod]["e"]
+                    )
+
+                    # Create the multiplicity table
+                    if not Moecache.get("ecc_distributions", None):
+                        Moecache["ecc_distributions"] = []
+
+                    for ecc in eccs:
+                        Moecache["ecc_distributions"].append(
+                            [float(logmass), float(logperiod), float(ecc), ecc_data[ecc]]
+                        )
+
+                ############################################################
+                # upper bound the period distributions to zero probability
                 Moecache["period_distributions"].append(
                     [
                         float(logmass),
-                        float(logperiod),
-                        json_data["log10M1"][logmass]["logP"][logperiod][
+                        float(logperiods[-1]) + 0.5 * dlog10P,  # TODO: why this shift?
+                        json_data["log10M1"][logmass]["logP"][logperiods[-1]][
                             "normed_bin_frac_p_dist"
                         ]
                         / dlog10P,
-                        json_data["log10M1"][logmass]["logP"][logperiod][
+                        json_data["log10M1"][logmass]["logP"][logperiods[-1]][
                             "normed_tripquad_frac_p_dist"
                         ]
                         / dlog10P,
                     ]
                 )
-
-                ############################################################
-                # distributions as a function of mass, period, q
-                #
-                # First, get a list of the qs given by Moe
-                #
-                qs = sorted(json_data["log10M1"][logmass]["logP"][logperiod]["q"])
-
-                # Fill the data and 'normalize'
-                qdata = fill_data(
-                    qs, json_data["log10M1"][logmass]["logP"][logperiod]["q"]
-                )
-
-                # Create the multiplicity table
-                if not Moecache.get("q_distributions", None):
-                    Moecache["q_distributions"] = []
-
-                for q in qs:
-                    Moecache["q_distributions"].append(
-                        [float(logmass), float(logperiod), float(q), qdata[q]]
-                    )
-
-                ############################################################
-                # eccentricity distributions as a function of mass, period, ecc
-                eccs = sorted(json_data["log10M1"][logmass]["logP"][logperiod]["e"])
-
-                # Fill the data and 'normalize'
-                ecc_data = fill_data(
-                    eccs, json_data["log10M1"][logmass]["logP"][logperiod]["e"]
+                Moecache["period_distributions"].append(
+                    [
+                        float(logmass),
+                        float(logperiods[-1]) + 0.5 * dlog10P + epslog10P,
+                        0.0,
+                        0.0,
+                    ]
                 )
 
-                # Create the multiplicity table
-                if not Moecache.get("ecc_distributions", None):
-                    Moecache["ecc_distributions"] = []
-
-                for ecc in eccs:
-                    Moecache["ecc_distributions"].append(
-                        [float(logmass), float(logperiod), float(ecc), ecc_data[ecc]]
-                    )
-
-            ############################################################
-            # upper bound the period distributions to zero probability
-            Moecache["period_distributions"].append(
-                [
-                    float(logmass),
-                    float(logperiods[-1]) + 0.5 * dlog10P,  # TODO: why this shift?
-                    json_data["log10M1"][logmass]["logP"][logperiods[-1]][
-                        "normed_bin_frac_p_dist"
-                    ]
-                    / dlog10P,
-                    json_data["log10M1"][logmass]["logP"][logperiods[-1]][
-                        "normed_tripquad_frac_p_dist"
-                    ]
-                    / dlog10P,
-                ]
+            verbose_print(
+                "\tMoe_de_Stefano_2017: Length period_distributions table: {}".format(
+                    len(Moecache["period_distributions"])
+                ),
+                self.grid_options["verbosity"],
+                _MS_VERBOSITY_LEVEL,
             )
-            Moecache["period_distributions"].append(
-                [
-                    float(logmass),
-                    float(logperiods[-1]) + 0.5 * dlog10P + epslog10P,
-                    0.0,
-                    0.0,
-                ]
+            verbose_print(
+                "\tMoe_de_Stefano_2017: Length multiplicity table: {}".format(
+                    len(Moecache["multiplicity_table"])
+                ),
+                self.grid_options["verbosity"],
+                _MS_VERBOSITY_LEVEL,
+            )
+            verbose_print(
+                "\tMoe_de_Stefano_2017: Length q table: {}".format(
+                    len(Moecache["q_distributions"])
+                ),
+                self.grid_options["verbosity"],
+                _MS_VERBOSITY_LEVEL,
+            )
+            verbose_print(
+                "\tMoe_de_Stefano_2017: Length ecc table: {}".format(
+                    len(Moecache["ecc_distributions"])
+                ),
+                self.grid_options["verbosity"],
+                _MS_VERBOSITY_LEVEL,
             )
 
-        verbose_print(
-            "\tMoe_de_Stefano_2017: Length period_distributions table: {}".format(
-                len(Moecache["period_distributions"])
-            ),
-            self.grid_options["verbosity"],
-            _MS_VERBOSITY_LEVEL,
-        )
-        verbose_print(
-            "\tMoe_de_Stefano_2017: Length multiplicity table: {}".format(
-                len(Moecache["multiplicity_table"])
-            ),
-            self.grid_options["verbosity"],
-            _MS_VERBOSITY_LEVEL,
-        )
-        verbose_print(
-            "\tMoe_de_Stefano_2017: Length q table: {}".format(
-                len(Moecache["q_distributions"])
-            ),
-            self.grid_options["verbosity"],
-            _MS_VERBOSITY_LEVEL,
-        )
-        verbose_print(
-            "\tMoe_de_Stefano_2017: Length ecc table: {}".format(
-                len(Moecache["ecc_distributions"])
-            ),
-            self.grid_options["verbosity"],
-            _MS_VERBOSITY_LEVEL,
-        )
-
-        # Write to logfile
-        with open(os.path.join(ms_tmp_dir, "moecache.json"), "w") as cache_filehandle:
-            cache_filehandle.write(json.dumps(Moecache, indent=4))
-
+            # Write to logfile
+            os.makedirs(os.path.join(self.grid_options["tmp_dir"], "moe_distefano"), exist_ok=True)
+            with open(os.path.join(os.path.join(self.grid_options["tmp_dir"], "moe_distefano"), "moecache.json"), "w") as cache_filehandle:
+                cache_filehandle.write(json.dumps(Moecache, indent=4))
 
-        ############################################################
-        # construct the grid here
-        #
+            # Signal that the data has been loaded
+            self.grid_options['_loaded_ms_data'] = True
 
-        ############################################################
-        # make a version of the options which can be passed to
-        # the probability density function in the gridcode
+    def _set_moe_distefano_distributions(self):
+        """
+        Function to set the moe & distefano distribution
+        """
 
         ############################################################
         # first, the multiplicity, this is 1,2,3,4, ...
         # for singles, binaries, triples, quadruples, ...
 
-        max_multiplicity = get_max_multiplicity(options["multiplicity_modulator"])
+        max_multiplicity = get_max_multiplicity(self.grid_options['m&s_options']["multiplicity_modulator"])
         verbose_print(
             "\tMoe_de_Stefano_2017: Max multiplicity = {}".format(max_multiplicity),
             self.grid_options["verbosity"],
@@ -3448,11 +3547,11 @@ class Population:
             valuerange=[1, max_multiplicity],
             resolution=1,
             spacingfunc="range(1, 5)",
-            precode='self.grid_options["multiplicity"] = multiplicity; options={}'.format(
-                options
+            precode='self.grid_options["multiplicity"] = multiplicity; self.bse_options["multiplicity"] = multiplicity; options={}'.format(
+                self.grid_options['m&s_options']
             ),
             condition="({}[multiplicity-1] > 0)".format(
-                str(options["multiplicity_modulator"])
+                str(self.grid_options['m&s_options']["multiplicity_modulator"])
             ),
             gridtype="edge",
             dphasevol=-1,
@@ -3472,19 +3571,19 @@ class Population:
             longname="Primary mass",
             resolution="options['resolutions']['M'][0]",
             spacingfunc="const(np.log({}), np.log({}), {})".format(
-                options["ranges"]["M"][0],
-                options["ranges"]["M"][1],
-                options["resolutions"]["M"][0],
+                self.grid_options['m&s_options']["ranges"]["M"][0],
+                self.grid_options['m&s_options']["ranges"]["M"][1],
+                self.grid_options['m&s_options']["resolutions"]["M"][0],
             ),
             valuerange=[
-                "np.log({})".format(options["ranges"]["M"][0]),
-                "np.log({})".format(options["ranges"]["M"][1]),
+                "np.log({})".format(self.grid_options['m&s_options']["ranges"]["M"][0]),
+                "np.log({})".format(self.grid_options['m&s_options']["ranges"]["M"][1]),
             ],
             gridtype="centred",
             dphasevol="dlnm1",
-            precode='M_1 = np.exp(lnm1); options["M1"]=M_1',
+            precode='M_1 = np.exp(lnm1); options["M_1"]=M_1',
             probdist="Moe_de_Stefano_2017_pdf({{{}, {}, {}}}, verbosity=self.grid_options['verbosity'])['total_probdens'] if multiplicity == 1 else 1".format(
-                str(options)[1:-1], "'multiplicity': multiplicity", "'M1': M_1"
+                str(dict(self.grid_options['m&s_options']))[1:-1], "'multiplicity': multiplicity", "'M_1': M_1"
             ),
         )
 
@@ -3495,23 +3594,23 @@ class Population:
                 name="log10per",
                 parameter_name="orbital_period",
                 longname="log10(Orbital_Period)",
-                resolution=options["resolutions"]["logP"][0],
+                resolution=self.grid_options['m&s_options']["resolutions"]["logP"][0],
                 probdist=1.0,
                 condition='(self.grid_options["multiplicity"] >= 2)',
                 branchpoint=1 if max_multiplicity > 1 else 0, # Signal here to put a branchpoint if we have a max multiplicity higher than 1. 
                 gridtype="centred",
                 dphasevol="({} * dlog10per)".format(LOG_LN_CONVERTER),
-                valuerange=[options["ranges"]["logP"][0], options["ranges"]["logP"][1]],
+                valuerange=[self.grid_options['m&s_options']["ranges"]["logP"][0], self.grid_options['m&s_options']["ranges"]["logP"][1]],
                 spacingfunc="const({}, {}, {})".format(
-                    options["ranges"]["logP"][0],
-                    options["ranges"]["logP"][1],
-                    options["resolutions"]["logP"][0],
+                    self.grid_options['m&s_options']["ranges"]["logP"][0],
+                    self.grid_options['m&s_options']["ranges"]["logP"][1],
+                    self.grid_options['m&s_options']["resolutions"]["logP"][0],
                 ),
                 precode="""orbital_period = 10.0**log10per
 qmin={}/M_1
 qmax=maximum_mass_ratio_for_RLOF(M_1, orbital_period)
 """.format(
-                    options.get("Mmin", 0.07)
+                    self.grid_options['m&s_options'].get("Mmin", 0.07)
                 ),
             ) # TODO: change the maximum_mass_ratio_for_RLOF 
 
@@ -3521,14 +3620,14 @@ qmax=maximum_mass_ratio_for_RLOF(M_1, orbital_period)
                 parameter_name="M_2",
                 longname="Mass ratio",
                 valuerange=[
-                    options["ranges"]["q"][0]
-                    if options.get("ranges", {}).get("q", None)
+                    self.grid_options['m&s_options']["ranges"]["q"][0]
+                    if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
                     else "options.get('Mmin', 0.07)/M_1",
-                    options["ranges"]["q"][1]
-                    if options.get("ranges", {}).get("q", None)
+                    self.grid_options['m&s_options']["ranges"]["q"][1]
+                    if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
                     else "qmax",
                 ],
-                resolution=options["resolutions"]["M"][1],
+                resolution=self.grid_options['m&s_options']["resolutions"]["M"][1],
                 probdist=1,
                 gridtype="centred",
                 dphasevol="dq",
@@ -3537,35 +3636,35 @@ M_2 = q * M_1
 sep = calc_sep_from_period(M_1, M_2, orbital_period)
     """,
                 spacingfunc="const({}, {}, {})".format(
-                    options["ranges"]["q"][0]
-                    if options.get("ranges", {}).get("q", [None, None])[0]
-                    else "{}/M_1".format(options.get("Mmin", 0.07)),
-                    options["ranges"]["q"][1]
-                    if options.get("ranges", {}).get("q", [None, None])[1]
+                    self.grid_options['m&s_options']["ranges"]["q"][0]
+                    if self.grid_options['m&s_options'].get("ranges", {}).get("q", [None, None])[0]
+                    else "{}/M_1".format(self.grid_options['m&s_options'].get("Mmin", 0.07)),
+                    self.grid_options['m&s_options']["ranges"]["q"][1]
+                    if self.grid_options['m&s_options'].get("ranges", {}).get("q", [None, None])[1]
                     else "qmax",
-                    options["resolutions"]["M"][1],
+                    self.grid_options['m&s_options']["resolutions"]["M"][1],
                 ),
             )
 
             # (optional) binaries: eccentricity
-            if options["resolutions"]["ecc"][0] > 0:
+            if self.grid_options['m&s_options']["resolutions"]["ecc"][0] > 0:
                 self.add_grid_variable(
                     name="ecc",
                     parameter_name="eccentricity",
                     longname="Eccentricity",
-                    resolution=options["resolutions"]["ecc"][0],
+                    resolution=self.grid_options['m&s_options']["resolutions"]["ecc"][0],
                     probdist=1,
                     gridtype="centred",
                     dphasevol="decc",
                     precode="eccentricity=ecc",
                     valuerange=[
-                        options["ranges"]["ecc"][0],  # Just fail if not defined.
-                        options["ranges"]["ecc"][1],
+                        self.grid_options['m&s_options']["ranges"]["ecc"][0],  # Just fail if not defined.
+                        self.grid_options['m&s_options']["ranges"]["ecc"][1],
                     ],
                     spacingfunc="const({}, {}, {})".format(
-                        options["ranges"]["ecc"][0],  # Just fail if not defined.
-                        options["ranges"]["ecc"][1],
-                        options["resolutions"]["ecc"][0],
+                        self.grid_options['m&s_options']["ranges"]["ecc"][0],  # Just fail if not defined.
+                        self.grid_options['m&s_options']["ranges"]["ecc"][1],
+                        self.grid_options['m&s_options']["resolutions"]["ecc"][0],
                     ),
                 )
 
@@ -3576,45 +3675,44 @@ sep = calc_sep_from_period(M_1, M_2, orbital_period)
                     name="log10per2",
                     parameter_name="orbital_period_triple",
                     longname="log10(Orbital_Period2)",
-                    resolution=options["resolutions"]["logP"][1],
+                    resolution=self.grid_options['m&s_options']["resolutions"]["logP"][1],
                     probdist=1.0,
                     condition='(self.grid_options["multiplicity"] >= 3)',
                     branchpoint=2 if max_multiplicity > 2 else 0, # Signal here to put a branchpoint if we have a max multiplicity higher than 1. 
                     gridtype="centred",
                     dphasevol="({} * dlog10per2)".format(LOG_LN_CONVERTER),
                     valuerange=[
-                        options["ranges"]["logP"][0],
-                        options["ranges"]["logP"][1],
+                        self.grid_options['m&s_options']["ranges"]["logP"][0],
+                        self.grid_options['m&s_options']["ranges"]["logP"][1],
                     ],
                     spacingfunc="const({}, {}, {})".format(
-                        options["ranges"]["logP"][0],
-                        options["ranges"]["logP"][1],
-                        options["resolutions"]["logP"][1],
+                        self.grid_options['m&s_options']["ranges"]["logP"][0],
+                        self.grid_options['m&s_options']["ranges"]["logP"][1],
+                        self.grid_options['m&s_options']["resolutions"]["logP"][1],
                     ),
                     precode="""orbital_period_triple = 10.0**log10per2
 q2min={}/(M_1+M_2)
 q2max=maximum_mass_ratio_for_RLOF(M_1+M_2, orbital_period_triple)
     """.format(
-                        options.get("Mmin", 0.07)
+                        self.grid_options['m&s_options'].get("Mmin", 0.07)
                     ),
                 )
 
                 # Triples: mass ratio
                 # Note, the mass ratio is M_outer/M_inner
-                print("M2 resolution {}".format(options["resolutions"]["M"][2]))
                 self.add_grid_variable(
                     name="q2",
                     parameter_name="M_3",
                     longname="Mass ratio outer/inner",
                     valuerange=[
-                        options["ranges"]["q"][0]
-                        if options.get("ranges", {}).get("q", None)
+                        self.grid_options['m&s_options']["ranges"]["q"][0]
+                        if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
                         else "options.get('Mmin', 0.07)/(M_1+M_2)",
-                        options["ranges"]["q"][1]
-                        if options.get("ranges", {}).get("q", None)
+                        self.grid_options['m&s_options']["ranges"]["q"][1]
+                        if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
                         else "q2max",
                     ],
-                    resolution=options["resolutions"]["M"][2],
+                    resolution=self.grid_options['m&s_options']["resolutions"]["M"][2],
                     probdist=1,
                     gridtype="centred",
                     dphasevol="dq2",
@@ -3624,35 +3722,35 @@ sep2 = calc_sep_from_period((M_1+M_2), M_3, orbital_period_triple)
 eccentricity2=0
 """,
                     spacingfunc="const({}, {}, {})".format(
-                        options["ranges"]["q"][0]
-                        if options.get("ranges", {}).get("q", None)
+                        self.grid_options['m&s_options']["ranges"]["q"][0]
+                        if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
                         else "options.get('Mmin', 0.07)/(M_1+M_2)",
-                        options["ranges"]["q"][1]
-                        if options.get("ranges", {}).get("q", None)
+                        self.grid_options['m&s_options']["ranges"]["q"][1]
+                        if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
                         else "q2max",
-                        options["resolutions"]["M"][2],
+                        self.grid_options['m&s_options']["resolutions"]["M"][2],
                     ),
                 )
 
                 # (optional) triples: eccentricity
-                if options["resolutions"]["ecc"][1] > 0:
+                if self.grid_options['m&s_options']["resolutions"]["ecc"][1] > 0:
                     self.add_grid_variable(
                         name="ecc2",
                         parameter_name="eccentricity2",
                         longname="Eccentricity of the triple",
-                        resolution=options["resolutions"]["ecc"][1],
+                        resolution=self.grid_options['m&s_options']["resolutions"]["ecc"][1],
                         probdist=1,
                         gridtype="centred",
                         dphasevol="decc2",
                         precode="eccentricity2=ecc2",
                         valuerange=[
-                            options["ranges"]["ecc"][0],  # Just fail if not defined.
-                            options["ranges"]["ecc"][1],
+                            self.grid_options['m&s_options']["ranges"]["ecc"][0],  # Just fail if not defined.
+                            self.grid_options['m&s_options']["ranges"]["ecc"][1],
                         ],
                         spacingfunc="const({}, {}, {})".format(
-                            options["ranges"]["ecc"][0],  # Just fail if not defined.
-                            options["ranges"]["ecc"][1],
-                            options["resolutions"]["ecc"][1],
+                            self.grid_options['m&s_options']["ranges"]["ecc"][0],  # Just fail if not defined.
+                            self.grid_options['m&s_options']["ranges"]["ecc"][1],
+                            self.grid_options['m&s_options']["resolutions"]["ecc"][1],
                         ),
                     )
 
@@ -3662,44 +3760,43 @@ eccentricity2=0
                         name="log10per3",
                         parameter_name="orbital_period_quadruple",
                         longname="log10(Orbital_Period3)",
-                        resolution=options["resolutions"]["logP"][2],
+                        resolution=self.grid_options['m&s_options']["resolutions"]["logP"][2],
                         probdist=1.0,
                         condition='(self.grid_options["multiplicity"] >= 4)',
                         branchpoint=3 if max_multiplicity > 3 else 0, # Signal here to put a branchpoint if we have a max multiplicity higher than 1.
                         gridtype="centred",
                         dphasevol="({} * dlog10per3)".format(LOG_LN_CONVERTER),
                         valuerange=[
-                            options["ranges"]["logP"][0],
-                            options["ranges"]["logP"][1],
+                            self.grid_options['m&s_options']["ranges"]["logP"][0],
+                            self.grid_options['m&s_options']["ranges"]["logP"][1],
                         ],
                         spacingfunc="const({}, {}, {})".format(
-                            options["ranges"]["logP"][0],
-                            options["ranges"]["logP"][1],
-                            options["resolutions"]["logP"][2],
+                            self.grid_options['m&s_options']["ranges"]["logP"][0],
+                            self.grid_options['m&s_options']["ranges"]["logP"][1],
+                            self.grid_options['m&s_options']["resolutions"]["logP"][2],
                         ),
                         precode="""orbital_period_quadruple = 10.0**log10per3
 q3min={}/(M_3)
 q3max=maximum_mass_ratio_for_RLOF(M_3, orbital_period_quadruple)
     """.format(
-                            options.get("Mmin", 0.07)
+                            self.grid_options['m&s_options'].get("Mmin", 0.07)
                         ),
                     )
 
                     # Quadruple: mass ratio : M_outer / M_inner
-                    print("M3 resolution {}".format(options["resolutions"]["M"][3]))
                     self.add_grid_variable(
                         name="q3",
                         parameter_name="M_4",
                         longname="Mass ratio outer low/outer high",
                         valuerange=[
-                            options["ranges"]["q"][0]
-                            if options.get("ranges", {}).get("q", None)
-                            else "options.get('Mmin', 0.07)/(m3)",
-                            options["ranges"]["q"][1]
-                            if options.get("ranges", {}).get("q", None)
+                            self.grid_options['m&s_options']["ranges"]["q"][0]
+                            if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
+                            else "options.get('Mmin', 0.07)/(M_3)",
+                            self.grid_options['m&s_options']["ranges"]["q"][1]
+                            if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
                             else "q3max",
                         ],
-                        resolution=options["resolutions"]["M"][3],
+                        resolution=self.grid_options['m&s_options']["resolutions"]["M"][3],
                         probdist=1,
                         gridtype="centred",
                         dphasevol="dq3",
@@ -3707,41 +3804,41 @@ q3max=maximum_mass_ratio_for_RLOF(M_3, orbital_period_quadruple)
 M_4 = q3 * M_3
 sep3 = calc_sep_from_period((M_3), M_4, orbital_period_quadruple)
 eccentricity3=0
-        """,
+""",
                         spacingfunc="const({}, {}, {})".format(
-                            options["ranges"]["q"][0]
-                            if options.get("ranges", {}).get("q", None)
-                            else "options.get('Mmin', 0.07)/(m3)",
-                            options["ranges"]["q"][1]
-                            if options.get("ranges", {}).get("q", None)
+                            self.grid_options['m&s_options']["ranges"]["q"][0]
+                            if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
+                            else "options.get('Mmin', 0.07)/(M_3)",
+                            self.grid_options['m&s_options']["ranges"]["q"][1]
+                            if self.grid_options['m&s_options'].get("ranges", {}).get("q", None)
                             else "q3max",
-                            options["resolutions"]["M"][2],
+                            self.grid_options['m&s_options']["resolutions"]["M"][2],
                         ),
                     )
 
                     # (optional) triples: eccentricity
-                    if options["resolutions"]["ecc"][2] > 0:
+                    if self.grid_options['m&s_options']["resolutions"]["ecc"][2] > 0:
                         self.add_grid_variable(
                             name="ecc3",
                             parameter_name="eccentricity3",
                             longname="Eccentricity of the triple+quadruple/outer binary",
-                            resolution=options["resolutions"]["ecc"][2],
+                            resolution=self.grid_options['m&s_options']["resolutions"]["ecc"][2],
                             probdist=1,
                             gridtype="centred",
                             dphasevol="decc3",
                             precode="eccentricity3=ecc3",
                             valuerange=[
-                                options["ranges"]["ecc"][
+                                self.grid_options['m&s_options']["ranges"]["ecc"][
                                     0
                                 ],  # Just fail if not defined.
-                                options["ranges"]["ecc"][1],
+                                self.grid_options['m&s_options']["ranges"]["ecc"][1],
                             ],
                             spacingfunc="const({}, {}, {})".format(
-                                options["ranges"]["ecc"][
+                                self.grid_options['m&s_options']["ranges"]["ecc"][
                                     0
                                 ],  # Just fail if not defined.
-                                options["ranges"]["ecc"][1],
-                                options["resolutions"]["ecc"][2],
+                                self.grid_options['m&s_options']["ranges"]["ecc"][1],
+                                self.grid_options['m&s_options']["resolutions"]["ecc"][2],
                             ),
                         )
 
@@ -3751,13 +3848,13 @@ eccentricity3=0
         # the real probability. The trick we use is to strip the options_dict as a string
         # and add some keys to it:
 
-        probdist_addition = "Moe_de_Stefano_2017_pdf({{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}}, verbosity=self.grid_options['verbosity'])['total_probdens']".format(
-            str(options)[1:-1],
+        updated_options = "{{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}}".format(
+            str(dict(self.grid_options['m&s_options']))[1:-1],
             '"multiplicity": multiplicity',
-            '"M1": M_1',
-            '"M2": M_2',
-            '"M3": M_3',
-            '"M4": M_4',
+            '"M_1": M_1',
+            '"M_2": M_2',
+            '"M_3": M_3',
+            '"M_4": M_4',
             '"P": orbital_period',
             '"P2": orbital_period_triple',
             '"P3": orbital_period_quadruple',
@@ -3766,11 +3863,20 @@ eccentricity3=0
             '"ecc3": eccentricity3',
         )
 
+        probdist_addition = "Moe_de_Stefano_2017_pdf({}, verbosity=self.grid_options['verbosity'])['total_probdens']".format(
+            updated_options
+        )
+
         # and finally the probability calculator
         self.grid_options["_grid_variables"][self.last_grid_variable()][
             "probdist"
         ] = probdist_addition
 
+        # Add the options to the grid
+        # self.grid_options["_grid_variables"][self.last_grid_variable()][
+        #     "precode"
+        # ] += "; self.grid_options['m&s_ensemble_options'] = {}".format(o)
+
         verbose_print(
             "\tMoe_de_Stefano_2017: Added final call to the pdf function".format(
                 max_multiplicity
@@ -3782,6 +3888,25 @@ eccentricity3=0
         # Signal that the M&S grid has been set
         self.grid_options['_set_ms_grid'] = True
 
+    ################################################################################################
+    def Moe_de_Stefano_2017(self, options=None):
+        """
+        Function to handle setting the user input settings,
+        set up the data and load that into interpolators and
+        and then set the distribution functions
+
+        Takes a dictionary as its only argument
+        """
+
+        # Set the userinput
+        self.set_moe_distefano_settings(options=options)
+
+        # Load the data
+        self._load_moe_distefano_data()
+
+        # construct the grid here
+        self._set_moe_distefano_distributions()
+
     def _clean_interpolators(self):
         """
         Function to clean up the interpolators after a run
@@ -3799,3 +3924,93 @@ eccentricity3=0
             Moecache[key].destroy()
             del Moecache[key]
         gc.collect()
+
+    ##### Unsorted functions
+    def _calculate_multiplicity_fraction(self, system_dict):
+        """
+        Function to calculate multiplicity fraction
+
+        Makes use of the self.bse_options['multiplicity'] value. If its not set, it will raise an error
+
+        grid_options['multiplicity_fraction_function'] will be checked for the choice
+        """
+
+        # Just return 1 if no option has been chosen
+        if self.grid_options['multiplicity_fraction_function'] == 0:
+            print("Chosen not to use any multiplicity fraction.")
+            return 1
+
+        # Raise an error if the multiplicity is not set
+        if not system_dict.get('multiplicity', None):
+            msg = "Multiplicity value has not been set. When using a specific multiplicity fraction function please set the multiplicity"
+            raise ValueError(msg)
+
+        # Go over the chosen options
+        if self.grid_options['multiplicity_fraction_function'] == 1:
+            # Arenou 2010 will be used
+            verbose_print(
+                "_calculate_multiplicity_fraction: Using Arenou 2010 to calculate multiplicity fractions",
+                self.grid_options["verbosity"],
+                1,
+            )
+
+            binary_fraction = Arenou2010_binary_fraction(system_dict['M_1'])
+            multiplicity_fraction_dict = {1: 1-binary_fraction, 2: binary_fraction, 3: 0, 4: 0}
+
+        elif self.grid_options['multiplicity_fraction_function'] == 2:
+            # Raghavan 2010 will be used
+            verbose_print(
+                "_calculate_multiplicity_fraction: Using Rhagavan 2010 to calculate multiplicity fractions",
+                self.grid_options["verbosity"],
+                1,
+            )
+
+            binary_fraction = raghavan2010_binary_fraction(system_dict['M_1'])
+            multiplicity_fraction_dict = {1: 1-binary_fraction, 2: binary_fraction, 3: 0, 4: 0}
+
+        elif self.grid_options['multiplicity_fraction_function'] == 3:
+            # We need to check several things now here:
+
+            # First, are the options for the M&S grid set? On start it is filled with the default settings
+            if not self.grid_options['m&s_options']:
+                msg = "The M&S options do not seem to be set properly. The value is {}".format(self.grid_options['m&s_options'])
+                raise ValueError(msg)
+
+            # Second: is the moecache filled.
+            if not Moecache:
+                verbose_print(
+                    "_calculate_multiplicity_fraction: Moecache is empty. It needs to be filled with the data for the interpolators. Loading the data now",
+                    self.grid_options["verbosity"],
+                    1,
+                )
+
+                # Load the data
+                self._load_moe_distefano_data()
+
+            # record the prev value
+            prev_M1_value_ms = self.grid_options['m&s_options'].get('M_1', None)
+
+            # Set value of M1 of the current system
+            self.grid_options['m&s_options']['M_1'] = system_dict['M_1']
+
+            # Calculate the multiplicity fraction
+            multiplicity_fraction_list = Moe_de_Stefano_2017_multiplicity_fractions(self.grid_options['m&s_options'], self.grid_options["verbosity"])
+
+            # Turn into dict
+            multiplicity_fraction_dict = {el+1:multiplicity_fraction_list[el] for el in range(len(multiplicity_fraction_list))}
+
+            # Set the prev value back
+            self.grid_options['m&s_options']['M_1'] = prev_M1_value_ms
+
+        # we don't know what to do next
+        else:
+            msg = "Chosen value for the multiplicity fraction function is not known."
+            raise ValueError(msg)
+
+        verbose_print(
+            "Multiplicity: {} multiplicity_fraction: {}".format(system_dict['multiplicity'], multiplicity_fraction_dict[system_dict['multiplicity']]),
+            self.grid_options["verbosity"],
+            1,
+        )
+
+        return multiplicity_fraction_dict[system_dict['multiplicity']]
diff --git a/binarycpython/utils/grid_options_defaults.py b/binarycpython/utils/grid_options_defaults.py
index 667b552b89e36e7f8544a2e89ead0bf9d7331ee1..5d9240ecb6823bd2e5b7031faf7c9fefd1b4f143 100644
--- a/binarycpython/utils/grid_options_defaults.py
+++ b/binarycpython/utils/grid_options_defaults.py
@@ -11,8 +11,6 @@ There are several other functions in this module, mostly to generate help texts
 With this its also possible to automatically generate a document containing all the setting names + descriptions. 
 
 All the options starting with _ should not be changed by the user except when you really know what you're doing (which is probably hacking the code :P)
-
-# TODO: add moe distefano
 """
 
 import os
@@ -24,26 +22,24 @@ _MS_VERBOSITY_LEVEL = 5
 _MS_VERBOSITY_INTERPOLATOR_LEVEL = 6
 _MS_VERBOSITY_INTERPOLATOR_EXTRA_LEVEL = 7
 
-
 # Options dict
 grid_options_defaults_dict = {
     ##########################
     # general (or unordered..)
     ##########################
     "amt_cores": 1,  # total amount of cores used to evolve the population
-    "binary": 0,  # FLag on whether the systems are binary systems or single systems.
     "parse_function": None,  # FUnction to parse the output with.
+    "multiplicity_fraction_function": 0, # Which multiplicity fraction function to use. 0: None, 1: Arenou 2010, 2: Rhagavan 2010, 3: M&S 2017
     "tmp_dir": temp_dir(),  # Setting the temp dir of the program
     "_main_pid": -1,  # Placeholder for the main process id of the run.
     "combine_ensemble_with_thread_joining": True,  # Flag on whether to combine everything and return it to the user or if false: write it to data_dir/ensemble_output_{popuation_id}_{thread_id}.json
-    # "output_dir":
     "_commandline_input": "",
     "log_runtime_systems": 0,  # whether to log the runtime of the systems (1 file per thread. stored in the tmp_dir)
     "_actually_evolve_system": True,  # Whether to actually evolve the systems of just act as if. for testing. used in _process_run_population_grid
-    "max_queue_size": 1000,  # Maximum size of the system call queue. Can't be too big!
-    "_set_ms_grid": False, # Whether the M&S grid has been loaded
+    "max_queue_size": 1000,  # Maximum size of the system call queue.
     "run_zero_probability_system": True, # Whether to run the zero probability systems
     "_zero_prob_stars_skipped": 0,
+    "ensemble_factor_in_probability_weighted_mass": False, # Whether to multiply the ensemble results by 1/probability_weighted_mass
     ##########################
     # Execution log:
     ##########################
@@ -65,6 +61,12 @@ grid_options_defaults_dict = {
     ),  # TODO: make this more robust
     "_binary_c_dir": os.environ["BINARY_C"],
     ##########################
+    # M&S internal settings
+    ##########################
+    "_loaded_ms_data": False, # Holds flag whether the M&S data is loaded into memory
+    "_set_ms_grid": False, # Whether the M&S grid has been loaded
+    "m&s_options": None, # Holds the M&S options.
+    ##########################
     # Custom logging
     ##########################
     "C_auto_logging": None,  # Should contain a dictionary where the kes are they headers
@@ -484,7 +486,11 @@ grid_options_descriptions = {
     "max_queue_size": "Maximum size of the queue that is used to feed the processes. Don't make this too big! Default: 1000. Input: int",
     "_set_ms_grid": "Internal flag whether the M&S grid has been loaded",
     "run_zero_probability_system": "Whether to run the zero probability systems. Default: True. Input: boolean",
-    "_zero_prob_stars_skipped": "Internal counter to track how many systems are skipped because they have 0 probability"
+    "_zero_prob_stars_skipped": "Internal counter to track how many systems are skipped because they have 0 probability",
+    "ensemble_factor_in_probability_weighted_mass": "Flag to multiply all the ensemble results with 1/probability_weighted_mass",
+    "multiplicity_fraction_function": "Which multiplicity fraction function to use. 0: None, 1: Arenou 2010, 2: Rhagavan 2010, 3: M&S 2017",
+    "m&s_options": "Internal variable that holds the M&S options. Don't write to this your self",
+    "_loaded_ms_data": "Internal variable storing whether the M&S data has been loaded into memory",
 }
 
 ###
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 8424ee5026658de08bfdbdc2de9017a52d9c8006..cb25228599ceb5b3605588e6989f2041502d7907 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,15 +1,18 @@
-# Configuration file for the Sphinx documentation builder.
-#
-# This file only contains a selection of the most common options. For a full
-# list see the documentation:
-# https://www.sphinx-doc.org/en/master/usage/configuration.html
-# https://brendanhasz.github.io/2019/01/05/sphinx.html
+"""
+Configuration file for the Sphinx documentation builder.
+
+This file only contains a selection of the most common options. For a full
+list see the documentation:
+https://www.sphinx-doc.org/en/master/usage/configuration.html
+https://brendanhasz.github.io/2019/01/05/sphinx.html
+"""
+
 # -- Path setup --------------------------------------------------------------
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#
+
 import os
 import sys
 
@@ -127,4 +130,4 @@ print("Done")
 from functions import write_custom_footer
 print("Generating custom footer")
 write_custom_footer()
-print("Done")
\ No newline at end of file
+print("Done")
diff --git a/examples/notebooks/basic_example.ipynb b/examples/notebooks/basic_example.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..ab0bd54d76b898f29e6b0bbb07651c415bd192c9
--- /dev/null
+++ b/examples/notebooks/basic_example.ipynb
@@ -0,0 +1,128 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "66ca8e31-7cbf-4484-bb6c-c9235e14bda2",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "import numpy as np\n",
+    "\n",
+    "import binarycpython\n",
+    "from binarycpython.utils.custom_logging_functions import binary_c_log_code\n",
+    "from binarycpython.utils.run_system_wrapper import run_system\n",
+    "from binarycpython.utils.functions import example_parse_output\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "id": "31851c7f-4ccf-4b5a-9f74-8a3e06c6b2d8",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<AxesSubplot:xlabel='time'>"
+      ]
+     },
+     "execution_count": 10,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "<Figure size 432x288 with 1 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# You can decide to `write` your own logging_line, which allows you to write a more complex logging statement with conditionals.\n",
+    "logging_line = 'Printf(\"MY_STELLAR_DATA time=%g radius=%g\\\\n\",stardata->model.time,stardata->star[0].radius)'\n",
+    "\n",
+    "# Generate entire shared lib code around logging lines\n",
+    "custom_logging_code = binary_c_log_code(logging_line, verbose=-1)\n",
+    "\n",
+    "# Run system. all arguments can be given as optional arguments. the custom_logging_code is \n",
+    "# one of them and will be processed automatically.\n",
+    "output = run_system(\n",
+    "    M_1=1, metallicity=0.002, M_2=0.1, separation=0, orbital_period=100000000000, \\\n",
+    "    custom_logging_code=custom_logging_code,\n",
+    ")\n",
+    "\n",
+    "df = pd.DataFrame.from_dict(example_parse_output(output, 'MY_STELLAR_DATA'), dtype=np.float64)\n",
+    "\n",
+    "df.plot('time', 'radius', logy=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "db0f3990-8680-4ea7-80d3-cdb430950c7f",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<AxesSubplot:xlabel='time'>"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "<Figure size 432x288 with 1 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "39509b1c-f5fb-4edd-847a-97757c2197df",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}