changeset 0:c70012022f0f draft

planemo upload for repository https://github.com/muon-spectroscopy-computational-project/muon-galaxy-tools/main/muspinsim_config commit d130cf2c46d933fa9d0214ddbd5ddf860f322dc4
author muon-spectroscopy-computational-project
date Thu, 25 Aug 2022 16:16:47 +0000
parents
children 844b73d2f40c
files build_file.py muspinsim_config.xml sample_fitting_data.dat test-data/test_1.in test-data/test_2.in test-data/test_3.in test-data/test_4.in test-data/test_5.in
diffstat 8 files changed, 1025 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/build_file.py	Thu Aug 25 16:16:47 2022 +0000
@@ -0,0 +1,397 @@
+import json
+import re
+import sys
+
+from muspinsim import MuSpinInput
+
+
+def write_file(file_name, content):
+    """
+    Write muspinsim file
+    :param file_name: name of file
+    :param content: list of strings containing blocks to write
+    """
+    with open(file_name, "w") as f:
+        f.write(
+            """
+#######################################################
+#Muspinsim Input File
+#Generated using Muon Galaxy Tool Muspinsim_Input
+#######################################################\n\n"""
+        )
+        f.write("".join(content))
+
+
+def build_block(title, vals):
+    """
+    Build keyword block
+    :param title: string - Keyword
+    :param vals: list of strings - lines containing values for keyword
+    :return: A string containing formatted keyword block
+    """
+    return "{0}\n    {1}\n".format(title, "\n    ".join(vals))
+
+
+def format_entry(entry):
+    """
+    Helper function to remove whitespace between function parameters
+    and remove ',' or ';' inbetween parameters
+    :param entry: string - user entry
+    :return: string containing only valid parameters
+    """
+    stck = []
+    new_str = ""
+    for i, char in enumerate(entry):
+        if char == "(":
+            stck.append(i)
+        elif char == ")":
+            if len(stck) == 0:
+                raise ValueError(
+                    "Could not parse entry {0}"
+                    "brackets mismatch - unexpected ')' "
+                    "found on char {1}".format(entry, i)
+                )
+            stck.pop()
+        elif char == " " and len(stck) > 0:
+            continue
+
+        # remove ',' between functions
+        elif char in [",", ";"] and len(stck) == 0:
+            new_str += " "
+            continue
+        new_str += char
+
+    if len(stck) != 0:
+        raise ValueError(
+            "Could not parse entry {0}"
+            "brackets mismatch - unclosed '(' found on char(s): {1}".format(
+                entry, stck
+            )
+        )
+    return new_str
+
+
+def split_into_args(entry, nargs=1):
+    """
+    Helper function to split input into a list of args
+    :param entry: a string containing a user inputted line
+    :param nargs: number of expected arguments
+    :return: a list of arguments found
+    :exception: ValueError - if number of arguments
+        found does not match expected (nargs)
+    """
+
+    # remove square brackets and extra whitespace/newline
+    content = " ".join(entry.replace("[", "").replace("]", "").split())
+
+    # remove whitespace in between expressions/functions
+    # remove commas/semicolons in between expressions/functions
+    # split on whitespace to separate args
+
+    content = re.split(r"\s", format_entry(content))
+    chars = [elem.strip() for elem in content if elem != ""]
+    if len(chars) != nargs:
+        raise ValueError(
+            "Could not parse entry {0}"
+            " incorrect number of args"
+            " found {1}:\n({2})\nBut expected {3}".format(
+                entry, len(chars), chars, nargs
+            )
+        )
+    return chars
+
+
+def parse_matrix(entry_string, size):
+    """
+    Helper function to parse and format matrix/vector
+    to be readable by Muspinsim
+    :param entry_string: a user input string for a matrix/vector
+    :param size: (x, y) integer tuple: dimensions of matrix
+    :return: a list of strings of length y, each string
+    containing x elements (space separated)
+    """
+    content = split_into_args(entry_string, nargs=size[0] * size[1])
+    return [
+        " ".join(content[x: x + size[0]])
+        for x in range(0, len(content), size[0])
+    ]
+
+
+def parse_interactions(interaction):
+    """
+    Helper function to build keyword blocks for all
+    interaction parameters entered
+        (hyperfine, zeeman, dipolar, quadrupolar and dissipation)
+
+    :param interaction: a dictionary containing all interaction parameters
+    :return: a string containing several formatted blocks
+    """
+
+    options = interaction["interaction_options"]
+    interaction_type = options["interaction"]
+    try:
+        return {
+            "zeeman": lambda options: build_block(
+                "zeeman {0}".format(options["zeeman_index"]),
+                parse_matrix(options["zeeman_vector"], (3, 1)),
+            ),
+            "hyperfine": lambda options: build_block(
+                "hyperfine {0} {1}".format(
+                    options["hfine_index"],
+                    options["hfine_e_index"]
+                    if options["hfine_e_index"]
+                    else "",
+                ).strip(),
+                parse_matrix(options["hfine_matrix"], (3, 3)),
+            ),
+            "dipolar": lambda options: build_block(
+                "dipolar {0} {1}".format(
+                    options["di_index"], options["di_index_2"]
+                ),
+                parse_matrix(options["di_vector"], (3, 1)),
+            ),
+            "quadrupolar": lambda options: build_block(
+                "quadrupolar {0}".format(options["quad_index"]),
+                parse_matrix(options["quad_matrix"], (3, 3)),
+            ),
+            "dissipation": lambda options: build_block(
+                "dissipation {0}".format(options["dis_index"]),
+                [options["dis_val"]],
+            ),
+        }.get(interaction_type)(options)
+    except ValueError as e:
+        raise ValueError("Error occurred when parsing {0}".format(e))
+
+
+def parse_orientation(orientation):
+    """
+    Helper function to parse orientation keyword arguments
+    :param orientation: a dictionary containing one set of
+        orientation arguments
+    :return: a formatted string
+    """
+
+    options = orientation["orientation_options"]
+    preset = options["orientation_preset"]
+
+    return {
+        "zcw": lambda options: "zcw({0})".format(
+            " ".join(split_into_args(options["zcw_n"], 1))
+        ),
+        "eulrange": lambda options: "eulrange({0})".format(
+            " ".join(split_into_args(options["eul_n"], 1))
+        ),
+        "2_polar": lambda options: "{0} {1}".format(
+            " ".join(split_into_args(options["theta"], 1)),
+            " ".join(split_into_args(options["phi"], 1)),
+        ),
+        "3_euler": lambda options: "{0} {1} {2}".format(
+            " ".join(split_into_args(options["eul_1"], 1)),
+            " ".join(split_into_args(options["eul_2"], 1)),
+            " ".join(split_into_args(options["eul_3"], 1)),
+        ),
+        "4_euler": lambda options: "{0} {1} {2} {3}".format(
+            " ".join(split_into_args(options["eul_1"], 1)),
+            " ".join(split_into_args(options["eul_2"], 1)),
+            " ".join(split_into_args(options["eul_3"], 1)),
+            options["weight"],
+        ),
+    }.get(preset)(options)
+
+
+def parse_polarization(polarization):
+    """
+    Helper function to parse polarization keyword arguments
+    :param polarization: a dictionary containing one set
+        of polarization arguments
+    :return: a formatted string
+    """
+    options = polarization["polarization_options"]
+    preset = options["polarization_preset"]
+    if preset != "custom":
+        return preset
+    else:
+        try:
+            return " ".join(split_into_args(options["polarization"], 1))
+        except ValueError:
+            return " ".join(split_into_args(options["polarization"], 3))
+
+
+def parse_field(field):
+    """
+    Helper function to parse field keyword arguments
+    :param field: a dictionary containing one set of field arguments
+    :return: a formatted string
+    """
+    try:
+        return " ".join(split_into_args(field["field"], 1))
+    except ValueError:
+        return " ".join(split_into_args(field["field"], 3))
+
+
+def parse_fitting_variables(fitting_variables):
+    """
+    Helper function to parse field keyword fitting_variables
+    :param fitting_variables: a dictionary containing one set of
+        arguments
+    :return: a formatted string
+    """
+    return "{0} {1} {2} {3}".format(
+        fitting_variables["var_name"].strip().replace(" ", "_"),
+        " ".join(split_into_args(fitting_variables["start_val"], 1))
+        if fitting_variables["start_val"].strip() != ""
+        else "",
+        " ".join(split_into_args(fitting_variables["min_bound"], 1))
+        if fitting_variables["min_bound"].strip() != ""
+        else "",
+        " ".join(split_into_args(fitting_variables["max_bound"], 1))
+        if fitting_variables["max_bound"].strip() != ""
+        else "",
+    ).strip()
+
+
+def parse_spin(spin):
+    if spin["spin_preset"] != "custom":
+        return spin["spin_preset"]
+    else:
+        elem_name = spin["spin"].strip()
+        if elem_name not in ['e', 'mu']:
+            elem_name = elem_name.capitalize()
+        return "{0}{1}".format(
+            int(spin["atomic_mass"]) if spin["atomic_mass"] else "",
+            elem_name
+        ).strip()
+
+
+parse_func_dict = {
+    "spins": lambda values: build_block(
+        "spins",
+        [
+            " ".join(
+                [
+                    parse_spin(entry["spin_options"])
+                    for entry in values
+                ]
+            )
+        ],
+    ),
+    # either 1x3 vector or scalar or function
+    "fields": lambda values: build_block(
+        "field", [parse_field(entry) for entry in values]
+    ),
+    # either scalar or single function
+    "times": lambda values: build_block(
+        "time",
+        [
+            " ".join(split_into_args(entry["time"], 1))
+            for entry in values
+        ],
+    ),
+    # either scalar or single function
+    "temperatures": lambda values: build_block(
+        "temperature",
+        [
+            " ".join(split_into_args(entry["temperature"], 1))
+            for entry in values
+        ],
+    ),
+    "x_axis": lambda value: build_block("x_axis", [value]),
+    "y_axis": lambda value: build_block("y_axis", [value]),
+    "average_axes": lambda values: build_block(
+        "average_axes", values
+    ),
+    "experiment_preset": lambda value: build_block(
+        "experiment", [value]
+    ),
+    "orientations": lambda values: build_block(
+        "orientation {0}".format(euler_convention),
+        [parse_orientation(entry) for entry in values],
+    ),
+    "interactions": lambda values: "".join(
+        [parse_interactions(entry) for entry in values]
+    ),
+    "polarizations": lambda values: build_block(
+        "polarization",
+        [parse_polarization(entry) for entry in values],
+    ),
+    "fitting": lambda value: build_block(
+        "fitting_data", ['load("fitting_data.dat")']
+    ),
+    "fitting_method": lambda value: build_block(
+        "fitting_method", [value]
+    ),
+    "fitting_variables": lambda values: build_block(
+        "fitting_variables",
+        [parse_fitting_variables(entry) for entry in values],
+    ),
+    "fitting_tolerance": lambda value: build_block(
+        "fitting_tolerance",
+        [str(value)],
+    ),
+}
+euler_convention = 'ZYZ'
+
+
+def main():
+    input_json_path = sys.argv[1]
+    mu_params = json.load(open(input_json_path, "r"))
+
+    out_file_name = mu_params["out_file_prefix"].strip().replace(" ", "_")
+
+    # combine all sections
+    mu_params = {
+        **mu_params["spins"],
+        **mu_params["interaction_params"],
+        **mu_params["experiment_params"],
+        **mu_params["fitting_params"]["fitting_options"],
+    }
+
+    # get experiment parameters
+    experiment = mu_params["experiment"]
+    mu_params = {**mu_params, **experiment}
+
+    if experiment["experiment_preset"] == "custom":
+        del mu_params["experiment_preset"]
+    del mu_params["experiment"]
+
+    global euler_convention
+    euler_convention = mu_params["euler_convention"]
+
+    err_found = False
+    file_contents = [
+        build_block("name", [out_file_name.strip().replace(" ", "_")])
+    ]
+    for keyword, val in mu_params.items():
+        if val and val not in ["None"]:
+            try:
+                keyword_func = parse_func_dict.get(keyword)
+                if keyword_func:
+                    file_contents.append(keyword_func(val))
+
+            except ValueError as e:
+                sys.stderr.write(
+                    "Error occurred when parsing {0}\n{1}".format(
+                        keyword, str(e)
+                    )
+                )
+                err_found = True
+
+    if err_found:
+        sys.exit(1)
+
+    write_file("outfile.in", file_contents)
+
+    try:
+        MuSpinInput(open("outfile.in"))
+    except Exception as e:
+        sys.stdout.write(
+            "Warning, This created file may not work properly. "
+            "Error(s) encountered when trying to parse the file : {0}".format(
+                str(e)
+            )
+        )
+        sys.exit(1)
+
+
+if __name__ == "__main__":
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/muspinsim_config.xml	Thu Aug 25 16:16:47 2022 +0000
@@ -0,0 +1,493 @@
+<tool id="muspinsim_config" name="MuSpinSim Configure" version="@TOOL_VERSION@+galaxy@WRAPPER_VERSION@" python_template_version="3.5" profile="22.01">
+    <description>define simulation parameters</description>
+       <macros>
+        <!-- version of underlying tool (PEP 440) -->
+        <token name="@TOOL_VERSION@">1.1.0</token>
+        <!-- version of this tool wrapper (integer) -->
+        <token name="@WRAPPER_VERSION@">0</token>
+        <!-- citation should be updated with every underlying tool version -->
+        <!-- typical fields to update are version, month, year, and doi -->
+        <token name="@TOOL_CITATION@">
+            @software{muspinsim,
+                author = {Sturniolo, Simone and Liborio, Leandro and Owen, Josh and Mudaraddi, Anish and {Muon Spectroscopy Computational Project}},
+                license = {MIT},
+                title = {{muspinsim}},
+                url = {https://github.com/muon-spectroscopy-computational-project/muspinsim},
+                version = {v1.1.0},
+                month = {5},
+                year = {2022},
+                doi = {10.5281/zenodo.6563074}
+            }
+        </token>
+    </macros>
+    <creator>
+        <person givenName="Anish" familyName="Mudaraddi" identifier="https://orcid.org/0000-0002-2135-2705"/>
+        <person givenName="Eli" familyName="Chadwick" url="https://github.com/elichad" identifier="https://orcid.org/0000-0002-0035-6475"/>
+        <organization url="https://muon-spectroscopy-computational-project.github.io/index.html" name="The Muon Spectroscopy Computational Project"/>
+    </creator>
+    <requirements>
+        <requirement type="package" version="@TOOL_VERSION@">muspinsim</requirement>
+    </requirements>
+    <command detect_errors="exit_code"><![CDATA[
+        cp ${__tool_directory__}/sample_fitting_data.dat ./fitting_data.dat &&
+        python ${__tool_directory__}/build_file.py inputs.json
+    ]]></command>
+    <configfiles>
+        <inputs name="inputs" filename="inputs.json" />
+    </configfiles>
+    <inputs>
+        <param type="text" name="out_file_prefix" label="Name" help="A name with which to label this configuration" optional="true" value="muspinsim" />
+        <section name="spins" expanded="true" title="Spins">
+            <repeat name="spins" title="Spins to simulate" min="1" help="Specify the spins to be used in the system. This should include a muon (mu) and one or more electrons (e)">
+                <conditional name="spin_options">
+                    <param name="spin_preset" type="select" value="mu" label="Species" help="Select 'custom' to define own">
+                        <option selected="true" value="mu">mu</option>
+                        <option value="e">e</option>
+                        <option value="custom">custom</option>
+                    </param>
+                    <when value="custom">
+                        <param name="spin" optional="false" type="text" label="Species name"/>
+                        <param name="atomic_mass" optional="true" type="integer" min="0" value="" label="Atomic mass" help="Leave blank to use default mass - whole numbers only"/>
+                    </when>
+                    <when value="mu"/>
+                    <when value="e"/>
+                </conditional>
+            </repeat>
+        </section>
+        <section name="interaction_params" expanded="true" title="Spin Interactions" help="">
+            <repeat name="interactions" title="Interactions to simulate" help="Add couplings between spins, and/or dissipation terms. Interaction terms available: Zeeman, hyperfine, dipolar, quadrupolar or dissipation. See muspinsim docs for more info">
+                <conditional name="interaction_options">
+                    <param name="interaction" type="select" label="Choose interaction type">
+                        <option value="zeeman">Zeeman</option>
+                        <option value="hyperfine">hyperfine</option>
+                        <option value="dipolar">dipolar</option>
+                        <option value="quadrupolar">quadrupolar</option>
+                        <option value="dissipation">dissipation</option>
+                    </param>
+                    <when value="zeeman">
+                        <param name="zeeman_index" type="integer" value="" label="Index of coupled spin" min="1"
+                               help="Index refers to the order of the spins listed in 'spins' section.  The first spin in the list has index 1, the second has index 2, and so on."/>
+                        <param name="zeeman_vector" type="text" value="" label="Zeeman coupling vector"
+                            help="Define 1X3 vector for local magnetic field coupling (T).
+                            Allows default expressions, constants and functions (see help)"/>
+                    </when>
+                    <when value="hyperfine">
+                        <param name="hfine_index" type="integer" value="" label="Index of nuclear coupled spin" min="1"
+                               help="Non-electronic spin - muon or otherwise.
+                               Index refers to the order of the spins listed in 'spins' section.  The first spin in the list has index 1, the second has index 2, and so on."/>
+                        <param name="hfine_e_index" type="integer" value="" optional="true" min="1" label="Index of electronic coupled spin"
+                               help="Optional, will use first defined electronic spin if unspecified"/>
+                        <param name="hfine_matrix" area="true" type="text" value="" label="Hyperfine coupling tensor"
+                               help="Define 3X3 tensor for coupling between electron and non-electron spins (in MHz).
+                               Allows default expressions, constants and functions (see help)">
+                            <sanitizer>
+                                <valid initial="string.printable">
+                                </valid>
+                            </sanitizer>
+                        </param>
+                    </when>
+                    <when value="dipolar">
+                        <param name="di_index" type="integer" value="" min="1" label="Index of 1st coupled spin"
+                               help="Index refers to the order of the spins listed in 'spins' section.  The first spin in the list has index 1, the second has index 2, and so on."/>
+                        <param name="di_index_2" type="integer" value="" label="Index of 2nd coupled spin"
+                               help="Index refers to the order of the spins listed in 'spins' section.  The first spin in the list has index 1, the second has index 2, and so on."/>
+                        <param name="di_vector" type="text" value="" label="Dipole coupling vector"
+                               help="Define 1X3 vector for coupling between two spins (Angstrom).
+                               Allows default expressions, constants and functions (see help)"/>
+                    </when>
+                    <when value="quadrupolar">
+                        <param name="quad_index" type="integer" value="" label="Index of coupled spin"
+                               help="Index refers to the order of the spins listed in 'spins' section.  The first spin in the list has index 1, the second has index 2, and so on."/>
+                        <param name="quad_matrix" area="true" type="text" value="" label="Electric Field Gradient tensor"
+                               help="Define 3X3 tensor (in atomic units) for quadrupolar coupling.
+                               Allows default expressions, constants and functions (see help).
+                               Warning: spins with zero quadrupole moment will have zero coupling regardless of the input">
+                            <sanitizer>
+                                <valid initial="string.printable">
+                                </valid>
+                            </sanitizer>
+                        </param>
+                    </when>
+                    <when value="dissipation">
+                        <param name="dis_index" type="integer" value="" label="Index of spin with dissipation"
+                               help="Index refers to the order of the spins listed in 'spins' section.  The first spin in the list has index 1, the second has index 2, and so on."/>
+                        <param name="dis_val" type="text" value="" label="Dissipation"
+                               help="Define dissipation term (MHz).
+                               Allows default expressions, constants and functions (see help)"/>
+                    </when>
+                </conditional>
+            </repeat>
+        </section>
+        <section name="experiment_params" expanded="true" title="Experiment Parameters">
+            <conditional name="experiment">
+                <param name="experiment_preset" type="select" value='custom' display="radio" label="Experiment type"
+                       help="Experiment preset to use. Avoided Level Crossing (ALC): sets polarization to longitudinal, x-axis to field and y-axis to integral.
+                             Zero field: sets polarization to transverse and field to 0. Choose custom for no preset">
+                    <option value="alc">Avoided Level Crossing (ALC)</option>
+                    <option value="zero_field">Zero Field</option>
+                    <option selected="true" value="custom">Custom</option>
+                </param>
+                <when value="alc">
+                    <repeat name="fields" title="Fields" help="Magnetic fields">
+                        <param name="field" type="text" value="0" label="Field"
+                            optional="true" help="Optional, (default is 0).
+                            Accepts 1X3 vector, or scalar value. Scalar value means field is assumed to be aligned with z axis.
+                            Allows default expressions, constants and functions, plus MHz, muon_gyr constants and special 'range()' function (see help section)"/>
+                    </repeat>
+                </when>
+                <when value="zero_field">
+                    <param name="x_axis" type="select" value="time" display="radio" label="X axis"
+                           help="Range to use as X axis for output file(s)">
+                        <option selected="true" value="time">time</option>
+                        <option value="field">field</option>
+                    </param>
+                    <param name="y_axis" type="select" value="asymmetry" display="radio" label="Y axis"
+                           help="Range to use as y axis for output file(s)
+                                Asymmetry: use muon's polarization.
+                                Integral: use muon's polarization integral over time.
+                                WARNING: if integral chosen, 'time' parameter is ignored, and cannot be used as x-axis parameter ">
+                        <option value="integral">integral</option>
+                        <option selected="true" value="asymmetry">asymmetry</option>
+                    </param>
+                </when>
+                <when value="custom">
+                    <param name="x_axis" type="select" value="time" display="radio" label="X axis"
+                           help="Range to use as X axis for output file(s)">
+                        <option selected="true" value="time">time</option>
+                        <option value="field">field</option>
+                    </param>
+                    <param name="y_axis" type="select" value="asymmetry" display="radio" label="Y axis"
+                           help="Range to use as y axis for output file(s)
+                                Asymmetry: use muon's polarization.
+                                Integral: use muon's polarization integral over time.
+                                WARNING: if integral chosen, 'time' parameter is ignored, and cannot be used as x-axis parameter ">
+                        <option value="integral">integral</option>
+                        <option selected="true" value="asymmetry">asymmetry</option>
+                    </param>
+                    <repeat name="fields" title="Fields" help="Magnetic fields">
+                         <param name="field" type="text" value="0" label="Field (T)"
+                            optional="true" help="Optional, (default is 0).
+                            Accepts 1X3 vector, or scalar value. Scalar value means field is assumed to be aligned with z axis.
+                            Allows default expressions, constants and functions, plus MHz, muon_gyr constants and special 'range()' function (see help section)"/>
+                    </repeat>
+                    <repeat name="polarizations" title="Polarizations" help="The direction along which the muon
+                    should be polarized when starting, as well as the one in which it will be measured.
+                    Each entry will generate a separate calculation when muspinsim is run">
+                        <conditional name="polarization_options">
+                            <param name="polarization_preset" value="custom" type="select" display="radio" label="Polarization"
+                                   help="transverse: along x-axis, longitudinal: along z-axis, custom: define vector">
+                                <option value="longitudinal">longditudinal</option>
+                                <option value="transverse">transverse</option>
+                                <option selected="true" value="custom">custom</option>
+                            </param>
+                            <when value="custom">
+                                <param name="polarization" type="text" value="" label="Enter custom vector for polarization"
+                                       help="Accepts 1X3 vector.
+                                       Allows default expressions, constants and functions (see help section)"/>
+                            </when>
+                            <when value="longitudinal" />
+                            <when value="transverse" />
+                        </conditional>
+                    </repeat>
+                </when>
+            </conditional>
+            <param name="average_axes" type="select" display="checkboxes" multiple="true" optional="true" value="orientation" label="Average axes"
+                   help="Keywords that should have an average carried out over them. Each keyword ticked should have a range specified.
+                   Keywords not ticked or set as X axis, but which have a range set, will generate separate calculations for each value in the range when muspinsim is run.">
+                <option selected="true" value="orientation">orientation</option>
+                <option value="polarization">polarization</option>
+                <option value="field">field</option>
+                <option value="time">time</option>
+                <option value="temperature">temperature</option>
+            </param>
+            <repeat name="orientations" title="Orientations" help="Orientations to use for crystallites - (define powder averages)">
+                <conditional name="orientation_options">
+                    <param name="orientation_preset" type="select" display="radio" label="Orientation" optional="false"
+                           help="Polar angles: Define two polar angles θ and ϕ, defining only the direction of the z-axis (recommended for powder averages).
+                                Euler angles: Define 3 Euler angles defining a new frame, convention used is ZYZ by default.
+                                Euler angles with weight: define 3 Euler angles and a weight (will be normalized automatically).
+                                Euler angles helper function: eulrange(n).
+                                Zaremba-Conroy-Wolfsberg helper function: zcw(n)">
+                        <option value="2_polar">Polar angles</option>
+                        <option value="3_euler">Euler angles</option>
+                        <option value="4_euler">Euler angles with weight</option>
+                        <option value="eulrange">Euler angles helper function (eulrange(n))</option>
+                        <option selected="true" value="zcw">Zaremba-Conroy-Wolfsberg helper function (zcw(n))</option>
+                    </param>
+                    <when value="eulrange">
+                        <param name="eul_n" type="text" value="0" label="n"
+                               help="value for n for eulrange(n),
+                               Allows default expressions, constants and functions.
+                               WARNING: large values are more computationally expensive."
+                        />
+                    </when>
+                    <when value="zcw">
+                        <param name="zcw_n" type="text" value="0" label="n"
+                               help="value for n for zcw(n)
+                               Allows default expressions, constants and functions.
+                               WARNING: large values are more computationally expensive."
+                        />
+                    </when>
+                    <when value="2_polar">
+                        <param name="theta" type="text" value="" label="θ (theta/inclination) angle"
+                               help="All polar angle entries allow default expressions, constants and functions"/>
+                        <param name="phi" type="text" value="" label="ϕ (phi/azimuth) angle"
+                               help="All polar angle entries allow default expressions, constants and functions"/>
+                    </when>
+                    <when value="3_euler">
+                        <param name="eul_1" type="text" value="" label="Euler angle 1"
+                               help="All Euler angle entries allow default expressions, constants and functions"/>
+                        <param name="eul_2" type="text" value="" label="Euler angle 2"
+                               help="All Euler angle entries allow default expressions, constants and functions"/>
+                        <param name="eul_3" type="text" value="" label="Euler angle 3"
+                               help="All Euler angle entries allow default expressions, constants and functions"/>
+
+                    </when>
+                    <when value="4_euler">
+                        <param name="eul_1" type="text" value="" label="Euler angle 1"
+                               help="All Euler angle entries allow default expressions and constants"/>
+                        <param name="eul_2" type="text" value="" label="Euler angle 2"
+                               help="All Euler angle entries allow default expressions and constants"/>
+                        <param name="eul_3" type="text" value="" label="Euler angle 3"
+                               help="All Euler angle entries allow default expressions and constants"/>
+                        <param name="weight" type="float" value="0" label="Weight"
+                               help="Allows only floating point value, weights will automatically be normalised"/>
+                    </when>
+                </conditional>
+            </repeat>
+            <param name="euler_convention" type="select" display="radio" label="Euler Convention"
+                   help="Euler angle convention to use for orientation definitions (ignored if Euler angles not defined)">
+                <option selected="true" value="zyz">ZYZ</option>
+                <option value="zxz">ZXZ</option>
+            </param>
+            <repeat name="times" title="Time" help="A time or range of times (μs)">
+                <param name="time" type="text" label="Time"
+                       help="Either a single time value or `range` function
+                       Allows default expressions, default constants, and special 'range()' function (see help section)"
+                       optional="true" value="range(0, 10, 101)"/>
+            </repeat>
+            <repeat name="temperatures" title="Temperature" help="Temperature or range of temperatures (K)">
+                <param name="temperature" type="text"
+                       help="Either single value or 'range()' function
+                       Allows default expressions, default constants, and special 'range()' function (see help section).
+                       Warning: both density matrices and dissipative couplings for finite temperatures are only calculated approximatively, see muspinsim docs."
+                       optional="true" value="inf"/>
+            </repeat>
+        </section>
+        <section name="fitting_params" expanded="true" title="Fitting Parameters">
+            <conditional name="fitting_options">
+                <param name="fitting" type="select" display="radio" optional="false" value="" label="Fit experimental data with simulations"
+                       help="Fitting requires a file with data to fit. File must be given in muspinsim tool, or by manually setting filepath for keyword 'fitting_data' if running muspinsim externally">
+                    <option value="true">Yes</option>
+                    <option selected="true" value="">No</option>
+                </param>
+                <when value="true">
+                    <param name="fitting_method" type="select" display="radio" optional="false" value="nelder-mead" label="Method to use to fit the data"
+                           help="See the help section for a description of each method">
+                        <option selected="true" value="nelder-mead">Nelder-Mead</option>
+                        <option value="lbfgs">L-BFGS</option>
+                    </param>
+                    <repeat name="fitting_variables" title="Variable to fit to the experimental data">
+                        <param name="var_name" type="text" optional="false" label="Name of the variable"/>
+                        <param name="start_val" type="text" value="0" label="Starting value"
+                               help="Allows default expressions, constants and functions, plus MHz, muon_gyr constants. Cannot contain names of other variables"/>
+                        <param name="min_bound" type="text" value="-inf" label="minimum bound"
+                               help="Allows default expressions, constants and functions, plus MHz, muon_gyr constants"/>
+                        <param name="max_bound" type="text" value="inf" label="maximum bound"
+                               help="Allows default expressions, constants and functions, plus MHz, muon_gyr constants"/>
+                    </repeat>
+                    <param name="fitting_tolerance" type="float" optional='true' value="" label="Fitting Tolerance"
+                           help="Used as the tol parameter in Scipy's scipy.optimize.minimize method. Will use scipy defaults if left blank.
+                           Does not accept expressions/functions/constants"
+                    />
+                </when>
+                <when value=""/>
+            </conditional>
+        </section>
+    </inputs>
+    <outputs>
+        <data format="txt" label="muspinsim input file $out_file_prefix" name="out_file" from_work_dir="outfile.in" />
+    </outputs>
+    <tests>
+        <test>
+            <param name="out_file_prefix" value="test_1"/>
+            <param name="spin_preset" value="custom"/>
+            <param name="spin" value="H"/>
+            <param name="spin_preset" value="mu"/>
+            <param name="interaction" value="zeeman"/>
+            <param name="zeeman_index" value="1" />
+            <param name="zeeman_vector" value="1 0 0" />
+            <param name="zeeman_index" value="2" />
+            <param name="zeeman_vector" value="2 0 0" />
+            <output name="out_file" file="test_1.in" ftype="txt" compare="diff" />
+        </test>
+        <test>
+            <param name="out_file_prefix" value="test_2"/>
+            <param name="spin_preset" value="e"/>
+            <param name="spin_preset" value="mu"/>
+            <param name="interaction" value="hyperfine"/>
+            <param name="hfine_index" value="1" />
+            <param name="hfine_matrix" value="[1 0 0 sin(10) (5*2) 0 10*pi 5 cos(20)]"/>
+            <param name="time" value="range(0, 0.1)" />
+            <param name="y_axis" value="asymmetry" />
+            <param name="field" value="1.0" />
+            <param name="temperature" value="1.0" />
+            <output name="out_file" file="test_2.in" ftype="txt" compare="diff" />
+        </test>
+        <test>
+            <param name="out_file_prefix" value="test_3"/>
+            <param name="spin_preset" value="custom"/>
+            <param name="spin" value="H"/>
+            <param name="spin_preset" value="mu"/>
+            <param name="spin_preset" value="e"/>
+            <param name="interaction" value="hyperfine"/>
+            <param name="hfine_index" value="2" />
+            <param name="hfine_matrix" value="[580 5  10 5   580 9 10  9   580]"/>
+            <param name="interaction" value="hyperfine"/>
+            <param name="hfine_index" value="3" />
+            <param name="hfine_matrix" value="[(300/2)  3   4*10 ], [3  15*10 6-3+2]  ,[4  5   15 ]"/>
+            <param name="average_axes" value="polarization,temperature" />
+            <param name="experiment_preset" value="alc" />
+            <param name="field" value="range(1.8, 2.6, 100)" />
+            <param name="orientation_preset" value="zcw" />
+            <param name="zcw_n" value="20" />
+            <output name="out_file" file="test_3.in" ftype="txt" compare="diff" />
+        </test>
+        <test>
+            <param name="out_file_prefix" value="test_4"/>
+            <param name="spin_preset" value="custom"/>
+            <param name="spin" value="F"/>
+            <param name="spin_preset" value="custom"/>
+            <param name="spin" value="F"/>
+            <param name="spin_preset" value="mu"/>
+            <param name="interaction" value="dipolar"/>
+            <param name="di_index" value="1"/>
+            <param name="di_index_2" value="2"/>
+            <param name="di_vector" value="0.9 0.9 0"/>
+            <param name="interaction" value="dipolar"/>
+            <param name="di_index" value="1"/>
+            <param name="di_index_2" value="3"/>
+            <param name="di_vector" value="-0.9 -0.9 0"/>
+            <param name="interaction" value="dissipation"/>
+            <param name="dis_index" value="1"/>
+            <param name="dis_val" value="0.5"/>
+            <param name="average_axes" value="" />
+            <param name="experiment_preset" value="custom" />
+            <param name="field" value="1.5e-2 1.0e-2 1.0e-2"/>
+            <param name="field" value="0.01"/>
+            <param name="polarization_preset" value="custom"/>
+            <param name="polarization" value="1 0 0"/>
+            <param name="time" value="range(0,8.0,1000)"/>
+            <param name="time" value="range(0,1.0)"/>
+            <param name="orientation_preset" value="eulrange"/>
+            <param name="eul_n" value="10"/>
+            <output name="out_file" file="test_4.in" ftype="txt" compare="diff" />
+        </test>
+        <test>
+            <param name="out_file_prefix" value="test_5"/>
+            <param name="spin_preset" value="mu"/>
+            <param name="interaction" value="dissipation"/>
+            <param name="dis_index" value="1"/>
+            <param name="dis_val" value="g"/>
+            <param name="experiment_preset" value="custom" />
+            <param name="field" value="1.0/muon_gyr" />
+            <param name="fitting" value="true"/>
+            <param name="var_name" value="g" />
+            <param name="min_bound" value="0.0" />
+            <param name="fitting_tolerance" value="1.0" />
+            <output name="out_file" file="test_5.in" ftype="txt" compare="diff" />
+        </test>
+    </tests>
+    <help><![CDATA[
+    Tool to create input parameter file for Muspinsim.
+
+
+    This tool creates a structured text file with keywords and values which describe the system to model for Muspinsim.
+    See muspinsim docs for more information https://muon-spectroscopy-computational-project.github.io/muspinsim/input/.
+
+
+    Muspinsim allows expressions and special functions to be used when defining certain keywords. This tool also allows this.
+    Check the hint at the bottom of each input to see what, if any, special function or expressions can be used.
+
+
+    Default expressions include the use of the operators :code:`+ - * /` and :code:`^` for exponentiation. Expressions
+    should not contain whitespace. For example, use :code:`1+2` not :code:`1 + 2`.
+
+
+    Default constants include:
+        - :code:`pi`: ratio of a circle and its diameter
+        - :code:`e`: base of the natural logarithm
+        - :code:`deg`: conversion factor between radians and degrees, equivalent to 180/pi
+        - :code:`inf`: infinity
+
+    Special constants include:
+        - :code:`muon_gyr`: gyromagnetic ratio of muon (135.5388 MHz/T)
+        - :code:`MHz`: :code:`1/(2*muon_gyr)`
+
+
+    Default functions include:
+        - :code:`sin(x)`: sine
+        - :code:`cos(x)`: cosine
+        - :code:`tan(x)`: tangent
+        - :code:`arcsin(x)`: inverse of the sine
+        - :code:`arccos(x)`: inverse of the cosine
+        - :code:`arctan(x)`: inverse of the tangent
+        - :code:`arctan2(y, x)`: inverse of the tangent taking two arguments as (sine, cosine) to resolve the quadrant
+        - :code:`exp(x)`: exponential with base e
+        - :code:`log(x)`: natural logarithm
+        - :code:`sqrt(x)`: square root
+
+    Special functions include:
+        - :code:`range(x, y, z)`: get z equally spaced values between x and y
+        - :code:`zcw(n)`: Zaremba-Conroy-Wolfsberg helper function
+        - :code:`eulrange(n)`: helper function to create regular grid of n × n × n Euler angles with appropriate weights.
+
+
+    To enter vectors or matrices the following formats are accepted:
+        - :code:`[[1, 2, 3], [4, 5, 6], [7, 8, 9]]`
+        - :code:`1 2 3 4 5 6 7 8 9`
+        - :code:`[1 2 3] [4 5 6] [7 8 9]`
+
+
+    The fitting (function minimization) algorithms available are:
+
+
+    Nelder-Mead
+        - A direct search method.
+        - Starting with a 'simplex' of candidates, the algorithm will iteratively move the position of the worst candidate towards the optimum until all candidates converge (have values within a predefined tolerance level).
+        - Scipy default tolerance is :code:`1e-4`
+
+
+    L-BFGS
+        - Limited Memory Broyden–Fletcher–Goldfarb–Shanno algorithm.
+        - A second-order Quasi-Newton optimization algorithm
+        - Makes use of second-order derivative (Hessian Matrix) to converge on optimum.
+        - Scipy default tolerance is :code:`1e-5`
+    ]]></help>
+    <citations>
+        <citation type="bibtex">
+            @article{nelder_mead_1965,
+                title={A Simplex Method for Function Minimization},
+                volume={7},
+                doi={10.1093/comjnl/7.4.308},
+                number={4},
+                journal={The Computer Journal},
+                author={Nelder, J. A. and Mead, R.},
+                year={1965},
+                pages={308-313}
+            }
+        </citation>
+        <citation type="bibtex">
+            @book{nocedal_wright_2006,
+                place={New York (NY)},
+                title={Numerical Optimization},
+                publisher={Springer},
+                author={Nocedal, Jorge and Wright, Stephen J},
+                year={2006}
+            }
+        </citation>
+        <citation type="bibtex">
+            @TOOL_CITATION@
+        </citation>
+    </citations>
+</tool>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample_fitting_data.dat	Thu Aug 25 16:16:47 2022 +0000
@@ -0,0 +1,4 @@
+0 1
+2 3
+4 5
+6 7
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/test_1.in	Thu Aug 25 16:16:47 2022 +0000
@@ -0,0 +1,20 @@
+
+#######################################################
+#Muspinsim Input File
+#Generated using Muon Galaxy Tool Muspinsim_Input
+#######################################################
+
+name
+    test_1
+spins
+    mu H
+zeeman 2
+    2 0 0
+zeeman 1
+    1 0 0
+average_axes
+    orientation
+x_axis
+    time
+y_axis
+    asymmetry
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/test_2.in	Thu Aug 25 16:16:47 2022 +0000
@@ -0,0 +1,26 @@
+
+#######################################################
+#Muspinsim Input File
+#Generated using Muon Galaxy Tool Muspinsim_Input
+#######################################################
+
+name
+    test_2
+spins
+    mu e
+hyperfine 1
+    1 0 0
+    sin(10) (5*2) 0
+    10*pi 5 cos(20)
+average_axes
+    orientation
+time
+    range(0,0.1)
+temperature
+    1.0
+x_axis
+    time
+y_axis
+    asymmetry
+field
+    1.0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/test_3.in	Thu Aug 25 16:16:47 2022 +0000
@@ -0,0 +1,27 @@
+
+#######################################################
+#Muspinsim Input File
+#Generated using Muon Galaxy Tool Muspinsim_Input
+#######################################################
+
+name
+    test_3
+spins
+    e mu H
+hyperfine 3
+    (300/2) 3 4*10
+    3 15*10 6-3+2
+    4 5 15
+hyperfine 2
+    580 5 10
+    5 580 9
+    10 9 580
+average_axes
+    polarization
+    temperature
+orientation zyz
+    zcw(20)
+field
+    range(1.8,2.6,100)
+experiment
+    alc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/test_4.in	Thu Aug 25 16:16:47 2022 +0000
@@ -0,0 +1,30 @@
+
+#######################################################
+#Muspinsim Input File
+#Generated using Muon Galaxy Tool Muspinsim_Input
+#######################################################
+
+name
+    test_4
+spins
+    mu F F
+dissipation 1
+    0.5
+dipolar 1 3
+    -0.9 -0.9 0
+dipolar 1 2
+    0.9 0.9 0
+orientation zyz
+    eulrange(10)
+time
+    range(0,1.0)
+    range(0,8.0,1000)
+x_axis
+    time
+y_axis
+    asymmetry
+field
+    0.01
+    1.5e-2 1.0e-2 1.0e-2
+polarization
+    1 0 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/test_5.in	Thu Aug 25 16:16:47 2022 +0000
@@ -0,0 +1,28 @@
+
+#######################################################
+#Muspinsim Input File
+#Generated using Muon Galaxy Tool Muspinsim_Input
+#######################################################
+
+name
+    test_5
+spins
+    mu
+dissipation 1
+    g
+average_axes
+    orientation
+fitting_method
+    nelder-mead
+fitting_variables
+    g 0 0.0 inf
+fitting_tolerance
+    1.0
+fitting_data
+    load("fitting_data.dat")
+x_axis
+    time
+y_axis
+    asymmetry
+field
+    1.0/muon_gyr