Source code for despasito.input_output.read_input

""" Routines for parsing input files in JSON format to dictionaries for program use.
"""

import logging
import json
import numpy as np
import os

logger = logging.getLogger(__name__)


[docs] def append_data_file_path(input_dict, path="."): r""" Appends path from command line option to data file(s). Parameters ---------- input_dict Dictionary of input data path: str relative path to append to existing data files """ if "EOSgroup" in input_dict: input_dict["EOSgroup"] = os.path.join(path, input_dict["EOSgroup"]) if "EOScross" in input_dict: input_dict["EOScross"] = os.path.join(path, input_dict["EOScross"]) for key, val in input_dict.items(): if isinstance(val, dict) and "file" in val: input_dict[key]["file"] = os.path.join(path, input_dict[key]["file"])
[docs] def json_to_dict(filename): r""" Extract contents of JSON formatted file as a dictionary Parameters ---------- filename : str File name and path leading to file (in JSON format) location Returns ------- dictionary : dict Dictionary resulting from file """ with open(filename, "r") as f: output = f.read() dictionary = json.loads(output) return dictionary
[docs] def extract_calc_data(input_fname, path=".", **thermo_dict): r""" Parse dictionary from input file in JSON format into a dictionary. Resulting dictionaries are used for creating the equation of state object, and for passing instructions for thermodynamic calculations. Parameters ---------- input_fname : str The file name of a file in JSON format in the current directory containing (1) the paths to equation of state parameters, (2) :mod:`~despasito.thermodynamics.calculation_types` and inputs for thermodynamic calculations (e.g. density options for :func:`~despasito.thermodynamics.calc.pressure_vs_volume_arrays`). path : str, Optional, default="." Path to ``input_fname`` thermo_dict Additional keyword arguments Returns ------- eos_dict : dict Dictionary of keyword arguments representing bead definitions and parameters used to later initialize Eos object. :func:`despasito.equations_of_state.initiate_eos` thermo_dict : dict Dictionary of keyword arguments for thermodynamic calculations or parameter fitting. :func:`despasito.thermodynamics.thermo` output_file : str Output from calculation. Default is None, but an alternative can be defined as output_file keyword argument. """ # Extract dictionary from input file input_dict = json_to_dict(input_fname) # Look for data (library) files in the user-supplied path append_data_file_path(input_dict, path) if "output_file" in input_dict: output_file = input_dict["output_file"] else: output_file = None # Make bead data dictionary for EOS # process input file if "bead_configuration" in input_dict: beads, molecular_composition = process_bead_data(input_dict["bead_configuration"]) eos_dict = {"beads": beads, "molecular_composition": molecular_composition} elif "optimization_parameters" in input_dict: eos_dict = {} else: raise ValueError("Bead configuration line is missing for thermodynamic calculation.") # read EOS groups file eos_dict["bead_library"] = json_to_dict(input_dict["EOSgroup"]) if "EOScross" in input_dict: eos_dict["cross_library"] = json_to_dict(input_dict["EOScross"]) logger.info("Cross interaction parameters have been accepted") else: logger.info("No EOScross file specified") # Extract relevant system state inputs EOS_dict_keys = ["bead_configuration", "EOSgroup", "EOScross", "output_file"] for key, value in input_dict.items(): if key.startswith("eos_"): new_key = "_".join(key.split("_")[1:]) eos_dict[new_key] = input_dict[key] elif key == "eos": eos_dict["eos"] = input_dict["eos"] elif key not in EOS_dict_keys: thermo_dict[key] = value for key in ["numba", "cython", "python"]: if key in thermo_dict: eos_dict[key] = thermo_dict[key] del thermo_dict[key] if "optimization_parameters" not in thermo_dict: logger.info( "The following thermo calculation parameters have been " "provided: {}\n".format((", ".join(thermo_dict.keys()))) ) else: # parameter fitting thermo_dict = process_param_fit_inputs(thermo_dict) for exp_key in thermo_dict["exp_data"]: if "eos_dict" not in thermo_dict["exp_data"][exp_key]: thermo_dict["exp_data"][exp_key]["eos_dict"] = {} for key, value in eos_dict.items(): if key not in thermo_dict["exp_data"][exp_key]["eos_dict"]: thermo_dict["exp_data"][exp_key]["eos_dict"][key] = value tmp = "" for key, value in thermo_dict["exp_data"].items(): tmp += " {} ({}),".format(key, value["data_class_type"]) logger.info( "The bead, {}, will have the parameters {}, fit using the following " "data:\n {}".format( thermo_dict["optimization_parameters"]["fit_bead"], thermo_dict["optimization_parameters"]["fit_parameter_names"], tmp, ) ) return eos_dict, thermo_dict, output_file
[docs] def file2paramdict(filename, delimiter=" "): r""" Converted text file into a dictionary. Each line in the input file is a key followed by a value in the resulting dictionary. Parameters ---------- filename : str File of keys and values delimiter : str, Optional, default=" " String separating key and value within file Returns ------- dictionary : dict Resulting dictionary """ dictionary = {} with open(filename, "r") as filedata: for line in filedata: line.rstrip() linearray = line.split(delimiter) if len(linearray) == 2: try: dictionary[linearray[0]] = eval(linearray[1]) except Exception: if line[1].isdigit(): dictionary[linearray[0]] = float(linearray[1]) else: dictionary[linearray[0]] = linearray[1] return dictionary
[docs] def process_bead_data(bead_data): r""" Process system information from input file. Parameters ---------- bead_data : list[list] List of molecules in the system. Each molecule is represented as a list of beads and each bead is represented as a list with the bead name followed by an integer with the number of beads in that molecule. Returns ------- beads : list[str] List of unique bead names used among components molecular_composition : numpy.ndarray Array of number of components by number of bead types. Defines the number of each type of group in each component. """ # find list of unique beads beads = [] for i in range(len(bead_data)): for j in range(len(bead_data[i])): if bead_data[i][j][0] not in beads: beads.append(bead_data[i][j][0]) beads.sort() molecular_composition = np.zeros((len(bead_data), len(beads))) for i in range(len(bead_data)): for j in range(len(bead_data[i])): for k in range(np.size(beads)): if bead_data[i][j][0] == beads[k]: molecular_composition[i, k] = bead_data[i][j][1] return beads, molecular_composition
[docs] def process_param_fit_inputs(thermo_dict): r""" Process parameter fitting information. Parameters ---------- thermo_dict : dict Dictionary of instructions for thermodynamic calculations or parameter fitting. This dictionary is directly from the input file. - optimization_parameters (dict) - Parameters used in basin fitting algorithm - fit_bead (str) - Name of bead whose parameters are being fit, should be in bead list of bead_configuration - fit_parameter_names (list[str]) - This list of contains the name of the parameter being fit (e.g. epsilon). See EOS documentation for limitations on supported parameter names. Cross interaction parameter names should be composed of parameter name and the other bead type, separated by an underscore (e.g. epsilon_CO2). - \*_bounds (list[float]), Optional - This list contains the minimum and maximum of a parameter from fit_parameter_names, represented by the asterisk. See input file instructions for more information. - parameters_guess (list[float]), Optional - Initial guess in parameters being fit. Should be the same length at fit_parameter_names and contain a reasonable guess for each parameter. If this is not provided, a guess is made based on the type of parameter from the Eos object. - *name* (dict) - Dictionary representing a data set that the parameters are fit to. Each dictionary is added to the exp_data dictionary before being passed to the fitting algorithm. Each *name* is used as the key in exp_data. *name* is an arbitrary string used to identify the data set and used later in reporting objective function values during the fitting process. See data type objects for more details. - file (str) - File of experimental data - data_class_type (str) - One of the supported data type objects to fit parameters - calculation_type (str) - One of the supported thermo calculation types that would be associated with the chosen data_class_type Returns ------- new_thermo_dict : dict Dictionary of instructions for thermodynamic calculations or parameter fitting. This dictionary is reformatted and includes imported data. Dictionary values below are altered before being passed on, all other key and value sets are blindly passed. - optimization_parameters (dict) - Parameters used in basin fitting algorithm - fit_bead (str) - Name of bead whose parameters are being fit, should be in bead list of bead_configuration - fit_parameter_names (list[str]) - This list of contains the name of the parameter being fit (e.g. epsilon). See EOS documentation for restrictions on supported parameter names. Cross interaction parameter names should be composed of parameter name and the other bead type, separated by an underscore (e.g. epsilon_CO2). - parameters_guess (list[float]), Optional - Initial guess in parameter. If one is not provided, a guess is made based on the type of parameter from Eos object. - \*_bounds (list[float]), Optional - This list contains the minimum and maximum of a parameter in fit_parameter_names, represented in place of the asterisk. See input file instructions for more information. - exp_data (dict) - This dictionary is made up of a dictionary for each data set that the parameters are fit to. Each key is an arbitrary string used to identify the data set and used later in reporting objective function values during the fitting process. See data type objects for more details. - data_class_type (obj) - One of the supported data type objects to fit parameters """ # Initial new dictionary that will have dictionary for extracted data new_thermo_dict = {"exp_data": {}} for key, value in thermo_dict.items(): if isinstance(value, dict) and "data_class_type" in value: new_thermo_dict["exp_data"][key] = process_exp_data(value) else: new_thermo_dict[key] = value test1 = set(["exp_data", "optimization_parameters"]).issubset(list(new_thermo_dict.keys())) test2 = set(["fit_bead", "fit_parameter_names"]).issubset(list(new_thermo_dict["optimization_parameters"].keys())) if not all([test1, test2]): raise ValueError( "An exp_data dictionary (dictionary with 'data_class_type' key) as well as" " an optimization_parameters dictionary with 'fit_bead' and " "'fit_parameter_names' must be provided." ) return new_thermo_dict
[docs] def process_exp_data(exp_data_dict): r""" Convert experimental data dictionary into form used by parameter fitting module. Note that there should be one dictionary per data set. Parameters ---------- exp_data : dict This dictionary is made up of a dictionary for each data set that the parameters are fit to. Each key is an arbitrary string used to identify the data set and used later in reporting objective function values during the fitting process. See data type objects for more details. Dictionary values below are altered before being passed on, all other key and value sets are blindly passed on. - data_class_type (str) - One of the supported data type objects to fit parameters - file (str) - File name in current working directory, or path to desired experimental data. See experimental data page for examples of acceptable format. Returns ------- exp_data : dict Reformatted dictionary of experimental data. This dictionary is made up of a dictionary for each data set that the parameters are fit to. Each key is an arbitrary string used to identify the data set and used later in reporting objective function values during the fitting process. See data type objects for more details. Dictionary values below are altered from input dictionary, all other key and value sets are blindly passed on, or extracted from data file with process_exp_data_file function. - data_class_type (str) - One of the supported data type objects to fit parameters """ exp_data = {} for key, value in exp_data_dict.items(): if key == "data_class_type": exp_data["data_class_type"] = value elif key == "file": file_dict = process_exp_data_file(value) exp_data.update(file_dict) elif key == "bead_configuration": beads, molecular_composition = process_bead_data(value) exp_data["eos_dict"] = { "beads": beads, "molecular_composition": molecular_composition, } else: exp_data[key] = value return exp_data
[docs] def process_exp_data_file(fname): r""" Import data file and convert columns into dictionary entries. The headers in the file are the dictionary keys. The top line is skipped, and column headers are the second line. Note that column headers should be thermo properties defined in :ref:`data-types` (e.g. T, P, x1, x2, y1, y2). Mole fractions x1, x2, ... should be in the same order as in the bead_configuration line of the input file. No mole fractions should be left out. Parameters ---------- fname : str File name or path to experimental data file Returns ------- file_dict : dict Dictionary of experimental data from file. """ try: data = np.transpose(np.genfromtxt(fname, delimiter=",", names=True, skip_header=1)) except Exception: raise ValueError("Cannot import '{}', Check data file formatting.".format(fname)) file_dict = {name: data[name] for name in data.dtype.names} # Sort through properties key_del = [] xi, yi, zi = [[], [], []] for key, value in file_dict.items(): if "#" in key: key.replace("#", "").replace(" ", "") # Assuming mole fractions are listed starting at x1 and continue in order if key.startswith("x"): xi.append(value) elif key.startswith("y"): yi.append(value) elif key.startswith("z"): zi.append(value) else: continue key_del.append(key) for key in key_del: file_dict.pop(key, None) if xi: file_dict["xi"] = np.transpose(np.array([np.array(x) for x in xi])) if yi: file_dict["yi"] = np.transpose(np.array([np.array(y) for y in yi])) if zi: file_dict["zi"] = np.transpose(np.array([np.array(z) for z in zi])) return file_dict