view build_file.py @ 1:844b73d2f40c draft

planemo upload for repository https://github.com/muon-spectroscopy-computational-project/muon-galaxy-tools/main/muspinsim_config commit 1f0d5da860dd5cd0ffceac7a292773ed53617324
author muon-spectroscopy-computational-project
date Thu, 15 Sep 2022 10:18:52 +0000
parents c70012022f0f
children 331d0776abb4
line wrap: on
line source

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()