From 615730a5e460ef83932ed95e006f59082f01dd7a Mon Sep 17 00:00:00 2001 From: David Minton Date: Mon, 7 Nov 2022 19:22:22 -0500 Subject: [PATCH 01/75] Made sure that MIN_GMFRAG was cast as a float --- python/swiftest/swiftest/io.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 4678b4784..044f1eff5 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -105,6 +105,8 @@ def read_swiftest_param(param_file_name, param, verbose=True): param['ENCOUNTER_CHECK'] = param['ENCOUNTER_CHECK'].upper() if 'GMTINY' in param: param['GMTINY'] = real2float(param['GMTINY']) + if 'MIN_GMFRAG' in param: + param['MIN_GMFRAG'] = real2float(param['MIN_GMFRAG']) except IOError: print(f"{param_file_name} not found.") return param From 810f48129e0e6ede6e5c511430ba4305fde8e937 Mon Sep 17 00:00:00 2001 From: David Minton Date: Mon, 7 Nov 2022 19:22:49 -0500 Subject: [PATCH 02/75] Added new method that converts the units inside the param dictionary if the unit system changes --- python/swiftest/swiftest/simulation_class.py | 115 ++++++++++++++++--- 1 file changed, 98 insertions(+), 17 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 5ad945d69..b38c7b34d 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -28,27 +28,20 @@ def __init__(self, codename="Swiftest", param_file="param.in", readbin=False, ve self.ds = xr.Dataset() self.param = { '! VERSION': f"Swiftest parameter input", - 'T0': "0.0", - 'TSTOP': "0.0", - 'DT': "0.0", + 'T0': 0.0, + 'TSTART' : 0.0, + 'TSTOP': 0.0, + 'DT': 0.0, 'IN_FORM': "XV", 'IN_TYPE': "NETCDF_DOUBLE", 'NC_IN' : "init_cond.nc", - 'CB_IN' : "cb.in", - 'PL_IN' : "pl.in", - 'TP_IN' : "tp.in", - 'ISTEP_OUT': "1", - 'ISTEP_DUMP': "1", + 'ISTEP_OUT': 1, + 'ISTEP_DUMP': 1, 'BIN_OUT': "bin.nc", 'OUT_TYPE': 'NETCDF_DOUBLE', 'OUT_FORM': "XVEL", 'OUT_STAT': "REPLACE", - 'CHK_RMAX': "-1.0", - 'CHK_EJECT': "-1.0", - 'CHK_RMIN': "-1.0", - 'CHK_QMIN': "-1.0", 'CHK_QMIN_COORD': "HELIO", - 'CHK_QMIN_RANGE': "-1.0 -1.0", 'ENC_OUT': "", 'EXTRA_FORCE': "NO", 'DISCARD_OUT': "", @@ -66,6 +59,7 @@ def __init__(self, codename="Swiftest", param_file="param.in", readbin=False, ve } self.codename = codename self.verbose = verbose + self.set_simulation_range(rmin=constants.RSun, rmax=1000.0) self.set_unit_system() # If the parameter file is in a different location than the current working directory, we will need @@ -132,6 +126,11 @@ def set_unit_system(self,MU="Msun",DU="AU",TU="YR",MU2KG=None,DU2M=None,TU2S=Non Sets the values of MU2KG, DU2M, and TU2S in the param dictionary to the appropriate units. Also computes the gravitational constant, GU """ + # Save the previously set values of the unit conversion factors so we can update parameters as needed + MU2KG_old = self.param.pop('MU2KG',None) + DU2M_old = self.param.pop('DU2M',None) + TU2S_old = self.param.pop('TU2S',None) + if MU2KG is not None: self.param['MU2KG'] = MU2KG self.MU_name = None @@ -178,13 +177,13 @@ def set_unit_system(self,MU="Msun",DU="AU",TU="YR",MU2KG=None,DU2M=None,TU2S=Non self.param['TU2S'] = TU2S self.TU_name = None else: - if TU.upper() == "YR": + if TU.upper() == "YR" or TU.upper() == "Y" or TU.upper() == "YEAR" or TU.upper() == "YEARS": self.param['TU2S'] = constants.YR2S self.TU_name = "y" - elif TU.upper() == "DAY" or TU.upper() == "D" or TU.upper() == "JD": + elif TU.upper() == "DAY" or TU.upper() == "D" or TU.upper() == "JD" or TU.upper() == "DAYS": self.param['TU2S'] = constants.JD2S self.TU_name = "Day" - elif TU.upper() == "S": + elif TU.upper() == "S" or TU.upper() == "SECONDS" or TU.upper() == "SEC": self.param['TU2S'] = 1.0 self.TU_name = "s" else: @@ -195,6 +194,8 @@ def set_unit_system(self,MU="Msun",DU="AU",TU="YR",MU2KG=None,DU2M=None,TU2S=Non self.VU_name = f"{self.DU_name}/{self.TU_name}" self.GC = constants.GC * self.param['TU2S']**2 * self.param['MU2KG'] / self.param['DU2M']**3 + self.update_param_units(MU2KG_old, DU2M_old, TU2S_old) + if self.verbose: if self.MU_name is None or self.DU_name is None or self.TU_name is None: print(f"Custom units set.") @@ -205,7 +206,87 @@ def set_unit_system(self,MU="Msun",DU="AU",TU="YR",MU2KG=None,DU2M=None,TU2S=Non print(f"Units set to {self.MU_name}-{self.DU_name}-{self.TU_name}") return - + + def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): + """ + Updates the values of parameters that have units when the units have changed. + + Parameters + ---------- + MU2KG_old : Old value of the mass unit conversion factor + DU2M_old : Old value of the distance unit conversion factor + TU2S_old : Old value of the time unit conversion factor + + Returns + ------- + Updates the set of param dictionary values for the new unit system + + """ + + mass_keys = ['GMTINY', 'MIN_GMFRAG'] + distance_keys = ['CHK_QMIN','CHK_RMIN','CHK_RMAX', 'CHK_EJECT'] + time_keys = ['T0','TSTOP','DT'] + + if MU2KG_old is not None: + for k in mass_keys: + if k in self.param: + print(f"param['{k}']: {self.param[k]}") + self.param[k] *= self.param['MU2KG'] / MU2KG_old + + if DU2M_old is not None: + for k in distance_keys: + if k in self.param: + self.param[k] *= self.param['DU2M'] / DU2M_old + + CHK_QMIN_RANGE = self.param.pop('CHK_QMIN_RANGE', None) + if CHK_QMIN_RANGE is not None: + CHK_QMIN_RANGE = CHK_QMIN_RANGE.split(" ") + for i, v in enumerate(CHK_QMIN_RANGE): + CHK_QMIN_RANGE[i] = float(CHK_QMIN_RANGE[i]) * self.param['DU2M'] / DU2M_old + self.param['CHK_QMIN_RANGE'] = f"{CHK_QMIN_RANGE[0]} {CHK_QMIN_RANGE[1]}" + + if TU2S_old is not None: + for k in time_keys: + if k in self.param: + self.param[k] *= self.param['TU2S'] / TU2S_old + + return + + + def set_simulation_range(self,rmin=None,rmax=None): + """ + Sets the minimum and maximum distances of the simulation. + + Parameters + ---------- + rmin : float + Minimum distance of the simulation (CHK_QMIN, CHK_RMIN, CHK_QMIN_RANGE[0]) + rmax : float + Maximum distance of the simulation (CHK_RMAX, CHK_QMIN_RANGE[1]) + + Returns + ------- + Sets the appropriate param dictionary values. + + """ + CHK_QMIN_RANGE = self.param.pop('CHK_QMIN_RANGE',None) + if CHK_QMIN_RANGE is None: + CHK_QMIN_RANGE = [-1, -1] + else: + CHK_QMIN_RANGE = CHK_QMIN_RANGE.split(" ") + if rmin is not None: + self.param['CHK_QMIN'] = rmin + self.param['CHK_RMIN'] = rmin + CHK_QMIN_RANGE[0] = rmin + if rmax is not None: + self.param['CHK_RMAX'] = rmax + self.param['CHK_EJECT'] = rmax + CHK_QMIN_RANGE[1] = rmax + + self.param['CHK_QMIN_RANGE'] =f"{CHK_QMIN_RANGE[0]} {CHK_QMIN_RANGE[1]}" + + return + def add(self, plname, date=date.today().isoformat(), idval=None): """ Adds a solar system body to an existing simulation DataSet. From 585629db5e493d3505fc20aa473ec64614cbd65a Mon Sep 17 00:00:00 2001 From: David Minton Date: Tue, 8 Nov 2022 12:42:25 -0500 Subject: [PATCH 03/75] Restructured the Simulation class to accept argument-based parameters. In the new structure, the param dictionary values are set internally by what arguments are passed. This allows for arguments to be sanitized and checked for consistency and compatibility in a more robust way. --- .../Basic_Simulation/initial_conditions.py | 39 +- python/swiftest/swiftest/io.py | 3 - python/swiftest/swiftest/simulation_class.py | 529 ++++++++++++++---- 3 files changed, 433 insertions(+), 138 deletions(-) diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index 640330f1f..dceb33cc7 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -30,7 +30,7 @@ from numpy.random import default_rng # Initialize the simulation object as a variable -sim = swiftest.Simulation() +sim = swiftest.Simulation(init_cond_file_type="ASCII") # Add parameter attributes to the simulation object sim.param['T0'] = 0.0 @@ -38,40 +38,9 @@ sim.param['DT'] = 0.005 sim.param['ISTEP_OUT'] = 200 sim.param['ISTEP_DUMP'] = 200 -sim.param['OUT_FORM'] = 'XVEL' -sim.param['OUT_TYPE'] = 'NETCDF_DOUBLE' -sim.param['OUT_STAT'] = 'REPLACE' -sim.param['IN_FORM'] = 'EL' -sim.param['IN_TYPE'] = 'ASCII' -sim.param['PL_IN'] = 'pl.in' -sim.param['TP_IN'] = 'tp.in' -sim.param['CB_IN'] = 'cb.in' -sim.param['BIN_OUT'] = 'out.nc' -sim.param['CHK_QMIN'] = swiftest.RSun / swiftest.AU2M -sim.param['CHK_RMIN'] = swiftest.RSun / swiftest.AU2M -sim.param['CHK_RMAX'] = 1000.0 -sim.param['CHK_EJECT'] = 1000.0 -sim.param['CHK_QMIN_COORD'] = 'HELIO' -sim.param['CHK_QMIN_RANGE'] = f'{swiftest.RSun / swiftest.AU2M} 1000.0' -sim.param['MU2KG'] = swiftest.MSun -sim.param['TU2S'] = swiftest.YR2S -sim.param['DU2M'] = swiftest.AU2M -sim.param['EXTRA_FORCE'] = 'NO' -sim.param['BIG_DISCARD'] = 'NO' -sim.param['CHK_CLOSE'] = 'YES' -sim.param['GR'] = 'YES' -sim.param['INTERACTION_LOOPS'] = 'ADAPTIVE' -sim.param['ENCOUNTER_CHECK'] = 'ADAPTIVE' -sim.param['RHILL_PRESENT'] = 'YES' -sim.param['FRAGMENTATION'] = 'YES' -sim.param['ROTATION'] = 'YES' -sim.param['ENERGY'] = 'YES' sim.param['GMTINY'] = 1e-6 sim.param['MIN_GMFRAG'] = 1e-9 -# Set gravitational units of the system -GU = swiftest.GC / (sim.param['DU2M'] ** 3 / (sim.param['MU2KG'] * sim.param['TU2S'] ** 2)) - # Add the modern planets and the Sun using the JPL Horizons Database sim.add("Sun", idval=0, date="2022-08-08") sim.add("Mercury", idval=1, date="2022-08-08") @@ -95,9 +64,9 @@ capom_pl = default_rng().uniform(0.0, 360.0, npl) omega_pl = default_rng().uniform(0.0, 360.0, npl) capm_pl = default_rng().uniform(0.0, 360.0, npl) -GM_pl = (np.array([6e23, 8e23, 1e24, 3e24, 5e24]) / sim.param['MU2KG']) * GU -R_pl = np.full(npl, (3 * (GM_pl / GU) / (4 * np.pi * density_pl)) ** (1.0 / 3.0)) -Rh_pl = a_pl * ((GM_pl) / (3 * GU)) ** (1.0 / 3.0) +GM_pl = (np.array([6e23, 8e23, 1e24, 3e24, 5e24]) / sim.param['MU2KG']) * sim.GU +R_pl = np.full(npl, (3 * (GM_pl / sim.GU) / (4 * np.pi * density_pl)) ** (1.0 / 3.0)) +Rh_pl = a_pl * ((GM_pl) / (3 * sim.GU)) ** (1.0 / 3.0) Ip1_pl = np.array([0.4, 0.4, 0.4, 0.4, 0.4]) Ip2_pl = np.array([0.4, 0.4, 0.4, 0.4, 0.4]) Ip3_pl = np.array([0.4, 0.4, 0.4, 0.4, 0.4]) diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 044f1eff5..af74e34a4 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -94,9 +94,6 @@ def read_swiftest_param(param_file_name, param, verbose=True): param['CHK_CLOSE'] = param['CHK_CLOSE'].upper() param['RHILL_PRESENT'] = param['RHILL_PRESENT'].upper() param['FRAGMENTATION'] = param['FRAGMENTATION'].upper() - if param['FRAGMENTATION'] == 'YES' and param['PARTICLE_OUT'] == '': - if param['OUT_TYPE'] == 'REAL8' or param['OUT_TYPE'] == 'REAL4': - param['PARTICLE_OUT'] = 'particle.dat' param['ROTATION'] = param['ROTATION'].upper() param['TIDES'] = param['TIDES'].upper() param['ENERGY'] = param['ENERGY'].upper() diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index b38c7b34d..38fa44d65 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -9,6 +9,7 @@ You should have received a copy of the GNU General Public License along with Swiftest. If not, see: https://www.gnu.org/licenses. """ +from __future__ import annotations from swiftest import io from swiftest import init_cond @@ -19,12 +20,166 @@ import numpy as np import os import shutil +from typing import ( + Literal, + Dict, +) + class Simulation: """ This is a class that defines the basic Swift/Swifter/Swiftest simulation object """ - def __init__(self, codename="Swiftest", param_file="param.in", readbin=False, verbose=True): + def __init__(self, + codename: Literal["Swiftest", "Swifter", "Swift"] = "Swiftest", + param_file: os.PathLike | str ="param.in", + read_param: bool = False, + init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"] = "NETCDF_DOUBLE", + init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None = None, + init_cond_format: Literal["EL", "XV"] = "EL", + output_file_type: Literal["NETCDF_DOUBLE","NETCDF_FLOAT","REAL4","REAL8","XDR4","XDR8"] = "NETCDF_DOUBLE", + output_file_name: os.PathLike | str | None = None, + output_format: Literal["XV","XVEL"] = "XVEL", + read_old_output_file: bool = False, + MU: str = "MSUN", + DU: str = "AU", + TU: str = "Y", + MU2KG: float | None = None, + DU2M: float | None = None, + TU2S: float | None = None, + rmin: float = constants.RSun / constants.AU2M, + rmax: float = 10000.0, + close_encounter_check: bool =True, + general_relativity: bool =True, + fragmentation: bool =True, + rotation: bool = True, + compute_conservation_values: bool = False, + interaction_loops: Literal["TRIANGULAR","FLAT","ADAPTIVE"] = "TRIANGULAR", + encounter_check_loops: Literal["TRIANGULAR","SORTSWEEP","ADAPTIVE"] = "TRIANGULAR", + verbose: bool = True + ): + """ + + Parameters + ---------- + codename : {"Swiftest", "Swifter", "Swift"}, default "Swiftest" + Name of the n-body integrator that will be used. + param_file : str, path-like, or file-lke, default "param.in" + Name of the parameter input file that will be passed to the integrator. + read_param : bool, default False + If true, read in a pre-existing parameter input file given by the argument `param_file`. Otherwise, create + a new parameter file using the arguments passed to Simulation. + > *Note:* If set to true, the parameters defined in the input file will override any passed into the + > arguments of Simulation. + init_cond_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"}, default "NETCDF_DOUBLE" + The file type containing initial conditions for the simulation: + * NETCDF_DOUBLE: A single initial conditions input file in NetCDF file format of type NETCDF_DOUBLE + * NETCDF_FLOAT: A single initial conditions input file in NetCDF file format of type NETCDF_FLOAT + * ASCII : Three initial conditions files in ASCII format. The individual files define the central body, + massive body, and test particle initial conditions. + init_cond_file_name : str, path-like, or dict, optional + Name of the input initial condition file or files. Whether to pass a single file name or a dictionary + depends on the argument passed to `init_cond_file_type`: If `init_cond_file_type={"NETCDF_DOUBLE","NETCDF_FLOAT"}`, + then this will be a single file name. If `init_cond_file_type="ASCII"` then this must be a dictionary where: + ```init_cond_file_name = { + "CB" : *path to central body initial conditions file* (Swiftest only), + "PL" : *path to massive body initial conditions file*, + "TP" : *path to test particle initial conditions file* + }``` + If no file name is provided then the following default file names will be used. + * NETCDF_DOUBLE, NETCDF_FLOAT: `init_cond_file_name = "init_cond.nc"` + * ASCII: `init_cond_file_name = {"CB" : "cb.in", "PL" : "pl.in", "TP" : "tp.in"}` + init_cond_format : {"EL", "XV"}, default "EL" + Indicates whether the input initial conditions are given as orbital elements or cartesian position and + velocity vectors. + > *Note:* If `codename` is "Swift" or "Swifter", EL initial conditions are converted to XV. + output_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT","REAL4","REAL8","XDR4","XDR8"}, default "NETCDF_DOUBLE" + The file type for the outputs of the simulation. Compatible file types depend on the `codename` argument. + * Swiftest: Only "NETCDF_DOUBLE" or "NETCDF_FLOAT" supported. + * Swifter: Only "REAL4","REAL8","XDR4" or "XDR8" supported. + * Swift: Only "REAL4" supported. + output_file_name : str or path-like, optional + Name of output file to generate. If not supplied, then one of the default file names are used, depending on + the value passed to `output_file_type`. If one of the NetCDF types are used, the default is "bin.nc". + Otherwise, the default is "bin.dat". + output_format : {"XV","XVEL"}, default "XVEL" + Specifies the format for the data saved to the output file. If "XV" then cartesian position and velocity + vectors for all bodies are stored. If "XVEL" then the orbital elements are also stored. + read_old_output_file : bool, default False + If true, read in a pre-existing binary input file given by the argument `output_file_name`. + MU : str, default "MSUN" + The mass unit system to use. Case-insensitive valid options are: + * "Msun" : Solar mass + * "Mearth" : Earth mass + * "kg" : kilograms + * "g" : grams + DU : str, optional + The distance unit system to use. Case-insensitive valid options are: + * "AU" : Astronomical Unit + * "Rearth" : Earth radius + * "m" : meter + * "cm" : centimeter + TU : str, optional + The time unit system to use. Case-insensitive valid options are: + * "YR" : Year + * "DAY" : Julian day + * "d" : Julian day + * "JD" : Julian day + * "s" : second + MU2KG: float, optional + The conversion factor to multiply by the mass unit that would convert it to kilogram. + Setting this overrides MU + DU2M : float, optional + The conversion factor to multiply by the distance unit that would convert it to meter. + Setting this overrides DU + TU2S : float, optional + The conversion factor to multiply by the time unit that would convert it to seconds. + Setting this overrides TU + rmin : float, default value is the radius of the Sun in the unit system defined by the unit input arguments. + Minimum distance of the simulation (CHK_QMIN, CHK_RMIN, CHK_QMIN_RANGE[0]) + rmax : float, default value is 10000 AU in the unit system defined by the unit input arguments. + Maximum distance of the simulation (CHK_RMAX, CHK_QMIN_RANGE[1]) + close_encounter_check : bool, default True + Check for close encounters between bodies. If set to True, then the radii of massive bodies must be included + in initial conditions. + general_relativity : bool, default True + Include the post-Newtonian correction in acceleration calculations. + fragmentation : bool, default True + If set to True, this turns on the Fraggle fragment generation code and `rotation` must also be True. + This argument only applies to Swiftest-SyMBA simulations. It will be ignored otherwise. + rotation : bool, default True + If set to True, this turns on rotation tracking and radius, rotation vector, and moments of inertia values + must be included in the initial conditions. + This argument only applies to Swiftest-SyMBA simulations. It will be ignored otherwise. + compute_conservation_values : bool, default False + Turns on the computation of energy, angular momentum, and mass conservation and reports the values + every output step of a running simulation. + interaction_loops : {"TRIANGULAR","FLAT","ADAPTIVE"}, default "TRIANGULAR" + *Swiftest Experimental feature* + Specifies which algorithm to use for the computation of body-body gravitational forces. + * "TRIANGULAR" : Upper-triangular double-loops . + * "FLAT" : Body-body interation pairs are flattened into a 1-D array. + * "ADAPTIVE" : Periodically times the TRIANGULAR and FLAT methods and determines which one to use based on + the wall time to complete the loop. *Note:* Using ADAPTIVE means that bit-identical repeatability cannot + be assured, as the choice of algorithm depends on possible external factors that can influence the wall + time calculation. The exact floating-point results of the interaction will be different between the two + algorithm types. + encounter_check_loops : {"TRIANGULAR","SORTSWEEP","ADAPTIVE"}, default "TRIANGULAR" + *Swiftest Experimental feature* + Specifies which algorithm to use for checking whether bodies are in a close encounter state or not. + * "TRIANGULAR" : Upper-triangular double-loops. + * "SORTSWEEP" : A Sort-Sweep algorithm is used to reduce the population of potential close encounter bodies. + This algorithm is still in development, and does not necessarily speed up the encounter checking. + Use with caution. + * "ADAPTIVE" : Periodically times the TRIANGULAR and SORTSWEEP methods and determines which one to use based + on the wall time to complete the loop. *Note:* Using ADAPTIVE means that bit-identical repeatability cannot + be assured, as the choice of algorithm depends on possible external factors that can influence the wall + time calculation. The exact floating-point results of the interaction will be different between the two + algorithm types. + verbose : bool, default True + If set to True, then more information is printed by Simulation methods as they are executed. Setting to + False suppresses most messages other than errors. + """ self.ds = xr.Dataset() self.param = { '! VERSION': f"Swiftest parameter input", @@ -32,42 +187,48 @@ def __init__(self, codename="Swiftest", param_file="param.in", readbin=False, ve 'TSTART' : 0.0, 'TSTOP': 0.0, 'DT': 0.0, - 'IN_FORM': "XV", - 'IN_TYPE': "NETCDF_DOUBLE", - 'NC_IN' : "init_cond.nc", 'ISTEP_OUT': 1, 'ISTEP_DUMP': 1, - 'BIN_OUT': "bin.nc", - 'OUT_TYPE': 'NETCDF_DOUBLE', - 'OUT_FORM': "XVEL", - 'OUT_STAT': "REPLACE", 'CHK_QMIN_COORD': "HELIO", - 'ENC_OUT': "", 'EXTRA_FORCE': "NO", - 'DISCARD_OUT': "", - 'PARTICLE_OUT' : "", 'BIG_DISCARD': "NO", 'CHK_CLOSE': "YES", 'RHILL_PRESENT': "YES", - 'FRAGMENTATION': "NO", - 'ROTATION': "NO", + 'FRAGMENTATION': "YES", + 'ROTATION': "YES", 'TIDES': "NO", - 'ENERGY': "NO", + 'ENERGY': "YES", 'GR': "YES", - 'INTERACTION_LOOPS': "TRIANGULAR", - 'ENCOUNTER_CHECK': "TRIANGULAR" + 'INTERACTION_LOOPS': interaction_loops, + 'ENCOUNTER_CHECK': encounter_check_loops } self.codename = codename self.verbose = verbose - self.set_simulation_range(rmin=constants.RSun, rmax=1000.0) - self.set_unit_system() + + self.set_distance_range(rmin=rmin, rmax=rmax) + + self.set_unit_system(MU=MU, DU=DU, TU=TU, + MU2KG=MU2KG, DU2M=DU2M, TU2S=TU2S, + recompute_values=True) + + self.set_init_cond_files(init_cond_file_type=init_cond_file_type, + init_cond_file_name=init_cond_file_name, + init_cond_format=init_cond_format) + + self.set_output_files(output_file_type=output_file_type, + output_file_name=output_file_name, + output_format=output_format ) # If the parameter file is in a different location than the current working directory, we will need # to use it to properly open bin files self.sim_dir = os.path.dirname(os.path.realpath(param_file)) - if os.path.exists(param_file): - self.read_param(param_file, codename=codename, verbose=self.verbose) - if readbin: + if read_param: + if os.path.exists(param_file): + self.read_param(param_file, codename=codename, verbose=self.verbose) + else: + print(f"{param_file} not found.") + + if read_old_output_file: binpath = os.path.join(self.sim_dir,self.param['BIN_OUT']) if os.path.exists(binpath): self.bin2xr() @@ -76,7 +237,163 @@ def __init__(self, codename="Swiftest", param_file="param.in", readbin=False, ve return - def set_unit_system(self,MU="Msun",DU="AU",TU="YR",MU2KG=None,DU2M=None,TU2S=None): + def set_init_cond_files(self, + init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"], + init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None, + init_cond_format: Literal["EL", "XV"]): + """ + Sets the initial condition file parameters in the parameters dictionary. + + Parameters + ---------- + init_cond_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"} + The file type containing initial conditions for the simulation: + * NETCDF_DOUBLE: A single initial conditions input file in NetCDF file format of type NETCDF_DOUBLE + * NETCDF_FLOAT: A single initial conditions input file in NetCDF file format of type NETCDF_FLOAT + * ASCII : Three initial conditions files in ASCII format. The individual files define the central body, + massive body, and test particle initial conditions. + init_cond_file_name : str, path-like, or dict, optional + Name of the input initial condition file or files. Whether to pass a single file name or a dictionary + depends on the argument passed to `init_cond_file_type`: If `init_cond_file_type={"NETCDF_DOUBLE","NETCDF_FLOAT"}`, + then this will be a single file name. If `init_cond_file_type="ASCII"` then this must be a dictionary where: + ```init_cond_file_name = { + "CB" : *path to central body initial conditions file* (Swiftest only), + "PL" : *path to massive body initial conditions file*, + "TP" : *path to test particle initial conditions file* + }``` + If no file name is provided then the following default file names will be used. + * NETCDF_DOUBLE, NETCDF_FLOAT: `init_cond_file_name = "init_cond.nc"` + * ASCII: `init_cond_file_name = {"CB" : "cb.in", "PL" : "pl.in", "TP" : "tp.in"}` + init_cond_format : {"EL", "XV"} + Indicates whether the input initial conditions are given as orbital elements or cartesian position and + velocity vectors. + > *Note:* If `codename` is "Swift" or "Swifter", EL initial conditions are converted to XV. + + Returns + ------- + Sets the appropriate initial conditions file parameters inside the self.param dictionary. + + """ + + def ascii_file_input_error_msg(codename): + print(f"Error in set_init_cond_files: init_cond_file_name must be a dictionary of the form: ") + print('{') + if codename == "Swiftest": + print('"CB" : *path to central body initial conditions file*,') + print('"PL" : *path to massive body initial conditions file*,') + print('"TP" : *path to test particle initial conditions file*') + print('}') + return + + if self.codename == "Swiftest": + init_cond_keys = ["CB", "PL", "TP"] + else: + init_cond_keys = ["PL", "TP"] + if init_cond_file_type != "ASCII": + print(f"{init_cond_file_type} is not supported by {self.codename}. Using ASCII instead") + init_cond_file_type="ASCII" + if init_cond_format != "XV": + print(f"{init_cond_format} is not supported by {self.codename}. Using XV instead") + init_cond_format = "XV" + + self.param["IN_TYPE"] = init_cond_file_type + self.param["IN_FORM"] = init_cond_format + + if init_cond_file_type == "ASCII": + if init_cond_file_name is None: + # No file names passed, so we will just use the defaults. + for key in init_cond_keys: + self.param[f"{key}_IN"] = f"{key.lower()}.in" + elif type(init_cond_file_name) is not dict: + # Oops, accidentally passed a single string or path-like instead of the expected dictionary for ASCII + # input type. + ascii_file_input_error_msg(self.codename) + elif not all(key in init_cond_file_name for key in init_cond_keys): + # This is the case where the dictionary doesn't have all the keys we expect. Print an error message. + ascii_file_input_error_msg(self.codename) + else: + # A valid initial conditions file dictionary was passed. + for key in init_cond_keys: + self.param[f"{key}_IN"] = init_cond_file_name[key] + else: + if init_cond_file_name is None: + # No file names passed, so we will just use the default. + self.param["NC_IN"] = "init_cond.nc" + elif type(init_cond_file_name) is dict: + # Oops, accidentally passed a dictionary instead of the expected single string or path-like for NetCDF + # input type. + print(f"Only a single input file is used for NetCDF files") + else: + self.param["NC_IN"] = init_cond_file_name + + return + + def set_output_files(self, + output_file_type: Literal[ "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"], + output_file_name: os.PathLike | str | None, + output_format: Literal["XV", "XVEL"] + ): + """ + Sets the output file parameters in the parameters dictionary. + + Parameters + ---------- + output_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT","REAL4","REAL8","XDR4","XDR8"}, default "NETCDF_DOUBLE" + The file type for the outputs of the simulation. Compatible file types depend on the `codename` argument. + * Swiftest: Only "NETCDF_DOUBLE" or "NETCDF_FLOAT" supported. + * Swifter: Only "REAL4","REAL8","XDR4" or "XDR8" supported. + * Swift: Only "REAL4" supported. + output_file_name : str or path-like, optional + Name of output file to generate. If not supplied, then one of the default file names are used, depending on + the value passed to `output_file_type`. If one of the NetCDF types are used, the default is "bin.nc". + Otherwise, the default is "bin.dat". + output_format : {"XV","XVEL"}, default "XVEL" + Specifies the format for the data saved to the output file. If "XV" then cartesian position and velocity + vectors for all bodies are stored. If "XVEL" then the orbital elements are also stored. + + Returns + ------- + Sets the appropriate initial conditions file parameters inside the self.param dictionary. + + """ + + if self.codename == "Swiftest": + if output_file_type not in ["NETCDF_DOUBLE", "NETCDF_FLOAT"]: + print(f"{output_file_type} is not compatible with Swiftest. Setting to NETCDF_DOUBLE") + output_file_type = "NETCDF_DOUBLE" + elif self.codename == "Swifter": + if output_file_type not in ["REAL4","REAL8","XDR4","XDR8"]: + print(f"{output_file_type} is not compatible with Swifter. Setting to REAL8") + output_file_type = "REAL8" + elif self.codename == "Swift": + if output_file_type not in ["REAL4"]: + print(f"{output_file_type} is not compatible with Swift. Setting to REAL4") + output_file_type = "REAL4" + + self.param['OUT_TYPE'] = output_file_type + if output_file_name is None: + if output_file_type in ["NETCDF_DOUBLE", "NETCDF_FLOAT"]: + self.param['BIN_OUT'] = "bin.nc" + else: + self.param['BIN_OUT'] = "bin.dat" + else: + self.param['BIN_OUT'] = output_file_name + + if output_format != "XV" and self.codename != "Swiftest": + print(f"{output_format} is not compatible with {self.codename}. Setting to XV") + output_format = "XV" + self.param['OUT_FORM'] = output_format + + return + + def set_unit_system(self, + MU: str | None = None, + DU: str | None = None, + TU: str | None = None, + MU2KG: float | None = None, + DU2M: float | None = None, + TU2S: float | None = None, + recompute_values: bool = False): """ Setter for setting the unit conversion between one of the standard sets. @@ -90,111 +407,121 @@ def set_unit_system(self,MU="Msun",DU="AU",TU="YR",MU2KG=None,DU2M=None,TU2S=Non Parameters ---------- - MU : str, default "Msun" + MU : str, optional The mass unit system to use. Case-insensitive valid options are: "Msun" : Solar mass "Mearth" : Earth mass "kg" : kilograms "g" : grams - - DU : str, default "AU" + DU : str, optional The distance unit system to use. Case-insensitive valid options are: "AU" : Astronomical Unit "Rearth" : Earth radius "m" : meter "cm" : centimeter - - TU : str, default "YR" + TU : str, optional The time unit system to use. Case-insensitive valid options are: "YR" : Year "DAY" : Julian day "d" : Julian day "JD" : Julian day "s" : second - - MU2KG : float, default `None` - The conversion factor to multiply by the mass unit that would convert it to kilogram. Setting this overrides MU - - DU2M : float, default `None` - The conversion factor to multiply by the distance unit that would convert it to meter. Setting this overrides DU - - TU2S : float, default `None` - The conversion factor to multiply by the time unit that would convert it to seconds. Setting this overrides TU + MU2KG : float, optional + The conversion factor to multiply by the mass unit that would convert it to kilogram. + Setting this overrides MU + DU2M : float, optional + The conversion factor to multiply by the distance unit that would convert it to meter. + Setting this overrides DU + TU2S : float, optional + The conversion factor to multiply by the time unit that would convert it to seconds. + Setting this overrides TU + recompute_values : bool, default False + Recompute all values into the new unit system. + >*Note:* This is a destructive operation, however if not executed then the values contained in the parameter + > file and input/output data files computed previously may not be consistent with the new unit conversion + > factors. Returns ---------- - Sets the values of MU2KG, DU2M, and TU2S in the param dictionary to the appropriate units. Also computes the gravitational constant, GU + Sets the values of MU2KG, DU2M, and TU2S in the param dictionary to the appropriate units. Also computes the + gravitational constant, self.GU and recomput """ - # Save the previously set values of the unit conversion factors so we can update parameters as needed - MU2KG_old = self.param.pop('MU2KG',None) - DU2M_old = self.param.pop('DU2M',None) - TU2S_old = self.param.pop('TU2S',None) + MU2KG_old = None + DU2M_old = None + TU2S_old = None - if MU2KG is not None: - self.param['MU2KG'] = MU2KG - self.MU_name = None - else: - if MU.upper() == "MSUN": - self.param['MU2KG'] = constants.MSun - self.MU_name = "MSun" - elif MU.upper() == "MEARTH": - self.param['MU2KG'] = constants.MEarth - self.MU_name = "MEarth" - elif MU.upper() == "KG": - self.param['MU2KG'] = 1.0 - self.MU_name = "kg" - elif MU.upper() == "G": - self.param['MU2KG'] = 1000.0 - self.MU_name = "g" + if MU2KG is not None or MU is not None: + MU2KG_old = self.param.pop('MU2KG',None) + if MU2KG is not None: + self.param['MU2KG'] = MU2KG + self.MU_name = None else: - print(f"{MU} not a recognized unit system. Using MSun as a default.") - self.param['MU2KG'] = constants.MSun - self.MU_name = "MSun" - - if DU2M is not None: - self.param['DU2M'] = DU2M - self.DU_name = None - else: - if DU.upper() == "AU": - self.param['DU2M'] = constants.AU2M - self.DU_name = "AU" - elif DU.upper() == "REARTH": - self.param['DU2M'] = constants.REarth - self.DU_name = "REarth" - elif DU.upper() == "M": - self.param['DU2M'] = 1.0 - self.DU_name = "m" - elif DU.upper() == "CM": - self.param['DU2M'] = 100.0 - self.DU_name = "cm" + if MU.upper() == "MSUN": + self.param['MU2KG'] = constants.MSun + self.MU_name = "MSun" + elif MU.upper() == "MEARTH": + self.param['MU2KG'] = constants.MEarth + self.MU_name = "MEarth" + elif MU.upper() == "KG": + self.param['MU2KG'] = 1.0 + self.MU_name = "kg" + elif MU.upper() == "G": + self.param['MU2KG'] = 1000.0 + self.MU_name = "g" + else: + print(f"{MU} not a recognized unit system. Using MSun as a default.") + self.param['MU2KG'] = constants.MSun + self.MU_name = "MSun" + + if DU2M is not None or DU is not None: + DU2M_old = self.param.pop('DU2M',None) + if DU2M is not None: + self.param['DU2M'] = DU2M + self.DU_name = None else: - print(f"{DU} not a recognized unit system. Using AU as a default.") - self.param['DU2M'] = constants.AU2M - self.DU_name = "AU" - - if TU2S is not None: - self.param['TU2S'] = TU2S - self.TU_name = None - else: - if TU.upper() == "YR" or TU.upper() == "Y" or TU.upper() == "YEAR" or TU.upper() == "YEARS": - self.param['TU2S'] = constants.YR2S - self.TU_name = "y" - elif TU.upper() == "DAY" or TU.upper() == "D" or TU.upper() == "JD" or TU.upper() == "DAYS": - self.param['TU2S'] = constants.JD2S - self.TU_name = "Day" - elif TU.upper() == "S" or TU.upper() == "SECONDS" or TU.upper() == "SEC": - self.param['TU2S'] = 1.0 - self.TU_name = "s" + if DU.upper() == "AU": + self.param['DU2M'] = constants.AU2M + self.DU_name = "AU" + elif DU.upper() == "REARTH": + self.param['DU2M'] = constants.REarth + self.DU_name = "REarth" + elif DU.upper() == "M": + self.param['DU2M'] = 1.0 + self.DU_name = "m" + elif DU.upper() == "CM": + self.param['DU2M'] = 100.0 + self.DU_name = "cm" + else: + print(f"{DU} not a recognized unit system. Using AU as a default.") + self.param['DU2M'] = constants.AU2M + self.DU_name = "AU" + + if TU2S is not None or TU is not None: + TU2S_old = self.param.pop('TU2S',None) + if TU2S is not None: + self.param['TU2S'] = TU2S + self.TU_name = None else: - print(f"{TU} not a recognized unit system. Using YR as a default.") - self.param['TU2S'] = constants.YR2S - self.TU_name = "y" + if TU.upper() == "YR" or TU.upper() == "Y" or TU.upper() == "YEAR" or TU.upper() == "YEARS": + self.param['TU2S'] = constants.YR2S + self.TU_name = "y" + elif TU.upper() == "DAY" or TU.upper() == "D" or TU.upper() == "JD" or TU.upper() == "DAYS": + self.param['TU2S'] = constants.JD2S + self.TU_name = "Day" + elif TU.upper() == "S" or TU.upper() == "SECONDS" or TU.upper() == "SEC": + self.param['TU2S'] = 1.0 + self.TU_name = "s" + else: + print(f"{TU} not a recognized unit system. Using YR as a default.") + self.param['TU2S'] = constants.YR2S + self.TU_name = "y" self.VU_name = f"{self.DU_name}/{self.TU_name}" - self.GC = constants.GC * self.param['TU2S']**2 * self.param['MU2KG'] / self.param['DU2M']**3 + self.GU = constants.GC * self.param['TU2S']**2 * self.param['MU2KG'] / self.param['DU2M']**3 - self.update_param_units(MU2KG_old, DU2M_old, TU2S_old) + if recompute_values: + self.update_param_units(MU2KG_old, DU2M_old, TU2S_old) if self.verbose: if self.MU_name is None or self.DU_name is None or self.TU_name is None: @@ -253,7 +580,7 @@ def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): return - def set_simulation_range(self,rmin=None,rmax=None): + def set_distance_range(self,rmin=None,rmax=None): """ Sets the minimum and maximum distances of the simulation. @@ -287,6 +614,8 @@ def set_simulation_range(self,rmin=None,rmax=None): return + + def add(self, plname, date=date.today().isoformat(), idval=None): """ Adds a solar system body to an existing simulation DataSet. From ccb405feb287cb0fe1dbd09668003d4c7486c7bc Mon Sep 17 00:00:00 2001 From: David Minton Date: Tue, 8 Nov 2022 14:58:59 -0500 Subject: [PATCH 04/75] Updated parameter "YES" and "NO" values to boolean on the Python side. Wrote a feature setter and getter that sets the boolean values of the simulation parameters --- python/swiftest/swiftest/io.py | 179 ++++++++++++------ python/swiftest/swiftest/simulation_class.py | 180 +++++++++++++++++-- 2 files changed, 289 insertions(+), 70 deletions(-) diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index af74e34a4..b12d32cd3 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -17,8 +17,72 @@ import tempfile import re -newfeaturelist = ("FRAGMENTATION", "ROTATION", "TIDES", "ENERGY", "GR", "YARKOVSKY", "YORP", "IN_FORM") +newfeaturelist = ("FRAGMENTATION", "ROTATION", "TIDES", "ENERGY", "GR", "YARKOVSKY", "YORP", "IN_FORM", "SEED", "INTERACTION_LOOPS", "ENCOUNTER_CHECK") string_varnames = ["name", "particle_type", "status", "origin_type"] +bool_param = ["CHK_CLOSE", "EXTRA_FORCE", "RHILL_PRESENT", "BIG_DISCARD", "FRAGMENTATION", "ROTATION", "TIDES", "ENERGY", "GR", "YARKOVSKY", "YORP"] + +def bool2yesno(boolval): + """ + Converts a boolean into a string of either "YES" or "NO". + + Parameters + ---------- + boolval : bool + Input value + + Returns + ------- + {"YES","NO"} + + """ + if boolval: + return "YES" + else: + return "NO" + +def bool2tf(boolval): + """ + Converts a boolean into a string of either "T" or "F". + + Parameters + ---------- + boolval : bool + Input value + + Returns + ------- + {"T","F"} + + """ + if boolval: + return "T" + else: + return "F" + +def str2bool(input_str): + """ + Converts a string into an equivalent boolean. + + Parameters + ---------- + input_str : {"YES", "Y", "T", "TRUE", ".TRUE.", "NO", "N", "F", "FALSE", ".FALSE."} + Input string. Input is case-insensitive. + + Returns + ------- + {True, False} + + """ + valid_true = ["YES", "Y", "T", "TRUE", ".TRUE."] + valid_false = ["NO", "N", "F", "FALSE", ".FALSE."] + if input_str.upper() in valid_true: + return True + elif input_str.lower() in valid_false: + return False + else: + raise ValueError(f"{input_str} cannot is not recognized as boolean") + + def real2float(realstr): """ @@ -27,7 +91,7 @@ def real2float(realstr): Parameters ---------- - realstr : string + realstr : str Fortran-generated ASCII string of a real value. Returns @@ -89,21 +153,15 @@ def read_swiftest_param(param_file_name, param, verbose=True): param['DU2M'] = real2float(param['DU2M']) param['MU2KG'] = real2float(param['MU2KG']) param['TU2S'] = real2float(param['TU2S']) - param['EXTRA_FORCE'] = param['EXTRA_FORCE'].upper() - param['BIG_DISCARD'] = param['BIG_DISCARD'].upper() - param['CHK_CLOSE'] = param['CHK_CLOSE'].upper() - param['RHILL_PRESENT'] = param['RHILL_PRESENT'].upper() - param['FRAGMENTATION'] = param['FRAGMENTATION'].upper() - param['ROTATION'] = param['ROTATION'].upper() - param['TIDES'] = param['TIDES'].upper() - param['ENERGY'] = param['ENERGY'].upper() - param['GR'] = param['GR'].upper() param['INTERACTION_LOOPS'] = param['INTERACTION_LOOPS'].upper() param['ENCOUNTER_CHECK'] = param['ENCOUNTER_CHECK'].upper() if 'GMTINY' in param: param['GMTINY'] = real2float(param['GMTINY']) if 'MIN_GMFRAG' in param: param['MIN_GMFRAG'] = real2float(param['MIN_GMFRAG']) + for b in bool_param: + if b in param: + param[b] = str2bool(param[b]) except IOError: print(f"{param_file_name} not found.") return param @@ -139,7 +197,7 @@ def read_swifter_param(param_file_name, verbose=True): 'OUT_STAT': "NEW", 'J2': "0.0", 'J4': "0.0", - 'CHK_CLOSE': 'NO', + 'CHK_CLOSE': False, 'CHK_RMIN': "-1.0", 'CHK_RMAX': "-1.0", 'CHK_EJECT': "-1.0", @@ -147,9 +205,9 @@ def read_swifter_param(param_file_name, verbose=True): 'CHK_QMIN_COORD': "HELIO", 'CHK_QMIN_RANGE': "", 'ENC_OUT': "", - 'EXTRA_FORCE': 'NO', - 'BIG_DISCARD': 'NO', - 'RHILL_PRESENT': 'NO', + 'EXTRA_FORCE': False, + 'BIG_DISCARD': False, + 'RHILL_PRESENT': False, 'C': "-1.0", } @@ -179,10 +237,9 @@ def read_swifter_param(param_file_name, verbose=True): param['CHK_RMAX'] = real2float(param['CHK_RMAX']) param['CHK_EJECT'] = real2float(param['CHK_EJECT']) param['CHK_QMIN'] = real2float(param['CHK_QMIN']) - param['EXTRA_FORCE'] = param['EXTRA_FORCE'].upper() - param['BIG_DISCARD'] = param['BIG_DISCARD'].upper() - param['CHK_CLOSE'] = param['CHK_CLOSE'].upper() - param['RHILL_PRESENT'] = param['RHILL_PRESENT'].upper() + for b in bool_param: + if b in param: + param[b] = str2bool(param[b]) if param['C'] != '-1.0': param['C'] = real2float(param['C']) else: @@ -349,10 +406,18 @@ def write_labeled_param(param, param_file_name): # Print the list of key/value pairs in the preferred order for key in keylist: val = ptmp.pop(key, None) - if val is not None: print(f"{key:<16} {val}", file=outfile) + if val is not None: + if type(val) is bool: + print(f"{key:<16} {bool2yesno(val)}", file=outfile) + else: + print(f"{key:<16} {val}", file=outfile) # Print the remaining key/value pairs in whatever order for key, val in ptmp.items(): - if val != "": print(f"{key:<16} {val}", file=outfile) + if val != "": + if type(val) is bool: + print(f"{key:<16} {bool2yesno(val)}", file=outfile) + else: + print(f"{key:<16} {val}", file=outfile) outfile.close() return @@ -483,12 +548,12 @@ def make_swiftest_labels(param): tlab.append('capm') plab = tlab.copy() plab.append('Gmass') - if param['CHK_CLOSE'] == 'YES': + if param['CHK_CLOSE']: plab.append('radius') - if param['RHILL_PRESENT'] == 'YES': + if param['RHILL_PRESENT']: plab.append('rhill') clab = ['Gmass', 'radius', 'j2rp2', 'j4rp4'] - if param['ROTATION'] == 'YES': + if param['ROTATION']: clab.append('Ip1') clab.append('Ip2') clab.append('Ip3') @@ -501,7 +566,7 @@ def make_swiftest_labels(param): plab.append('rotx') plab.append('roty') plab.append('rotz') - if param['TIDES'] == 'YES': + if param['TIDES']: clab.append('k2') clab.append('Q') plab.append('k2') @@ -588,14 +653,14 @@ def swiftest_stream(f, param): Rcb = f.read_reals(np.float64) J2cb = f.read_reals(np.float64) J4cb = f.read_reals(np.float64) - if param['ROTATION'] == 'YES': + if param['ROTATION']: Ipcbx = f.read_reals(np.float64) Ipcby = f.read_reals(np.float64) Ipcbz = f.read_reals(np.float64) rotcbx = f.read_reals(np.float64) rotcby = f.read_reals(np.float64) rotcbz = f.read_reals(np.float64) - if param['TIDES'] == 'YES': + if param['TIDES']: k2cb = f.read_reals(np.float64) Qcb = f.read_reals(np.float64) if npl[0] > 0: @@ -619,17 +684,17 @@ def swiftest_stream(f, param): p11 = f.read_reals(np.float64) p12 = f.read_reals(np.float64) GMpl = f.read_reals(np.float64) - if param['RHILL_PRESENT'] == 'YES': + if param['RHILL_PRESENT']: rhill = f.read_reals(np.float64) Rpl = f.read_reals(np.float64) - if param['ROTATION'] == 'YES': + if param['ROTATION']: Ipplx = f.read_reals(np.float64) Ipply = f.read_reals(np.float64) Ipplz = f.read_reals(np.float64) rotplx = f.read_reals(np.float64) rotply = f.read_reals(np.float64) rotplz = f.read_reals(np.float64) - if param['TIDES'] == 'YES': + if param['TIDES']: k2pl = f.read_reals(np.float64) Qpl = f.read_reals(np.float64) if ntp[0] > 0: @@ -679,14 +744,14 @@ def swiftest_stream(f, param): tpid = np.empty(0) tpnames = np.empty(0) cvec = np.array([Mcb, Rcb, J2cb, J4cb]) - if param['RHILL_PRESENT'] == 'YES': + if param['RHILL_PRESENT']: if npl > 0: pvec = np.vstack([pvec, rhill]) - if param['ROTATION'] == 'YES': + if param['ROTATION']: cvec = np.vstack([cvec, Ipcbx, Ipcby, Ipcbz, rotcbx, rotcby, rotcbz]) if npl > 0: pvec = np.vstack([pvec, Ipplx, Ipply, Ipplz, rotplx, rotply, rotplz]) - if param['TIDES'] == 'YES': + if param['TIDES']: cvec = np.vstack([cvec, k2cb, Qcb]) if npl > 0: pvec = np.vstack([pvec, k2pl, Qpl]) @@ -1018,14 +1083,14 @@ def swiftest_xr2infile(ds, param, in_type="NETCDF_DOUBLE", infile_name=None,fram tp = frame.where(np.isnan(frame['Gmass']), drop=True).drop_vars(['Gmass', 'radius', 'j2rp2', 'j4rp4'],errors="ignore") GMSun = np.double(cb['Gmass']) - if param['CHK_CLOSE'] == 'YES': + if param['CHK_CLOSE']: RSun = np.double(cb['radius']) else: RSun = param['CHK_RMIN'] J2 = np.double(cb['j2rp2']) J4 = np.double(cb['j4rp4']) cbname = cb['name'].values[0] - if param['ROTATION'] == 'YES': + if param['ROTATION']: Ip1cb = np.double(cb['Ip1']) Ip2cb = np.double(cb['Ip2']) Ip3cb = np.double(cb['Ip3']) @@ -1042,7 +1107,7 @@ def swiftest_xr2infile(ds, param, in_type="NETCDF_DOUBLE", infile_name=None,fram print(RSun, file=cbfile) print(J2, file=cbfile) print(J4, file=cbfile) - if param['ROTATION'] == 'YES': + if param['ROTATION']: print(Ip1cb, Ip2cb, Ip3cb, file=cbfile) print(rotxcb, rotycb, rotzcb, file=cbfile) cbfile.close() @@ -1051,11 +1116,11 @@ def swiftest_xr2infile(ds, param, in_type="NETCDF_DOUBLE", infile_name=None,fram print(pl.id.count().values, file=plfile) for i in pl.id: pli = pl.sel(id=i) - if param['RHILL_PRESENT'] == 'YES': + if param['RHILL_PRESENT']: print(pli['name'].values[0], pli['Gmass'].values[0], pli['rhill'].values[0], file=plfile) else: print(pli['name'].values[0], pli['Gmass'].values[0], file=plfile) - if param['CHK_CLOSE'] == 'YES': + if param['CHK_CLOSE']: print(pli['radius'].values[0], file=plfile) if param['IN_FORM'] == 'XV': print(pli['xhx'].values[0], pli['xhy'].values[0], pli['xhz'].values[0], file=plfile) @@ -1065,7 +1130,7 @@ def swiftest_xr2infile(ds, param, in_type="NETCDF_DOUBLE", infile_name=None,fram print(pli['capom'].values[0], pli['omega'].values[0], pli['capm'].values[0], file=plfile) else: print(f"{param['IN_FORM']} is not a valid input format type.") - if param['ROTATION'] == 'YES': + if param['ROTATION']: print(pli['Ip1'].values[0], pli['Ip2'].values[0], pli['Ip3'].values[0], file=plfile) print(pli['rotx'].values[0], pli['roty'].values[0], pli['rotz'].values[0], file=plfile) plfile.close() @@ -1114,7 +1179,7 @@ def swifter_xr2infile(ds, param, framenum=-1): tp = frame.where(np.isnan(frame['Gmass']), drop=True).drop_vars(['Gmass', 'radius', 'j2rp2', 'j4rp4']) GMSun = np.double(cb['Gmass']) - if param['CHK_CLOSE'] == 'YES': + if param['CHK_CLOSE']: RSun = np.double(cb['radius']) else: RSun = param['CHK_RMIN'] @@ -1130,11 +1195,11 @@ def swifter_xr2infile(ds, param, framenum=-1): print('0.0 0.0 0.0', file=plfile) for i in pl.id: pli = pl.sel(id=i) - if param['RHILL_PRESENT'] == "YES": + if param['RHILL_PRESENT']: print(i.values, pli['Gmass'].values, pli['rhill'].values, file=plfile) else: print(i.values, pli['Gmass'].values, file=plfile) - if param['CHK_CLOSE'] == "YES": + if param['CHK_CLOSE']: print(pli['radius'].values, file=plfile) print(pli['xhx'].values, pli['xhy'].values, pli['xhz'].values, file=plfile) print(pli['vhx'].values, pli['vhy'].values, pli['vhz'].values, file=plfile) @@ -1181,10 +1246,10 @@ def swift2swifter(swift_param, plname="", tpname="", conversion_questions={}): intxt = input("Is this a SyMBA input file with RHILL values in pl.in? (y/N)> ") if intxt.upper() == 'Y': isSyMBA = True - swifter_param['RHILL_PRESENT'] = 'YES' + swifter_param['RHILL_PRESENT'] = True else: isSyMBA = False - swifter_param['RHILL_PRESENT'] = 'NO' + swifter_param['RHILL_PRESENT'] = False isDouble = conversion_questions.get('DOUBLE', None) if not isDouble: @@ -1212,9 +1277,9 @@ def swift2swifter(swift_param, plname="", tpname="", conversion_questions={}): swifter_param['OUT_FORM'] = 'XV' if swift_param['LCLOSE'] == "T": - swifter_param['CHK_CLOSE'] = "YES" + swifter_param['CHK_CLOSE'] = True else: - swifter_param['CHK_CLOSE'] = "NO" + swifter_param['CHK_CLOSE'] = False swifter_param['CHK_RMIN'] = swift_param['RMIN'] swifter_param['CHK_RMAX'] = swift_param['RMAX'] @@ -1253,17 +1318,17 @@ def swift2swifter(swift_param, plname="", tpname="", conversion_questions={}): if not intxt: intxt = input("EXTRA_FORCE: Use additional user-specified force routines? (y/N)> ") if intxt.upper() == 'Y': - swifter_param['EXTRA_FORCE'] = 'YES' + swifter_param['EXTRA_FORCE'] = True else: - swifter_param['EXTRA_FORCE'] = 'NO' + swifter_param['EXTRA_FORCE'] = False intxt = conversion_questions.get('BIG_DISCARD', None) if not intxt: intxt = input("BIG_DISCARD: include data for all bodies > GMTINY for each discard record? (y/N)> ") if intxt.upper() == 'Y': - swifter_param['BIG_DISCARD'] = 'YES' + swifter_param['BIG_DISCARD'] = True else: - swifter_param['BIG_DISCARD'] = 'NO' + swifter_param['BIG_DISCARD'] = False # Convert the PL file if plname == '': @@ -1309,11 +1374,11 @@ def swift2swifter(swift_param, plname="", tpname="", conversion_questions={}): else: if swift_param['LCLOSE'] == "T": plrad = real2float(i_list[1]) - if swifter_param['RHILL_PRESENT'] == 'YES': + if swifter_param['RHILL_PRESENT']: print(n + 1, GMpl, rhill, file=plnew) else: print(n + 1, GMpl, file=plnew) - if swifter_param['CHK_CLOSE'] == 'YES': + if swifter_param['CHK_CLOSE']: print(plrad, file=plnew) line = plold.readline() i_list = [i for i in re.split(' +|\t',line) if i.strip()] @@ -1433,12 +1498,12 @@ def swifter2swiftest(swifter_param, plname="", tpname="", cbname="", conversion_ i_list = [i for i in re.split(' +|\t',line) if i.strip()] idnum = int(i_list[0]) GMpl = real2float(i_list[1]) - if swifter_param['RHILL_PRESENT'] == 'YES': + if swifter_param['RHILL_PRESENT']: rhill = real2float(i_list[2]) print(idnum, GMpl, rhill, file=plnew) else: print(idnum, GMpl, file=plnew) - if swifter_param['CHK_CLOSE'] == 'YES': + if swifter_param['CHK_CLOSE']: line = plold.readline() i_list = [i for i in re.split(' +|\t',line) if i.strip()] plrad = real2float(i_list[0]) @@ -1608,7 +1673,7 @@ def swifter2swiftest(swifter_param, plname="", tpname="", cbname="", conversion_ # Remove the unneeded parameters if 'C' in swiftest_param: - swiftest_param['GR'] = 'YES' + swiftest_param['GR'] = True swiftest_param.pop('C', None) swiftest_param.pop('J2', None) swiftest_param.pop('J4', None) @@ -1697,7 +1762,7 @@ def swiftest2swifter_param(swiftest_param, J2=0.0, J4=0.0): TU2S = swifter_param.pop("TU2S", 1.0) GR = swifter_param.pop("GR", None) if GR is not None: - if GR == 'YES': + if GR: swifter_param['C'] = swiftest.einsteinC * np.longdouble(TU2S) / np.longdouble(DU2M) for key in newfeaturelist: tmp = swifter_param.pop(key, None) @@ -1769,7 +1834,7 @@ def swifter2swift_param(swifter_param, J2=0.0, J4=0.0): swift_param['BINARY_OUTPUTFILE'] = swifter_param['BIN_OUT'] swift_param['STATUS_FLAG_FOR_OPEN_STATEMENTS'] = swifter_param['OUT_STAT'] - if swifter_param['CHK_CLOSE'] == "YES": + if swifter_param['CHK_CLOSE']: swift_param['LCLOSE'] = "T" else: swift_param['LCLOSE'] = "F" diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 38fa44d65..f95e8b426 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -23,6 +23,7 @@ from typing import ( Literal, Dict, + List ) @@ -49,11 +50,14 @@ def __init__(self, TU2S: float | None = None, rmin: float = constants.RSun / constants.AU2M, rmax: float = 10000.0, - close_encounter_check: bool =True, - general_relativity: bool =True, - fragmentation: bool =True, + close_encounter_check: bool = True, + general_relativity: bool = True, + fragmentation: bool = True, rotation: bool = True, compute_conservation_values: bool = False, + extra_force: bool = False, + big_discard: bool = False, + rhill_present: bool = False, interaction_loops: Literal["TRIANGULAR","FLAT","ADAPTIVE"] = "TRIANGULAR", encounter_check_loops: Literal["TRIANGULAR","SORTSWEEP","ADAPTIVE"] = "TRIANGULAR", verbose: bool = True @@ -154,6 +158,12 @@ def __init__(self, compute_conservation_values : bool, default False Turns on the computation of energy, angular momentum, and mass conservation and reports the values every output step of a running simulation. + extra_force: bool, default False + Turns on user-defined force function. + big_discard: bool, default False + Includes big bodies when performing a discard (Swifter only) + rhill_present: bool, default False + Include the Hill's radius with the input files. interaction_loops : {"TRIANGULAR","FLAT","ADAPTIVE"}, default "TRIANGULAR" *Swiftest Experimental feature* Specifies which algorithm to use for the computation of body-body gravitational forces. @@ -190,15 +200,6 @@ def __init__(self, 'ISTEP_OUT': 1, 'ISTEP_DUMP': 1, 'CHK_QMIN_COORD': "HELIO", - 'EXTRA_FORCE': "NO", - 'BIG_DISCARD': "NO", - 'CHK_CLOSE': "YES", - 'RHILL_PRESENT': "YES", - 'FRAGMENTATION': "YES", - 'ROTATION': "YES", - 'TIDES': "NO", - 'ENERGY': "YES", - 'GR': "YES", 'INTERACTION_LOOPS': interaction_loops, 'ENCOUNTER_CHECK': encounter_check_loops } @@ -217,7 +218,17 @@ def __init__(self, self.set_output_files(output_file_type=output_file_type, output_file_name=output_file_name, - output_format=output_format ) + output_format=output_format) + + self.set_features(close_encounter_check=close_encounter_check, + general_relativity=general_relativity, + fragmentation=fragmentation, + rotation=rotation, + compute_conservation_values=compute_conservation_values, + extra_force=extra_force, + big_discard=big_discard, + rhill_present=rhill_present, + verbose = False) # If the parameter file is in a different location than the current working directory, we will need # to use it to properly open bin files @@ -236,6 +247,149 @@ def __init__(self, print(f"BIN_OUT file {binpath} not found.") return + def set_features(self, + close_encounter_check: bool | None = None, + general_relativity: bool | None = None, + fragmentation: bool | None = None, + rotation: bool | None = None, + compute_conservation_values: bool | None=None, + extra_force: bool | None = None, + big_discard: bool | None = None, + rhill_present: bool | None = None, + tides: bool | None = None, + verbose: bool | None = None, + ): + """ + Turns on or off various features of a simulation. + + Parameters + ---------- + close_encounter_check : bool, optional + Check for close encounters between bodies. If set to True, then the radii of massive bodies must be included + in initial conditions. + general_relativity : bool, optional + Include the post-Newtonian correction in acceleration calculations. + fragmentation : bool, optional + If set to True, this turns on the Fraggle fragment generation code and `rotation` must also be True. + This argument only applies to Swiftest-SyMBA simulations. It will be ignored otherwise. + rotation : bool, optional + If set to True, this turns on rotation tracking and radius, rotation vector, and moments of inertia values + must be included in the initial conditions. + This argument only applies to Swiftest-SyMBA simulations. It will be ignored otherwise. + compute_conservation_values : bool, optional + Turns on the computation of energy, angular momentum, and mass conservation and reports the values + every output step of a running simulation. + extra_force: bool, optional + Turns on user-defined force function. + big_discard: bool, optional + Includes big bodies when performing a discard (Swifter only) + rhill_present: bool, optional + Include the Hill's radius with the input files. + tides: bool, optional + Turns on tidal model (IN DEVELOPMENT - IGNORED) + Yarkovsky: bool, optional + Turns on Yarkovsky model (IN DEVELOPMENT - IGNORED) + YORP: bool, optional + Turns on YORP model (IN DEVELOPMENT - IGNORED) + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag + + Returns + ------- + Sets the appropriate parameters in the self.param dictionary + + """ + + update_list = [] + if close_encounter_check is not None: + self.param["CHK_CLOSE"] = close_encounter_check + update_list.append("close_encounter_check") + + if general_relativity is not None: + self.param["GR"] = general_relativity + update_list.append("general_relativity") + + if fragmentation is not None: + self.param['FRAGMENTATION'] = fragmentation + update_list.append("fragmentation") + + if rotation is not None: + self.param['ROTATION'] = rotation + update_list.append("rotation") + + if compute_conservation_values is not None: + self.param["ENERGY"] = compute_conservation_values + update_list.append("compute_conservation_values") + + if extra_force is not None: + self.param["EXTRA_FORCE"] = extra_force + update_list.append("extra_force") + + if big_discard is not None: + if self.codename != "Swifter": + self.param["BIG_DISCARD"] = False + else: + self.param["BIG_DISCARD"] = big_discard + update_list.append("big_discard") + + if rhill_present is not None: + self.param["RHILL_PRESENT"] = rhill_present + update_list.append("rhill_present") + + if verbose is None: + verbose = self.verbose + if verbose: + if len(update_list) > 0: + feature_dict = self.get_features(update_list) + return + + + def get_features(self,feature: str | List[str] | None = None): + """ + + Returns a subset of the parameter dictionary containing the current value of the feature boolean values. + If the verbose option is set in the Simulation object, then it will also print the values. + + Parameters + ---------- + feature : str | List[str], optional + A single string or list of strings containing the names of the features to extract. Default is all of: + ["close_encounter_check", "general_relativity", "fragmentation", "rotation", "compute_conservation_values"] + + Returns + ------- + feature_dict : dict + A dictionary containing the requested features. + + """ + + valid_features = {"close_encounter_check": "CHK_CLOSE", + "general_relativity": "GR", + "fragmentation": "FRAGMENTATION", + "rotation": "ROTATION", + "compute_conservation_values": "ENERGY", + "extra_force": "EXTRA_FORCE", + "big_discard": "BIG_DISCARD", + "rhill_present": "RHILL_PRESENT" + } + + if feature is None: + feature_list = valid_features.keys() + elif type(feature) is str: + feature_list = [feature] + else: + # Only allow features to be checked if they are valid. Otherwise ignore. + feature_list = [feat for feat in feature if feat in set(valid_features.keys())] + + # Extract the feature dictionary + feature_dict = {valid_features[feat]:self.param[valid_features[feat]] for feat in feature_list} + + if self.verbose: + print("Simulation feature parameters:") + for key,val in feature_dict.items(): + print(f"{key:<16} {val}") + + return feature_dict def set_init_cond_files(self, init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"], From 2ac1e39d5a4fd012f381c50fecd27387f4369c2f Mon Sep 17 00:00:00 2001 From: David Minton Date: Tue, 8 Nov 2022 15:00:25 -0500 Subject: [PATCH 05/75] Changed from "_features" to "_feature" before it gets too late to change it --- python/swiftest/swiftest/simulation_class.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index f95e8b426..622e36fe2 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -220,7 +220,7 @@ def __init__(self, output_file_name=output_file_name, output_format=output_format) - self.set_features(close_encounter_check=close_encounter_check, + self.set_feature(close_encounter_check=close_encounter_check, general_relativity=general_relativity, fragmentation=fragmentation, rotation=rotation, @@ -247,7 +247,7 @@ def __init__(self, print(f"BIN_OUT file {binpath} not found.") return - def set_features(self, + def set_feature(self, close_encounter_check: bool | None = None, general_relativity: bool | None = None, fragmentation: bool | None = None, @@ -340,11 +340,11 @@ def set_features(self, verbose = self.verbose if verbose: if len(update_list) > 0: - feature_dict = self.get_features(update_list) + feature_dict = self.get_feature(update_list) return - def get_features(self,feature: str | List[str] | None = None): + def get_feature(self,feature: str | List[str] | None = None): """ Returns a subset of the parameter dictionary containing the current value of the feature boolean values. From a784afc04e82a7fc32c53cdd42fde3424f84dfcc Mon Sep 17 00:00:00 2001 From: David Minton Date: Tue, 8 Nov 2022 17:56:20 -0500 Subject: [PATCH 06/75] Added simulation time setter and getter methods --- .../Basic_Simulation/initial_conditions.py | 7 +- python/swiftest/swiftest/io.py | 37 ++- python/swiftest/swiftest/simulation_class.py | 236 ++++++++++++++++-- 3 files changed, 252 insertions(+), 28 deletions(-) diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index dceb33cc7..2b3693b0c 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -32,12 +32,9 @@ # Initialize the simulation object as a variable sim = swiftest.Simulation(init_cond_file_type="ASCII") +sim.set_simulation_time(tstart=0.0, tstop=10.0, dt=0.005, tstep_out=1.0,verbose=True) + # Add parameter attributes to the simulation object -sim.param['T0'] = 0.0 -sim.param['TSTOP'] = 10 -sim.param['DT'] = 0.005 -sim.param['ISTEP_OUT'] = 200 -sim.param['ISTEP_DUMP'] = 200 sim.param['GMTINY'] = 1e-6 sim.param['MIN_GMFRAG'] = 1e-9 diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index b12d32cd3..28303f8f8 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -17,9 +17,38 @@ import tempfile import re -newfeaturelist = ("FRAGMENTATION", "ROTATION", "TIDES", "ENERGY", "GR", "YARKOVSKY", "YORP", "IN_FORM", "SEED", "INTERACTION_LOOPS", "ENCOUNTER_CHECK") +# This defines features that are new in Swiftest and not in Swifter (for conversion between param.in files) +newfeaturelist = ("RESTART", + "FRAGMENTATION", + "ROTATION", + "TIDES", + "ENERGY", + "GR", + "YARKOVSKY", + "YORP", + "IN_FORM", + "SEED", + "INTERACTION_LOOPS", + "ENCOUNTER_CHECK", + "TSTART") + +# This list defines features that are booleans, so must be converted to/from string when writing/reading from file +bool_param = ["RESTART", + "CHK_CLOSE", + "EXTRA_FORCE", + "RHILL_PRESENT", + "BIG_DISCARD", + "FRAGMENTATION", + "ROTATION", + "TIDES", + "ENERGY", + "GR", + "YARKOVSKY", + "YORP"] + +# This defines Xarray Dataset variables that are strings, which must be processed due to quirks in how NetCDF-Fortran +# handles strings differently than Python's Xarray. string_varnames = ["name", "particle_type", "status", "origin_type"] -bool_param = ["CHK_CLOSE", "EXTRA_FORCE", "RHILL_PRESENT", "BIG_DISCARD", "FRAGMENTATION", "ROTATION", "TIDES", "ENERGY", "GR", "YARKOVSKY", "YORP"] def bool2yesno(boolval): """ @@ -144,6 +173,7 @@ def read_swiftest_param(param_file_name, param, verbose=True): param['IN_TYPE'] = param['IN_TYPE'].upper() param['IN_FORM'] = param['IN_FORM'].upper() param['T0'] = real2float(param['T0']) + param['TSTART'] = real2float(param['TSTART']) param['TSTOP'] = real2float(param['TSTOP']) param['DT'] = real2float(param['DT']) param['CHK_RMIN'] = real2float(param['CHK_RMIN']) @@ -401,7 +431,8 @@ def write_labeled_param(param, param_file_name): 'CHK_QMIN_RANGE', 'MU2KG', 'TU2S', - 'DU2M' ] + 'DU2M', + 'RESTART'] ptmp = param.copy() # Print the list of key/value pairs in the preferred order for key in keylist: diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 622e36fe2..bda2d521d 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -58,6 +58,7 @@ def __init__(self, extra_force: bool = False, big_discard: bool = False, rhill_present: bool = False, + restart: bool = False, interaction_loops: Literal["TRIANGULAR","FLAT","ADAPTIVE"] = "TRIANGULAR", encounter_check_loops: Literal["TRIANGULAR","SORTSWEEP","ADAPTIVE"] = "TRIANGULAR", verbose: bool = True @@ -164,6 +165,10 @@ def __init__(self, Includes big bodies when performing a discard (Swifter only) rhill_present: bool, default False Include the Hill's radius with the input files. + restart : bool, default False + If true, will restart an old run. The file given by `output_file_name` must exist before the run can + execute. If false, will start a new run. If the file given by `output_file_name` exists, it will be replaced + when the run is executed. interaction_loops : {"TRIANGULAR","FLAT","ADAPTIVE"}, default "TRIANGULAR" *Swiftest Experimental feature* Specifies which algorithm to use for the computation of body-body gravitational forces. @@ -205,6 +210,7 @@ def __init__(self, } self.codename = codename self.verbose = verbose + self.restart = restart self.set_distance_range(rmin=rmin, rmax=rmax) @@ -221,14 +227,15 @@ def __init__(self, output_format=output_format) self.set_feature(close_encounter_check=close_encounter_check, - general_relativity=general_relativity, - fragmentation=fragmentation, - rotation=rotation, - compute_conservation_values=compute_conservation_values, - extra_force=extra_force, - big_discard=big_discard, - rhill_present=rhill_present, - verbose = False) + general_relativity=general_relativity, + fragmentation=fragmentation, + rotation=rotation, + compute_conservation_values=compute_conservation_values, + extra_force=extra_force, + big_discard=big_discard, + rhill_present=rhill_present, + restart=restart, + verbose = False) # If the parameter file is in a different location than the current working directory, we will need # to use it to properly open bin files @@ -247,18 +254,198 @@ def __init__(self, print(f"BIN_OUT file {binpath} not found.") return + def set_simulation_time(self, + t0: float | None = None, + tstart: float | None = None, + tstop: float | None = None, + dt: float | None = None, + istep_out : int | None = None, + tstep_out : float | None = None, + istep_dump : int | None = None, + verbose : bool | None = None, + ): + """ + + Parameters + ---------- + t0 : float, optional + The reference time for the start of the simulation. Defaults is 0.0 + tstart : float, optional + The start time for a restarted simulation. For a new simulation, tstart will be set to t0 automatically. + tstop : float, optional + The stopping time for a simulation. `tstop` must be greater than `tstart`. + dt : float, optional + The step size of the simulation. `dt` must be less than or equal to `tstop-dstart`. + istep_out : int, optional + The number of time steps between outputs to file. *Note*: only `istep_out` or `toutput` can be set. + tstep_out : float, optional + The approximate time between when outputs are written to file. Passing this computes + `istep_out = floor(tstep_out/dt)`. *Note*: only `istep_out` or `toutput` can be set. + istep_dump : int, optional + The anumber of time steps between outputs to dump file. If not set, this will be set to the value of + `istep_out` (or the equivalent value determined by `tstep_out`) + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag + + Returns + ------- + Sets the appropriate parameter variables in the parameter dictionary. + + """ + + update_list = [] + + + if t0 is None: + t0 = self.param.pop("T0", None) + if t0 is None: + t0 = 0.0 + else: + update_list.append("t0") + + if tstart is None: + tstart = self.param.pop("TSTART", None) + if tstart is None: + tstart = t0 + else: + update_list.append("tstart") + + self.param['T0'] = t0 + self.param['TSTART'] = tstart + + if tstop is None: + tstop = self.param.pop("TSTOP", None) + if tstop is None: + print("Error! tstop is not set!") + return + else: + update_list.append("tstop") + + if tstop <= tstart: + print("Error! tstop must be greater than tstart.") + return + + self.param['TSTOP'] = tstop + + if dt is None: + dt = self.param.pop("DT", None) + if dt is None: + print("Error! dt is not set!") + return + else: + update_list.append("dt") + + if dt > (tstop - tstart): + print("Error! dt must be smaller than tstop-tstart") + print(f"Setting dt = {tstop-tstart} instead of {dt}") + dt = tstop - tstart + + self.param['DT'] = dt + + if istep_out is None and tstep_out is None: + istep_out = self.param.pop("ISTEP_OUT", None) + if istep_out is None: + print("Error! istep_out is not set") + return + + if istep_out is not None and tstep_out is not None: + print("Error! istep_out and tstep_out cannot both be set") + return + + if tstep_out is not None: + istep_out = int(np.floor(tstep_out / dt)) + + self.param['ISTEP_OUT'] = istep_out + + if istep_dump is None: + istep_dump = istep_out + + self.param['ISTEP_DUMP'] = istep_dump + + update_list.extend(["istep_out", "tstep_out", "istep_dump"]) + + if verbose is None: + verbose = self.verbose + if verbose: + if len(update_list) > 0: + time_dict = self.get_simulation_time(update_list) + + return + + def get_simulation_time(self, param_key: str | List[str] | None = None): + """ + + Returns a subset of the parameter dictionary containing the current simulation time parameters. + If the verbose option is set in the Simulation object, then it will also print the values. + + Parameters + ---------- + param_key : str | List[str], optional + A single string or list of strings containing the names of the simulation time parameters to extract. + Default is all of: + ["t0", "tstart", "tstop", "dt", "istep_out", "tstep_out", "istep_dump"] + + Returns + ------- + time_dict : dict + A dictionary containing the requested parameters + + """ + + valid_var = {"t0": "T0", + "tstart": "TSTART", + "tstop": "TSTOP", + "dt": "DT", + "istep_out": "ISTEP_OUT", + "istep_dump": "ISTEP_DUMP", + } + + units = {"T0" : self.TU_name, + "TSTART" : self.TU_name, + "TSTOP" : self.TU_name, + "DT" : self.TU_name, + "TSTEP_OUT" : self.TU_name, + "ISTEP_OUT" : "", + "ISTEP_DUMP" : ""} + + if "tstep_out" in param_key: + istep_out = self.param['ISTEP_OUT'] + dt = self.param['DT'] + tstep_out = istep_out * dt + else: + tstep_out = None + + if param_key is None: + param_list = valid_var.keys() + elif type(param_key) is str: + param_list = [param_key] + else: + param_list = [k for k in param_key if k in set(valid_var.keys())] + + time_dict = {valid_var[k]: self.param[valid_var[k]] for k in param_list} + + if self.verbose: + print("Simulation feature parameters:") + for key, val in time_dict.items(): + print(f"{key:<16} {val} {units[key]}") + if tstep_out is not None: + print(f"{'TSTEP_OUT':<16} {tstep_out} {units['TSTEP_OUT']}") + + return time_dict + def set_feature(self, - close_encounter_check: bool | None = None, - general_relativity: bool | None = None, - fragmentation: bool | None = None, - rotation: bool | None = None, - compute_conservation_values: bool | None=None, - extra_force: bool | None = None, - big_discard: bool | None = None, - rhill_present: bool | None = None, - tides: bool | None = None, - verbose: bool | None = None, - ): + close_encounter_check: bool | None = None, + general_relativity: bool | None = None, + fragmentation: bool | None = None, + rotation: bool | None = None, + compute_conservation_values: bool | None=None, + extra_force: bool | None = None, + big_discard: bool | None = None, + rhill_present: bool | None = None, + restart: bool | None = None, + tides: bool | None = None, + verbose: bool | None = None, + ): """ Turns on or off various features of a simulation. @@ -291,6 +478,10 @@ def set_feature(self, Turns on Yarkovsky model (IN DEVELOPMENT - IGNORED) YORP: bool, optional Turns on YORP model (IN DEVELOPMENT - IGNORED) + restart : bool, default False + If true, will restart an old run. The file given by `output_file_name` must exist before the run can + execute. If false, will start a new run. If the file given by `output_file_name` exists, it will be replaced + when the run is executed. verbose: bool, optional If passed, it will override the Simulation object's verbose flag @@ -336,6 +527,10 @@ def set_feature(self, self.param["RHILL_PRESENT"] = rhill_present update_list.append("rhill_present") + if restart is not None: + self.param["RESTART"] = restart + update_list.append("restart") + if verbose is None: verbose = self.verbose if verbose: @@ -370,7 +565,8 @@ def get_feature(self,feature: str | List[str] | None = None): "compute_conservation_values": "ENERGY", "extra_force": "EXTRA_FORCE", "big_discard": "BIG_DISCARD", - "rhill_present": "RHILL_PRESENT" + "rhill_present": "RHILL_PRESENT", + "restart" : "RESTART" } if feature is None: From b6d3018bbe03236bfbac8a3751a37e0e3e79f974 Mon Sep 17 00:00:00 2001 From: David Minton Date: Tue, 8 Nov 2022 17:57:49 -0500 Subject: [PATCH 07/75] Removed unnecessary verbose flag --- examples/Basic_Simulation/initial_conditions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index 2b3693b0c..c9d0823af 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -32,7 +32,7 @@ # Initialize the simulation object as a variable sim = swiftest.Simulation(init_cond_file_type="ASCII") -sim.set_simulation_time(tstart=0.0, tstop=10.0, dt=0.005, tstep_out=1.0,verbose=True) +sim.set_simulation_time(tstart=0.0, tstop=10.0, dt=0.005, tstep_out=1.0) # Add parameter attributes to the simulation object sim.param['GMTINY'] = 1e-6 From 6aa38058d294cb64352696778d60dbe8cf1a3934 Mon Sep 17 00:00:00 2001 From: David Minton Date: Tue, 8 Nov 2022 17:59:24 -0500 Subject: [PATCH 08/75] Set tides parameter to be False so at least it's in the param list --- python/swiftest/swiftest/simulation_class.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index bda2d521d..d01da849a 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -531,6 +531,8 @@ def set_feature(self, self.param["RESTART"] = restart update_list.append("restart") + self.param["TIDES"] = False + if verbose is None: verbose = self.verbose if verbose: From 163f321560a28724c206d5e8bd95050652b06d55 Mon Sep 17 00:00:00 2001 From: David Minton Date: Tue, 8 Nov 2022 19:41:20 -0500 Subject: [PATCH 09/75] Switched to using boolean True instead of YES in the initial conditions generator --- python/swiftest/swiftest/init_cond.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/python/swiftest/swiftest/init_cond.py b/python/swiftest/swiftest/init_cond.py index 287e5ecae..0166e78a8 100644 --- a/python/swiftest/swiftest/init_cond.py +++ b/python/swiftest/swiftest/init_cond.py @@ -146,7 +146,7 @@ def solar_system_horizons(plname, idval, param, ephemerides_start_date, ds): Rpl = Rcb J2 = J2RP2 J4 = J4RP4 - if param['ROTATION'] == 'YES': + if param['ROTATION']: Ip1 = [Ipsun[0]] Ip2 = [Ipsun[1]] Ip3 = [Ipsun[2]] @@ -202,19 +202,19 @@ def solar_system_horizons(plname, idval, param, ephemerides_start_date, ds): if ispl: GMpl = [] GMpl.append(GMcb[0] / MSun_over_Mpl[plname]) - if param['CHK_CLOSE'] == 'YES': + if param['CHK_CLOSE']: Rpl = [] Rpl.append(planetradius[plname] * DCONV) else: Rpl = None # Generate planet value vectors - if (param['RHILL_PRESENT'] == 'YES'): + if (param['RHILL_PRESENT']): rhill = [] rhill.append(pldata[plname].elements()['a'][0] * DCONV * (3 * MSun_over_Mpl[plname]) ** (-THIRDLONG)) else: rhill = None - if (param['ROTATION'] == 'YES'): + if (param['ROTATION']): Ip1 = [] Ip2 = [] Ip3 = [] @@ -304,7 +304,7 @@ def vec2xr(param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, else: iscb = False - if param['ROTATION'] == 'YES': + if param['ROTATION']: if Ip1 is None: Ip1 = np.full_like(v1, 0.4) if Ip2 is None: @@ -325,10 +325,10 @@ def vec2xr(param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, else: ispl = False - if ispl and param['CHK_CLOSE'] == 'YES' and Rpl is None: + if ispl and param['CHK_CLOSE'] and Rpl is None: print("Massive bodies need a radius value.") return None - if ispl and rhill is None and param['RHILL_PRESENT'] == 'YES': + if ispl and rhill is None and param['RHILL_PRESENT']: print("rhill is required.") return None @@ -342,7 +342,7 @@ def vec2xr(param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, if iscb: label_float = clab.copy() vec_float = np.vstack([GMpl,Rpl,J2,J4]) - if param['ROTATION'] == 'YES': + if param['ROTATION']: vec_float = np.vstack([vec_float, Ip1, Ip2, Ip3, rotx, roty, rotz]) particle_type = "Central Body" else: @@ -350,11 +350,11 @@ def vec2xr(param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, if ispl: label_float = plab.copy() vec_float = np.vstack([vec_float, GMpl]) - if param['CHK_CLOSE'] == 'YES': + if param['CHK_CLOSE']: vec_float = np.vstack([vec_float, Rpl]) - if param['RHILL_PRESENT'] == 'YES': + if param['RHILL_PRESENT']: vec_float = np.vstack([vec_float, rhill]) - if param['ROTATION'] == 'YES': + if param['ROTATION']: vec_float = np.vstack([vec_float, Ip1, Ip2, Ip3, rotx, roty, rotz]) particle_type = np.repeat("Massive Body",idvals.size) else: From c47953ac36eb1f85b10cb91a0964408eec5594b3 Mon Sep 17 00:00:00 2001 From: David Minton Date: Tue, 8 Nov 2022 20:53:58 -0500 Subject: [PATCH 10/75] Added setter and getter methods for initial conditions files --- python/swiftest/swiftest/simulation_class.py | 292 +++++++++++++++---- 1 file changed, 230 insertions(+), 62 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index d01da849a..1dba3a49d 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -224,7 +224,8 @@ def __init__(self, self.set_output_files(output_file_type=output_file_type, output_file_name=output_file_name, - output_format=output_format) + output_format=output_format, + verbose = False) self.set_feature(close_encounter_check=close_encounter_check, general_relativity=general_relativity, @@ -372,7 +373,7 @@ def set_simulation_time(self, return - def get_simulation_time(self, param_key: str | List[str] | None = None): + def get_simulation_time(self, arg_list: str | List[str] | None = None): """ Returns a subset of the parameter dictionary containing the current simulation time parameters. @@ -380,7 +381,7 @@ def get_simulation_time(self, param_key: str | List[str] | None = None): Parameters ---------- - param_key : str | List[str], optional + arg_list : str | List[str], optional A single string or list of strings containing the names of the simulation time parameters to extract. Default is all of: ["t0", "tstart", "tstop", "dt", "istep_out", "tstep_out", "istep_dump"] @@ -400,36 +401,37 @@ def get_simulation_time(self, param_key: str | List[str] | None = None): "istep_dump": "ISTEP_DUMP", } - units = {"T0" : self.TU_name, - "TSTART" : self.TU_name, - "TSTOP" : self.TU_name, - "DT" : self.TU_name, - "TSTEP_OUT" : self.TU_name, - "ISTEP_OUT" : "", - "ISTEP_DUMP" : ""} + units = {"t0" : self.TU_name, + "tstart" : self.TU_name, + "tstop" : self.TU_name, + "dt" : self.TU_name, + "tstep_out" : self.TU_name, + "istep_out" : "", + "istep_dump" : ""} - if "tstep_out" in param_key: + if arg_list is not None and "tstep_out" in arg_list: istep_out = self.param['ISTEP_OUT'] dt = self.param['DT'] tstep_out = istep_out * dt else: tstep_out = None - if param_key is None: - param_list = valid_var.keys() - elif type(param_key) is str: - param_list = [param_key] + if arg_list is None: + arg_list = valid_var.keys() + elif type(arg_list) is str: + arg_list = [arg_list] else: - param_list = [k for k in param_key if k in set(valid_var.keys())] + arg_list = [k for k in arg_list if k in set(valid_var.keys())] - time_dict = {valid_var[k]: self.param[valid_var[k]] for k in param_list} + time_dict = {valid_var[k]: self.param[valid_var[k]] for k in arg_list} if self.verbose: - print("Simulation feature parameters:") - for key, val in time_dict.items(): - print(f"{key:<16} {val} {units[key]}") + print("\nSimulation time parameters:") + for arg in arg_list: + key = valid_var[arg] + print(f"{arg:<32} {time_dict[key]} {units[arg]}") if tstep_out is not None: - print(f"{'TSTEP_OUT':<16} {tstep_out} {units['TSTEP_OUT']}") + print(f"{'tstep_out':<32} {tstep_out} {units['tstep_out']}") return time_dict @@ -541,7 +543,7 @@ def set_feature(self, return - def get_feature(self,feature: str | List[str] | None = None): + def get_feature(self, arg_list: str | List[str] | None = None): """ Returns a subset of the parameter dictionary containing the current value of the feature boolean values. @@ -549,7 +551,7 @@ def get_feature(self,feature: str | List[str] | None = None): Parameters ---------- - feature : str | List[str], optional + arg_list: str | List[str], optional A single string or list of strings containing the names of the features to extract. Default is all of: ["close_encounter_check", "general_relativity", "fragmentation", "rotation", "compute_conservation_values"] @@ -560,45 +562,48 @@ def get_feature(self,feature: str | List[str] | None = None): """ - valid_features = {"close_encounter_check": "CHK_CLOSE", - "general_relativity": "GR", - "fragmentation": "FRAGMENTATION", - "rotation": "ROTATION", - "compute_conservation_values": "ENERGY", - "extra_force": "EXTRA_FORCE", - "big_discard": "BIG_DISCARD", - "rhill_present": "RHILL_PRESENT", - "restart" : "RESTART" - } - - if feature is None: - feature_list = valid_features.keys() - elif type(feature) is str: - feature_list = [feature] + valid_var = {"close_encounter_check": "CHK_CLOSE", + "general_relativity": "GR", + "fragmentation": "FRAGMENTATION", + "rotation": "ROTATION", + "compute_conservation_values": "ENERGY", + "extra_force": "EXTRA_FORCE", + "big_discard": "BIG_DISCARD", + "rhill_present": "RHILL_PRESENT", + "restart" : "RESTART" + } + + if arg_list is None: + arg_list = valid_var.keys() + elif type(arg_list) is str: + arg_list = [arg_list] else: - # Only allow features to be checked if they are valid. Otherwise ignore. - feature_list = [feat for feat in feature if feat in set(valid_features.keys())] + # Only allow arg_lists to be checked if they are valid. Otherwise ignore. + arg_list = [k for k in arg_list if k in set(valid_var.keys())] - # Extract the feature dictionary - feature_dict = {valid_features[feat]:self.param[valid_features[feat]] for feat in feature_list} + # Extract the arg_list dictionary + feature_dict = {valid_var[feat]:self.param[valid_var[feat]] for feat in arg_list} if self.verbose: - print("Simulation feature parameters:") - for key,val in feature_dict.items(): - print(f"{key:<16} {val}") + print("\nSimulation feature parameters:") + for arg in arg_list: + key = valid_var[arg] + print(f"{arg:<32} {feature_dict[key]}") return feature_dict def set_init_cond_files(self, - init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"], - init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None, - init_cond_format: Literal["EL", "XV"]): + init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"] | None = None, + init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None = None, + init_cond_format: Literal["EL", "XV"] | None = None, + verbose: bool | None = None + ): """ Sets the initial condition file parameters in the parameters dictionary. Parameters ---------- - init_cond_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"} + init_cond_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"}, optional The file type containing initial conditions for the simulation: * NETCDF_DOUBLE: A single initial conditions input file in NetCDF file format of type NETCDF_DOUBLE * NETCDF_FLOAT: A single initial conditions input file in NetCDF file format of type NETCDF_FLOAT @@ -620,6 +625,8 @@ def set_init_cond_files(self, Indicates whether the input initial conditions are given as orbital elements or cartesian position and velocity vectors. > *Note:* If `codename` is "Swift" or "Swifter", EL initial conditions are converted to XV. + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag Returns ------- @@ -627,6 +634,12 @@ def set_init_cond_files(self, """ + update_list = ["init_cond_file_name"] + if init_cond_file_type is not None: + update_list.append("init_cond_file_type") + if init_cond_format is not None: + update_list.append("init_cond_format") + def ascii_file_input_error_msg(codename): print(f"Error in set_init_cond_files: init_cond_file_name must be a dictionary of the form: ") print('{') @@ -678,19 +691,94 @@ def ascii_file_input_error_msg(codename): else: self.param["NC_IN"] = init_cond_file_name + + if verbose is None: + verbose = self.verbose + if verbose: + if len(update_list) > 0: + init_cond_file_dict = self.get_init_cond_files(update_list) + return + + def get_init_cond_files(self, arg_list: str | List[str] | None = None): + """ + + Returns a subset of the parameter dictionary containing the current initial condition file parameters + If the verbose option is set in the Simulation object, then it will also print the values. + + Parameters + ---------- + arg_list : str | List[str], optional + A single string or list of strings containing the names of the simulation time parameters to extract. + Default is all of: + ["init_cond_file_type", "init_cond_file_name", "init_cond_format"] + + Returns + ------- + init_cond_file_dict : dict + A dictionary containing the requested parameters + + """ + + valid_var = {"init_cond_file_type": "IN_TYPE", + "init_cond_format": "IN_FORM", + "init_cond_file_name" : "NC_IN", + "init_cond_file_name (cb)" : "CB_IN", + "init_cond_file_name (pl)" : "PL_IN", + "init_cond_file_name (tp)" : "TP_IN", + } + + + if arg_list is None: + arg_list = list(valid_var.keys()) + elif type(arg_list) is str: + arg_list = [arg_list] + else: + arg_list = [k for k in arg_list if k in set(valid_var.keys())] + + if "init_cond_file_name" in arg_list: + if self.param["IN_TYPE"] == "ASCII": + arg_list.remove("init_cond_file_name") + if "init_cond_file_name (cb)" not in arg_list: + arg_list.append("init_cond_file_name (cb)") + if "init_cond_file_name (pl)" not in arg_list: + arg_list.append("init_cond_file_name (pl)") + if "init_cond_file_name (tp)" not in arg_list: + arg_list.append("init_cond_file_name (tp)") + else: + if "init_cond_file_name (cb)" in arg_list: + arg_list.remove("init_cond_file_name (cb)") + if "init_cond_file_name (pl)" in arg_list: + arg_list.remove("init_cond_file_name (pl)") + if "init_cond_file_name (tp)" in arg_list: + arg_list.remove("init_cond_file_name (tp)") + + init_cond_file_dict = {valid_var[k]: self.param[valid_var[k]] for k in arg_list} + + if self.verbose: + print("\nInitial condition file parameters:") + for arg in arg_list: + key = valid_var[arg] + print(f"{arg:<32} {init_cond_file_dict[key]}") + + return init_cond_file_dict + + + + def set_output_files(self, - output_file_type: Literal[ "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"], - output_file_name: os.PathLike | str | None, - output_format: Literal["XV", "XVEL"] + output_file_type: Literal[ "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"] | None = None, + output_file_name: os.PathLike | str | None = None, + output_format: Literal["XV", "XVEL"] | None = None, + verbose: bool | None = None ): """ Sets the output file parameters in the parameters dictionary. Parameters ---------- - output_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT","REAL4","REAL8","XDR4","XDR8"}, default "NETCDF_DOUBLE" + output_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT","REAL4","REAL8","XDR4","XDR8"}, optional The file type for the outputs of the simulation. Compatible file types depend on the `codename` argument. * Swiftest: Only "NETCDF_DOUBLE" or "NETCDF_FLOAT" supported. * Swifter: Only "REAL4","REAL8","XDR4" or "XDR8" supported. @@ -699,25 +787,46 @@ def set_output_files(self, Name of output file to generate. If not supplied, then one of the default file names are used, depending on the value passed to `output_file_type`. If one of the NetCDF types are used, the default is "bin.nc". Otherwise, the default is "bin.dat". - output_format : {"XV","XVEL"}, default "XVEL" + output_format : {"XV","XVEL"}, optional Specifies the format for the data saved to the output file. If "XV" then cartesian position and velocity vectors for all bodies are stored. If "XVEL" then the orbital elements are also stored. + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag Returns ------- Sets the appropriate initial conditions file parameters inside the self.param dictionary. """ + update_list = [] + if output_file_type is not None: + update_list.append("output_file_type") + if output_file_name is not None: + update_list.append("output_file_name") + if output_format is not None: + update_list.append("output_format") if self.codename == "Swiftest": - if output_file_type not in ["NETCDF_DOUBLE", "NETCDF_FLOAT"]: + if output_file_type is None: + output_file_type = self.param.pop("OUT_TYPE", None) + if output_file_type is None: + output_file_type = "NETCDF_DOUBLE" + elif output_file_type not in ["NETCDF_DOUBLE", "NETCDF_FLOAT"]: print(f"{output_file_type} is not compatible with Swiftest. Setting to NETCDF_DOUBLE") output_file_type = "NETCDF_DOUBLE" elif self.codename == "Swifter": - if output_file_type not in ["REAL4","REAL8","XDR4","XDR8"]: + if output_file_type is None: + output_file_type = self.param.pop("OUT_TYPE", None) + if output_file_type is None: + output_file_type = "REAL8" + elif output_file_type not in ["REAL4","REAL8","XDR4","XDR8"]: print(f"{output_file_type} is not compatible with Swifter. Setting to REAL8") output_file_type = "REAL8" elif self.codename == "Swift": + if output_file_type is None: + output_file_type = self.param.pop("OUT_TYPE", None) + if output_file_type is None: + output_file_type = "REAL4" if output_file_type not in ["REAL4"]: print(f"{output_file_type} is not compatible with Swift. Setting to REAL4") output_file_type = "REAL4" @@ -734,10 +843,65 @@ def set_output_files(self, if output_format != "XV" and self.codename != "Swiftest": print(f"{output_format} is not compatible with {self.codename}. Setting to XV") output_format = "XV" - self.param['OUT_FORM'] = output_format + self.param["OUT_FORM"] = output_format + + if self.restart: + self.param["OUT_STAT"] = "APPEND" + else: + self.param["OUT_STAT"] = "REPLACE" + + if verbose is None: + verbose = self.verbose + if verbose: + if len(update_list) > 0: + output_file_dict = self.get_output_files(update_list) return + + def get_output_files(self, arg_list: str | List[str] | None = None): + """ + + Returns a subset of the parameter dictionary containing the current output file parameters + If the verbose option is set in the Simulation object, then it will also print the values. + + Parameters + ---------- + arg_list : str | List[str], optional + A single string or list of strings containing the names of the simulation time parameters to extract. + Default is all of: + ["output_file_type", "output_file_name", "output_format"] + + Returns + ------- + output_file_dict : dict + A dictionary containing the requested parameters + + """ + + valid_var = {"output_file_type": "OUT_TYPE", + "output_file_name": "BIN_OUT", + "output_format": "OUT_FORM", + } + + if arg_list is None: + arg_list = valid_var.keys() + elif type(arg_list) is str: + arg_list = [arg_list] + else: + arg_list = [k for k in arg_list if k in set(valid_var.keys())] + + output_file_dict = {valid_var[k]: self.param[valid_var[k]] for k in arg_list} + + if self.verbose: + print("\nOutput file parameters:") + for arg in arg_list: + key = valid_var[arg] + print(f"{arg:<32} {output_file_dict[key]}") + + return output_file_dict + + def set_unit_system(self, MU: str | None = None, DU: str | None = None, @@ -1238,7 +1402,7 @@ def save(self, param_file, framenum=-1, codename="Swiftest"): """ if codename == "Swiftest": - io.swiftest_xr2infile(ds=self.ds, param=self.param, in_type=self.param['IN_TYPE'], framenum=framenum,infile_name=self.param['NC_IN']) + io.swiftest_xr2infile(ds=self.ds, param=self.param, in_type=self.param['IN_TYPE'], framenum=framenum) self.write_param(param_file) elif codename == "Swifter": if self.codename == "Swiftest": @@ -1280,25 +1444,29 @@ def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_fil if codename != "Swiftest": self.save(new_param_file, framenum, codename) return + if new_param is None: new_param = self.param.copy() if codename == "Swiftest": if restart: new_param['T0'] = self.ds.time.values[framenum] - if self.param['OUT_TYPE'] == 'NETCDF_DOUBLE' or self.param['OUT_TYPE'] == 'REAL8': + if self.param['OUT_TYPE'] == 'NETCDF_DOUBLE': new_param['IN_TYPE'] = 'NETCDF_DOUBLE' - elif self.param['OUT_TYPE'] == 'NETCDF_FLOAT' or self.param['OUT_TYPE'] == 'REAL4': + elif self.param['OUT_TYPE'] == 'NETCDF_FLOAT': new_param['IN_TYPE'] = 'NETCDF_FLOAT' else: print(f"{self.param['OUT_TYPE']} is an invalid OUT_TYPE file") return + if self.param['BIN_OUT'] != new_param['BIN_OUT'] and restart: - print(f"Restart run with new output file. Copying {self.param['BIN_OUT']} to {new_param['BIN_OUT']}") - shutil.copy2(self.param['BIN_OUT'],new_param['BIN_OUT']) + print(f"Restart run with new output file. Copying {self.param['BIN_OUT']} to {new_param['BIN_OUT']}") + shutil.copy2(self.param['BIN_OUT'],new_param['BIN_OUT']) + new_param['IN_FORM'] = 'XV' if restart: new_param['OUT_STAT'] = 'APPEND' + new_param['FIRSTKICK'] = 'T' new_param['NC_IN'] = new_initial_conditions_file new_param.pop('PL_IN', None) From b4c6604c0b6405bc4ba40b1c3224439843a841ac Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 08:35:38 -0500 Subject: [PATCH 11/75] Improved the unit system setter and added a unit system getter. Added kwargs to all the setters so I can write a main set_parameters setter that can take any possible parameter as an argument. --- python/swiftest/swiftest/simulation_class.py | 166 +++++++++++++++++-- 1 file changed, 149 insertions(+), 17 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 1dba3a49d..f538136ed 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -23,7 +23,8 @@ from typing import ( Literal, Dict, - List + List, + Any ) @@ -48,6 +49,9 @@ def __init__(self, MU2KG: float | None = None, DU2M: float | None = None, TU2S: float | None = None, + MU_name: str | None = None, + DU_name: str | None = None, + TU_name: str | None = None, rmin: float = constants.RSun / constants.AU2M, rmax: float = 10000.0, close_encounter_check: bool = True, @@ -140,6 +144,15 @@ def __init__(self, TU2S : float, optional The conversion factor to multiply by the time unit that would convert it to seconds. Setting this overrides TU + MU_name : str, optional + The name of the mass unit. When setting one of the standard units via `MU` a name will be + automatically set for the unit, so this argument will override the automatic name. + DU_name : str, optional + The name of the distance unit. When setting one of the standard units via `DU` a name will be + automatically set for the unit, so this argument will override the automatic name. + TU_name : str, optional + The name of the time unit. When setting one of the standard units via `TU` a name will be + automatically set for the unit, so this argument will override the automatic name. rmin : float, default value is the radius of the Sun in the unit system defined by the unit input arguments. Minimum distance of the simulation (CHK_QMIN, CHK_RMIN, CHK_QMIN_RANGE[0]) rmax : float, default value is 10000 AU in the unit system defined by the unit input arguments. @@ -212,15 +225,18 @@ def __init__(self, self.verbose = verbose self.restart = restart - self.set_distance_range(rmin=rmin, rmax=rmax) + self.set_distance_range(rmin=rmin, rmax=rmax, verbose = False) self.set_unit_system(MU=MU, DU=DU, TU=TU, MU2KG=MU2KG, DU2M=DU2M, TU2S=TU2S, - recompute_values=True) + MU_name=MU_name, DU_name=DU_name, TU_name=TU_name, + recompute_values=True, + verbose = False) self.set_init_cond_files(init_cond_file_type=init_cond_file_type, init_cond_file_name=init_cond_file_name, - init_cond_format=init_cond_format) + init_cond_format=init_cond_format, + verbose = False) self.set_output_files(output_file_type=output_file_type, output_file_name=output_file_name, @@ -264,6 +280,7 @@ def set_simulation_time(self, tstep_out : float | None = None, istep_dump : int | None = None, verbose : bool | None = None, + **kwargs: Any ): """ @@ -287,6 +304,9 @@ def set_simulation_time(self, `istep_out` (or the equivalent value determined by `tstep_out`) verbose: bool, optional If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -447,6 +467,7 @@ def set_feature(self, restart: bool | None = None, tides: bool | None = None, verbose: bool | None = None, + **kwargs: Any ): """ Turns on or off various features of a simulation. @@ -486,6 +507,9 @@ def set_feature(self, when the run is executed. verbose: bool, optional If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -596,7 +620,8 @@ def set_init_cond_files(self, init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"] | None = None, init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None = None, init_cond_format: Literal["EL", "XV"] | None = None, - verbose: bool | None = None + verbose: bool | None = None, + **kwargs: Any ): """ Sets the initial condition file parameters in the parameters dictionary. @@ -627,6 +652,9 @@ def set_init_cond_files(self, > *Note:* If `codename` is "Swift" or "Swifter", EL initial conditions are converted to XV. verbose: bool, optional If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -771,7 +799,8 @@ def set_output_files(self, output_file_type: Literal[ "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"] | None = None, output_file_name: os.PathLike | str | None = None, output_format: Literal["XV", "XVEL"] | None = None, - verbose: bool | None = None + verbose: bool | None = None, + **kwargs: Any ): """ Sets the output file parameters in the parameters dictionary. @@ -792,6 +821,9 @@ def set_output_files(self, vectors for all bodies are stored. If "XVEL" then the orbital elements are also stored. verbose: bool, optional If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -909,7 +941,12 @@ def set_unit_system(self, MU2KG: float | None = None, DU2M: float | None = None, TU2S: float | None = None, - recompute_values: bool = False): + MU_name: str | None = None, + DU_name: str | None = None, + TU_name: str | None = None, + recompute_values: bool = True, + verbose: bool | None = None, + **kwargs: Any): """ Setter for setting the unit conversion between one of the standard sets. @@ -951,11 +988,25 @@ def set_unit_system(self, TU2S : float, optional The conversion factor to multiply by the time unit that would convert it to seconds. Setting this overrides TU - recompute_values : bool, default False + MU_name : str, optional + The name of the mass unit. When setting one of the standard units via `MU` a name will be + automatically set for the unit, so this argument will override the automatic name. + DU_name : str, optional + The name of the distance unit. When setting one of the standard units via `DU` a name will be + automatically set for the unit, so this argument will override the automatic name. + TU_name : str, optional + The name of the time unit. When setting one of the standard units via `TU` a name will be + automatically set for the unit, so this argument will override the automatic name. + recompute_values : bool, default True Recompute all values into the new unit system. >*Note:* This is a destructive operation, however if not executed then the values contained in the parameter > file and input/output data files computed previously may not be consistent with the new unit conversion > factors. + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ---------- @@ -967,6 +1018,14 @@ def set_unit_system(self, DU2M_old = None TU2S_old = None + update_list = [] + if MU is not None or MU2KG is not None: + update_list.append("MU") + if DU is not None or DU2M is not None: + update_list.append("DU") + if TU is not None or TU2S is not None: + update_list.append("TU") + if MU2KG is not None or MU is not None: MU2KG_old = self.param.pop('MU2KG',None) if MU2KG is not None: @@ -1033,23 +1092,90 @@ def set_unit_system(self, self.param['TU2S'] = constants.YR2S self.TU_name = "y" + + if MU_name is not None: + self.MU_name = MU_name + if DU_name is not None: + self.DU_name = DU_name + if TU_name is not None: + self.TU_name = TU_name + self.VU_name = f"{self.DU_name}/{self.TU_name}" self.GU = constants.GC * self.param['TU2S']**2 * self.param['MU2KG'] / self.param['DU2M']**3 if recompute_values: self.update_param_units(MU2KG_old, DU2M_old, TU2S_old) - if self.verbose: - if self.MU_name is None or self.DU_name is None or self.TU_name is None: - print(f"Custom units set.") - print(f"MU2KG: {self.param['MU2KG']}") - print(f"DU2M : {self.param['DU2M']}") - print(f"TU2S : {self.param['TU2S']}") - else: - print(f"Units set to {self.MU_name}-{self.DU_name}-{self.TU_name}") + if verbose is None: + verbose = self.verbose + + if verbose: + unit_dict = self.get_unit_system(update_list) return + def get_unit_system(self, arg_list: str | List[str] | None = None): + """ + + Returns a subset of the parameter dictionary containing the current simulation unit system. + If the verbose option is set in the Simulation object, then it will also print the values. + + Parameters + ---------- + arg_list : str | List[str], optional + A single string or list of strings containing the names of the simulation unit system + Default is all of: + ["MU", "DU", "TU"] + + Returns + ------- + time_dict : dict + A dictionary containing the requested parameters + + """ + + valid_var = { + "MU": "MU2KG", + "DU": "DU2M", + "TU": "TU2S", + } + + if self.MU_name is None: + MU_name = "mass unit" + else: + MU_name = self.MU_name + if self.DU_name is None: + DU_name = "distance unit" + else: + DU_name = self.DU_name + if self.TU_name is None: + TU_name = "time unit" + else: + TU_name = self.TU_name + + units = { + "MU" : f"kg / {MU_name}", + "DU" : f"m / {DU_name}", + "TU" : f"s / {TU_name}" + } + + + if arg_list is None: + arg_list = valid_var.keys() + elif type(arg_list) is str: + arg_list = [arg_list] + else: + arg_list = [k for k in arg_list if k in set(valid_var.keys())] + + unit_dict = {valid_var[k]: self.param[valid_var[k]] for k in arg_list} + + if self.verbose: + for arg in arg_list: + key = valid_var[arg] + print(f"{arg:<32} {unit_dict[key]} {units[arg]}") + + return unit_dict + def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): """ Updates the values of parameters that have units when the units have changed. @@ -1096,7 +1222,10 @@ def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): return - def set_distance_range(self,rmin=None,rmax=None): + def set_distance_range(self, + rmin: float | None = None, + rmax: float | None = None, + **kwargs: Any): """ Sets the minimum and maximum distances of the simulation. @@ -1106,6 +1235,9 @@ def set_distance_range(self,rmin=None,rmax=None): Minimum distance of the simulation (CHK_QMIN, CHK_RMIN, CHK_QMIN_RANGE[0]) rmax : float Maximum distance of the simulation (CHK_RMAX, CHK_QMIN_RANGE[1]) + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- From 99c76197ab89e463bbbfabc5c38cd1347837fb71 Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 10:57:11 -0500 Subject: [PATCH 12/75] Improved the getters and setters for the initial conditions files --- python/swiftest/swiftest/simulation_class.py | 236 +++++++++++++------ 1 file changed, 161 insertions(+), 75 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index f538136ed..1c2cb3fca 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -211,12 +211,6 @@ def __init__(self, self.ds = xr.Dataset() self.param = { '! VERSION': f"Swiftest parameter input", - 'T0': 0.0, - 'TSTART' : 0.0, - 'TSTOP': 0.0, - 'DT': 0.0, - 'ISTEP_OUT': 1, - 'ISTEP_DUMP': 1, 'CHK_QMIN_COORD': "HELIO", 'INTERACTION_LOOPS': interaction_loops, 'ENCOUNTER_CHECK': encounter_check_loops @@ -436,18 +430,10 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None): else: tstep_out = None - if arg_list is None: - arg_list = valid_var.keys() - elif type(arg_list) is str: - arg_list = [arg_list] - else: - arg_list = [k for k in arg_list if k in set(valid_var.keys())] - - time_dict = {valid_var[k]: self.param[valid_var[k]] for k in arg_list} + valid_arg, time_dict = self._get_valid_arg_list(arg_list, valid_var) if self.verbose: - print("\nSimulation time parameters:") - for arg in arg_list: + for arg in valid_arg: key = valid_var[arg] print(f"{arg:<32} {time_dict[key]} {units[arg]}") if tstep_out is not None: @@ -597,20 +583,10 @@ def get_feature(self, arg_list: str | List[str] | None = None): "restart" : "RESTART" } - if arg_list is None: - arg_list = valid_var.keys() - elif type(arg_list) is str: - arg_list = [arg_list] - else: - # Only allow arg_lists to be checked if they are valid. Otherwise ignore. - arg_list = [k for k in arg_list if k in set(valid_var.keys())] - - # Extract the arg_list dictionary - feature_dict = {valid_var[feat]:self.param[valid_var[feat]] for feat in arg_list} + valid_arg, feature_dict = self._get_valid_arg_list(arg_list, valid_var) if self.verbose: - print("\nSimulation feature parameters:") - for arg in arg_list: + for arg in valid_arg: key = valid_var[arg] print(f"{arg:<32} {feature_dict[key]}") @@ -662,7 +638,9 @@ def set_init_cond_files(self, """ - update_list = ["init_cond_file_name"] + update_list = [] + if init_cond_file_name is not None: + update_list.append("init_cond_file_name") if init_cond_file_type is not None: update_list.append("init_cond_file_type") if init_cond_format is not None: @@ -678,6 +656,18 @@ def ascii_file_input_error_msg(codename): print('}') return + if init_cond_format is None: + if "IN_FORM" in self.param: + init_cond_format = self.param['IN_FORM'] + else: + init_cond_format = "EL" + + if init_cond_file_type is None: + if "IN_TYPE" in self.param: + init_cond_file_type = self.param['IN_TYPE'] + else: + init_cond_file_type = "NETCDF_DOUBLE" + if self.codename == "Swiftest": init_cond_keys = ["CB", "PL", "TP"] else: @@ -689,8 +679,18 @@ def ascii_file_input_error_msg(codename): print(f"{init_cond_format} is not supported by {self.codename}. Using XV instead") init_cond_format = "XV" - self.param["IN_TYPE"] = init_cond_file_type - self.param["IN_FORM"] = init_cond_format + + valid_formats={"EL", "XV"} + if init_cond_format not in valid_formats: + print(f"{init_cond_format} is not a valid input format") + else: + self.param['IN_FORM'] = init_cond_format + + valid_types = {"NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"} + if init_cond_file_type not in valid_types: + print(f"{init_cond_file_type} is not a valid input type") + else: + self.param['IN_TYPE'] = init_cond_file_type if init_cond_file_type == "ASCII": if init_cond_file_name is None: @@ -752,41 +752,48 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None): valid_var = {"init_cond_file_type": "IN_TYPE", "init_cond_format": "IN_FORM", "init_cond_file_name" : "NC_IN", - "init_cond_file_name (cb)" : "CB_IN", - "init_cond_file_name (pl)" : "PL_IN", - "init_cond_file_name (tp)" : "TP_IN", + "init_cond_file_name['CB']" : "CB_IN", + "init_cond_file_name['PL']" : "PL_IN", + "init_cond_file_name['TP']" : "TP_IN", } + three_file_args = ["init_cond_file_name['CB']", + "init_cond_file_name['PL']", + "init_cond_file_name['TP']"] + if self.codename == "Swifter": + three_file_args.remove("init_cond_file_name['CB']") + + # We have to figure out which initial conditions file model we are using (1 vs. 3 files) if arg_list is None: - arg_list = list(valid_var.keys()) + valid_arg = None + else: + valid_arg = arg_list.copy() + + if valid_arg is None: + valid_arg = list(valid_var.keys()) elif type(arg_list) is str: - arg_list = [arg_list] + valid_arg = [arg_list] else: - arg_list = [k for k in arg_list if k in set(valid_var.keys())] + # Only allow arg_lists to be checked if they are valid. Otherwise ignore. + valid_arg = [k for k in arg_list if k in list(valid_var.keys())] - if "init_cond_file_name" in arg_list: + # Figure out which input file model we need to use + if "init_cond_file_name" in valid_arg: if self.param["IN_TYPE"] == "ASCII": - arg_list.remove("init_cond_file_name") - if "init_cond_file_name (cb)" not in arg_list: - arg_list.append("init_cond_file_name (cb)") - if "init_cond_file_name (pl)" not in arg_list: - arg_list.append("init_cond_file_name (pl)") - if "init_cond_file_name (tp)" not in arg_list: - arg_list.append("init_cond_file_name (tp)") + valid_arg.remove("init_cond_file_name") + for key in three_file_args: + if key not in valid_arg: + valid_arg.append(key) else: - if "init_cond_file_name (cb)" in arg_list: - arg_list.remove("init_cond_file_name (cb)") - if "init_cond_file_name (pl)" in arg_list: - arg_list.remove("init_cond_file_name (pl)") - if "init_cond_file_name (tp)" in arg_list: - arg_list.remove("init_cond_file_name (tp)") + for key in three_file_args: + if key in valid_arg: + valid_arg.remove(key) - init_cond_file_dict = {valid_var[k]: self.param[valid_var[k]] for k in arg_list} + valid_arg, init_cond_file_dict = self._get_valid_arg_list(valid_arg, valid_var) if self.verbose: - print("\nInitial condition file parameters:") - for arg in arg_list: + for arg in valid_arg: key = valid_var[arg] print(f"{arg:<32} {init_cond_file_dict[key]}") @@ -794,7 +801,6 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None): - def set_output_files(self, output_file_type: Literal[ "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"] | None = None, output_file_name: os.PathLike | str | None = None, @@ -916,18 +922,10 @@ def get_output_files(self, arg_list: str | List[str] | None = None): "output_format": "OUT_FORM", } - if arg_list is None: - arg_list = valid_var.keys() - elif type(arg_list) is str: - arg_list = [arg_list] - else: - arg_list = [k for k in arg_list if k in set(valid_var.keys())] - - output_file_dict = {valid_var[k]: self.param[valid_var[k]] for k in arg_list} + valid_arg, output_file_dict = self._get_valid_arg_list(arg_list, valid_var) if self.verbose: - print("\nOutput file parameters:") - for arg in arg_list: + for arg in valid_arg: key = valid_var[arg] print(f"{arg:<32} {output_file_dict[key]}") @@ -1159,18 +1157,10 @@ def get_unit_system(self, arg_list: str | List[str] | None = None): "TU" : f"s / {TU_name}" } - - if arg_list is None: - arg_list = valid_var.keys() - elif type(arg_list) is str: - arg_list = [arg_list] - else: - arg_list = [k for k in arg_list if k in set(valid_var.keys())] - - unit_dict = {valid_var[k]: self.param[valid_var[k]] for k in arg_list} + valid_arg, unit_dict = self._get_valid_arg_list(arg_list, valid_var) if self.verbose: - for arg in arg_list: + for arg in valid_arg: key = valid_var[arg] print(f"{arg:<32} {unit_dict[key]} {units[arg]}") @@ -1262,6 +1252,102 @@ def set_distance_range(self, return + def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var: Dict | None = None): + """ + Internal function for getters that extracts subset of arguments that is contained in the dictionary of valid + argument/parameter variable pairs. + + Parameters + ---------- + arg_list : str | List[str], optional + A single string or list of strings containing the Simulation argument. If none are supplied, + then it will create the arg_list out of all keys in the valid_var dictionary. + valid_var : valid_var: Dict + A dictionary where the key is the argument name and the value is the equivalent key in the Simulation + parameter dictionary (i.e. the left-hand column of a param.in file) + + Returns + ------- + valid_arg : [str] + The list of valid arguments that match the keys in valid_var + param : dict + A dictionary that is the subset of the Simulation parameter dictionary corresponding to the arguments listed + in arg_list. + + """ + + + if arg_list is None: + valid_arg = None + else: + valid_arg = arg_list.copy() + + if valid_arg is None: + valid_arg = list(valid_var.keys()) + elif type(arg_list) is str: + valid_arg = [arg_list] + else: + # Only allow arg_lists to be checked if they are valid. Otherwise ignore. + valid_arg = [k for k in arg_list if k in list(valid_var.keys())] + + # Extract the arg_list dictionary + param = {valid_var[feat]:self.param[valid_var[feat]] for feat in valid_arg} + + return valid_arg, param + + def get_distance_range(self, arg_list: str | List[str] | None = None): + """ + + Returns a subset of the parameter dictionary containing the current values of the distance range parameters. + If the verbose option is set in the Simulation object, then it will also print the values. + + Parameters + ---------- + arg_list: str | List[str], optional + A single string or list of strings containing the names of the features to extract. Default is all of: + ["rmin", "rmax"] + + Returns + ------- + range_dict : dict + A dictionary containing the requested parameters. + + """ + + valid_var = {"rmin": "CHK_RMIN", + "rmax": "CHK_RMAX", + "qmin": "CHK_QMIN", + "qminR" : "CHK_QMIN_RANGE" + } + + units = {"rmin" : self.DU_name, + "rmax" : self.DU_name, + "qmin" : self.DU_name, + "qminR" : self.DU_name, + } + + if "rmin" in arg_list: + arg_list.append("qmin") + if "rmax" in arg_list or "rmin" in arg_list: + arg_list.append("qminR") + + valid_arg, range_dict = self._get_valid_arg_list(arg_list, valid_var) + + if self.verbose: + if "rmin" in valid_arg: + key = valid_arg["rmin"] + print(f"{'rmin':<32} {range_dict[key]} {units['rmin']}") + key = valid_arg["qmin"] + print(f"{'':<32} {range_dict[key]} {units['qmin']}") + if "rmax" in valid_arg: + key = valid_arg["rmax"] + print(f"{'rmax':<32} {range_dict[key]} {units['rmax']}") + if "rmax" in valid_arg or "rmin" in valid_arg: + key = valid_arg["qminR"] + print(f"{'':<32} {range_dict[key]} {units['qminR']}") + + + return range_dict def add(self, plname, date=date.today().isoformat(), idval=None): From 41c61b1aad2a1171c29db59818ab085a7089ff3c Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 11:06:01 -0500 Subject: [PATCH 13/75] Prints the tstep_out value whenever istep_out is given as an argument to get_simulation_time --- python/swiftest/swiftest/simulation_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 1c2cb3fca..c2c557be2 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -423,7 +423,7 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None): "istep_out" : "", "istep_dump" : ""} - if arg_list is not None and "tstep_out" in arg_list: + if arg_list is None or "tstep_out" in arg_list or "istep_out" in arg_list: istep_out = self.param['ISTEP_OUT'] dt = self.param['DT'] tstep_out = istep_out * dt From bd9bfad2a3b7026e609105a74faa56326da6aa14 Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 11:16:03 -0500 Subject: [PATCH 14/75] Fixed unit conversion bug and added simulation time parameters to Simulation class argument list --- python/swiftest/swiftest/simulation_class.py | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index c2c557be2..174d5e782 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -1,5 +1,4 @@ """ - self.param['BIN_OUT'] = binpath Copyright 2022 - David Minton, Carlisle Wishard, Jennifer Pouplin, Jake Elliott, & Dana Singh This file is part of Swiftest. Swiftest is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -36,6 +35,13 @@ def __init__(self, codename: Literal["Swiftest", "Swifter", "Swift"] = "Swiftest", param_file: os.PathLike | str ="param.in", read_param: bool = False, + t0: float = 0.0, + tstart: float = 0.0, + tstop: float = 1.0, + dt: float = 0.002, + istep_out: int = 50, + tstep_out: float | None = None, + istep_dump: int = 50, init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"] = "NETCDF_DOUBLE", init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None = None, init_cond_format: Literal["EL", "XV"] = "EL", @@ -248,6 +254,16 @@ def __init__(self, restart=restart, verbose = False) + self.set_simulation_time(t0=t0, + tstart=tstart, + tstop=tstop, + dt=dt, + tstep_out=tstep_out, + istep_out=istep_out, + istep_dump=istep_dump, + verbose = False + ) + # If the parameter file is in a different location than the current working directory, we will need # to use it to properly open bin files self.sim_dir = os.path.dirname(os.path.realpath(param_file)) @@ -1190,12 +1206,12 @@ def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): for k in mass_keys: if k in self.param: print(f"param['{k}']: {self.param[k]}") - self.param[k] *= self.param['MU2KG'] / MU2KG_old + self.param[k] *= MU2KG_old / self.param['MU2KG'] if DU2M_old is not None: for k in distance_keys: if k in self.param: - self.param[k] *= self.param['DU2M'] / DU2M_old + self.param[k] *= DU2M_old / self.param['DU2M'] CHK_QMIN_RANGE = self.param.pop('CHK_QMIN_RANGE', None) if CHK_QMIN_RANGE is not None: @@ -1207,7 +1223,7 @@ def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): if TU2S_old is not None: for k in time_keys: if k in self.param: - self.param[k] *= self.param['TU2S'] / TU2S_old + self.param[k] *= TU2S_old / self.param['TU2S'] return From 50f626187326682f9925eb4dc22654514a9376ff Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 11:46:41 -0500 Subject: [PATCH 15/75] Improved the handling of unset parameters and removed the arbitrary default values for tstop, dt, istep_out, and istep_dump. --- python/swiftest/swiftest/simulation_class.py | 89 ++++++++++++-------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 174d5e782..917165bd1 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -26,7 +26,6 @@ Any ) - class Simulation: """ This is a class that defines the basic Swift/Swifter/Swiftest simulation object @@ -37,11 +36,11 @@ def __init__(self, read_param: bool = False, t0: float = 0.0, tstart: float = 0.0, - tstop: float = 1.0, - dt: float = 0.002, - istep_out: int = 50, + tstop: float | None = None, + dt: float | None = None, + istep_out: int | None = None, tstep_out: float | None = None, - istep_dump: int = 50, + istep_dump: int | None = None, init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"] = "NETCDF_DOUBLE", init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None = None, init_cond_format: Literal["EL", "XV"] = "EL", @@ -86,6 +85,22 @@ def __init__(self, a new parameter file using the arguments passed to Simulation. > *Note:* If set to true, the parameters defined in the input file will override any passed into the > arguments of Simulation. + t0 : float, default 0.0 + The reference time for the start of the simulation. Defaults is 0.0 + tstart : float, default 0.0 + The start time for a restarted simulation. For a new simulation, tstart will be set to t0 automatically. + tstop : float, optional + The stopping time for a simulation. `tstop` must be greater than `tstart`. + dt : float, optional + The step size of the simulation. `dt` must be less than or equal to `tstop-dstart`. + istep_out : int, optional + The number of time steps between outputs to file. *Note*: only `istep_out` or `toutput` can be set. + tstep_out : float, optional + The approximate time between when outputs are written to file. Passing this computes + `istep_out = floor(tstep_out/dt)`. *Note*: only `istep_out` or `toutput` can be set. + istep_dump : int, optional + The anumber of time steps between outputs to dump file. If not set, this will be set to the value of + `istep_out` (or the equivalent value determined by `tstep_out`) init_cond_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"}, default "NETCDF_DOUBLE" The file type containing initial conditions for the simulation: * NETCDF_DOUBLE: A single initial conditions input file in NetCDF file format of type NETCDF_DOUBLE @@ -346,54 +361,54 @@ def set_simulation_time(self, if tstop is None: tstop = self.param.pop("TSTOP", None) - if tstop is None: - print("Error! tstop is not set!") - return else: update_list.append("tstop") - if tstop <= tstart: - print("Error! tstop must be greater than tstart.") - return + if tstop is not None: + if tstop <= tstart: + print("Error! tstop must be greater than tstart.") + return - self.param['TSTOP'] = tstop + if tstop is not None: + self.param['TSTOP'] = tstop if dt is None: dt = self.param.pop("DT", None) - if dt is None: - print("Error! dt is not set!") - return else: update_list.append("dt") - if dt > (tstop - tstart): - print("Error! dt must be smaller than tstop-tstart") - print(f"Setting dt = {tstop-tstart} instead of {dt}") - dt = tstop - tstart + if dt is not None and tstop is not None: + if dt > (tstop - tstart): + print("Error! dt must be smaller than tstop-tstart") + print(f"Setting dt = {tstop-tstart} instead of {dt}") + dt = tstop - tstart - self.param['DT'] = dt + if dt is not None: + self.param['DT'] = dt if istep_out is None and tstep_out is None: istep_out = self.param.pop("ISTEP_OUT", None) - if istep_out is None: - print("Error! istep_out is not set") - return if istep_out is not None and tstep_out is not None: print("Error! istep_out and tstep_out cannot both be set") return - if tstep_out is not None: + if tstep_out is not None and dt is not None: istep_out = int(np.floor(tstep_out / dt)) - self.param['ISTEP_OUT'] = istep_out + if istep_out is not None: + self.param['ISTEP_OUT'] = istep_out + update_list.append("istep_out") if istep_dump is None: - istep_dump = istep_out + istep_dump = self.param.pop("ISTEP_DUMP",None) + if istep_dump is None: + istep_dump = istep_out - self.param['ISTEP_DUMP'] = istep_dump + if istep_dump is not None: + self.param['ISTEP_DUMP'] = istep_dump + update_list.append("istep_dump") - update_list.extend(["istep_out", "tstep_out", "istep_dump"]) if verbose is None: verbose = self.verbose @@ -439,19 +454,22 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None): "istep_out" : "", "istep_dump" : ""} + tstep_out = None if arg_list is None or "tstep_out" in arg_list or "istep_out" in arg_list: - istep_out = self.param['ISTEP_OUT'] - dt = self.param['DT'] - tstep_out = istep_out * dt - else: - tstep_out = None + if "ISTEP_OUT" in self.param and "DT" in self.param: + istep_out = self.param['ISTEP_OUT'] + dt = self.param['DT'] + tstep_out = istep_out * dt valid_arg, time_dict = self._get_valid_arg_list(arg_list, valid_var) if self.verbose: for arg in valid_arg: key = valid_var[arg] - print(f"{arg:<32} {time_dict[key]} {units[arg]}") + if key in time_dict: + print(f"{arg:<32} {time_dict[key]} {units[arg]}") + else: + print(f"{arg:<32} NOT SET") if tstep_out is not None: print(f"{'tstep_out':<32} {tstep_out} {units['tstep_out']}") @@ -1292,7 +1310,6 @@ def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var """ - if arg_list is None: valid_arg = None else: @@ -1307,7 +1324,7 @@ def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var valid_arg = [k for k in arg_list if k in list(valid_var.keys())] # Extract the arg_list dictionary - param = {valid_var[feat]:self.param[valid_var[feat]] for feat in valid_arg} + param = {valid_var[arg]:self.param[valid_var[arg]] for arg in valid_arg if valid_var[arg] in self.param} return valid_arg, param From 99f8c8b7a627062c4b86ddfebbb141ef3fbe2e47 Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 11:52:09 -0500 Subject: [PATCH 16/75] Improved display of units in get_unit_system --- python/swiftest/swiftest/simulation_class.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 917165bd1..21e090747 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -1115,7 +1115,7 @@ def set_unit_system(self, self.TU_name = "y" elif TU.upper() == "DAY" or TU.upper() == "D" or TU.upper() == "JD" or TU.upper() == "DAYS": self.param['TU2S'] = constants.JD2S - self.TU_name = "Day" + self.TU_name = "d" elif TU.upper() == "S" or TU.upper() == "SECONDS" or TU.upper() == "SEC": self.param['TU2S'] = 1.0 self.TU_name = "s" @@ -1185,7 +1185,12 @@ def get_unit_system(self, arg_list: str | List[str] | None = None): else: TU_name = self.TU_name - units = { + units1 = { + "MU" : MU_name, + "DU" : DU_name, + "TU" : TU_name + } + units2 = { "MU" : f"kg / {MU_name}", "DU" : f"m / {DU_name}", "TU" : f"s / {TU_name}" @@ -1196,7 +1201,7 @@ def get_unit_system(self, arg_list: str | List[str] | None = None): if self.verbose: for arg in valid_arg: key = valid_var[arg] - print(f"{arg:<32} {unit_dict[key]} {units[arg]}") + print(f"{arg}: {units1[arg]:<28} {unit_dict[key]} {units2[arg]}") return unit_dict From 82cbe5357990818a4d26f934bbed767212c04c86 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Wed, 9 Nov 2022 12:54:53 -0500 Subject: [PATCH 17/75] reordered calls to nf90_inq_varid in netcdf.f90 --- src/netcdf/netcdf.f90 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 0cd27f737..0c444669a 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -366,12 +366,10 @@ module subroutine netcdf_open(self, param, readonly) call check( nf90_inq_varid(self%ncid, TIME_DIMNAME, self%time_varid), "netcdf_open nf90_inq_varid time_varid" ) call check( nf90_inq_varid(self%ncid, ID_DIMNAME, self%id_varid), "netcdf_open nf90_inq_varid id_varid" ) - call check( nf90_inq_varid(self%ncid, NPL_VARNAME, self%npl_varid), "netcdf_open nf90_inq_varid npl_varid" ) - call check( nf90_inq_varid(self%ncid, NTP_VARNAME, self%ntp_varid), "netcdf_open nf90_inq_varid ntp_varid" ) - if (param%integrator == SYMBA) call check( nf90_inq_varid(self%ncid, NPLM_VARNAME, self%nplm_varid), "netcdf_open nf90_inq_varid nplm_varid" ) call check( nf90_inq_varid(self%ncid, NAME_VARNAME, self%name_varid), "netcdf_open nf90_inq_varid name_varid" ) call check( nf90_inq_varid(self%ncid, PTYPE_VARNAME, self%ptype_varid), "netcdf_open nf90_inq_varid ptype_varid" ) call check( nf90_inq_varid(self%ncid, STATUS_VARNAME, self%status_varid), "netcdf_open nf90_inq_varid status_varid" ) + call check( nf90_inq_varid(self%ncid, GMASS_VARNAME, self%Gmass_varid), "netcdf_open nf90_inq_varid Gmass_varid" ) if ((param%out_form == XV) .or. (param%out_form == XVEL)) then call check( nf90_inq_varid(self%ncid, XHX_VARNAME, self%xhx_varid), "netcdf_open nf90_inq_varid xhx_varid" ) @@ -409,7 +407,6 @@ module subroutine netcdf_open(self, param, readonly) call check( nf90_inq_varid(self%ncid, OMEGA_VARNAME, self%omega_varid), "netcdf_open nf90_inq_varid omega_varid" ) call check( nf90_inq_varid(self%ncid, CAPM_VARNAME, self%capm_varid), "netcdf_open nf90_inq_varid capm_varid" ) end if - call check( nf90_inq_varid(self%ncid, GMASS_VARNAME, self%Gmass_varid), "netcdf_open nf90_inq_varid Gmass_varid" ) if (param%lrhill_present) call check( nf90_inq_varid(self%ncid, RHILL_VARNAME, self%rhill_varid), "netcdf_open nf90_inq_varid rhill_varid" ) From 516ae33973372d55b760c21edb4ea1020155b6ec Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Wed, 9 Nov 2022 12:55:29 -0500 Subject: [PATCH 18/75] commented out readonly bits so that new variables can be added --- src/netcdf/netcdf.f90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 0c444669a..9f2e9fba0 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -353,9 +353,9 @@ module subroutine netcdf_open(self, param, readonly) character(len=NF90_MAX_NAME) :: str_dim_name mode = NF90_WRITE - if (present(readonly)) then - if (readonly) mode = NF90_NOWRITE - end if + !if (present(readonly)) then + ! if (readonly) mode = NF90_NOWRITE + !end if call check( nf90_open(param%outfile, mode, self%ncid), "netcdf_open nf90_open" ) From 0a859905fd364825e9b7dddc2df9be6b3f4f9306 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Wed, 9 Nov 2022 12:56:27 -0500 Subject: [PATCH 19/75] check if npl, ntp, or nplm exist and if not, calculate and define them --- src/netcdf/netcdf.f90 | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 9f2e9fba0..e66498c35 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -351,6 +351,9 @@ module subroutine netcdf_open(self, param, readonly) ! Internals integer(I4B) :: mode, status character(len=NF90_MAX_NAME) :: str_dim_name + integer(I4B) :: idmax + real(DP), dimension(:), allocatable :: gmtemp + logical, dimension(:), allocatable :: tpmask, plmask, plmmask mode = NF90_WRITE !if (present(readonly)) then @@ -371,6 +374,45 @@ module subroutine netcdf_open(self, param, readonly) call check( nf90_inq_varid(self%ncid, STATUS_VARNAME, self%status_varid), "netcdf_open nf90_inq_varid status_varid" ) call check( nf90_inq_varid(self%ncid, GMASS_VARNAME, self%Gmass_varid), "netcdf_open nf90_inq_varid Gmass_varid" ) + if ((nf90_inq_varid(self%ncid, NPL_VARNAME, self%npl_varid) /= nf90_noerr) .or. & + (nf90_inq_varid(self%ncid, NTP_VARNAME, self%ntp_varid) /= nf90_noerr) .or. & + ((nf90_inq_varid(self%ncid, NPLM_VARNAME, self%nplm_varid) /= nf90_noerr) .and. (param%integrator == SYMBA))) then + call check( nf90_inquire_dimension(self%ncid, self%id_dimid, len=idmax), "netcdf_open nf90_inquire_dimension id_dimid" ) + allocate(gmtemp(idmax)) + call check( nf90_get_var(self%ncid, self%Gmass_varid, gmtemp, start=[1,1]), "netcdf_open nf90_getvar Gmass_varid" ) + allocate(tpmask(idmax)) + allocate(plmask(idmax)) + allocate(plmmask(idmax)) + plmask(:) = gmtemp(:) == gmtemp(:) + tpmask(:) = .not. plmask(:) + plmask(1) = .false. ! This is the central body + select type (param) + class is (symba_parameters) + plmmask(:) = gmtemp(:) > param%GMTINY .and. plmask(:) + end select + if ((nf90_inq_varid(self%ncid, NPL_VARNAME, self%npl_varid) /= nf90_noerr)) then + call check( nf90_redef(self%ncid), "netcdf_open nf90_redef npl_varid") + call check( nf90_def_var(self%ncid, NPL_VARNAME, NF90_INT, self%time_dimid, self%npl_varid), "netcdf_open nf90_def_var npl_varid" ) + call check( nf90_enddef(self%ncid), "netcdf_open nf90_enddef npl_varid") + call check( nf90_put_var(self%ncid, self%npl_varid, count(plmask(:)), start=[1]), "netcdf_open nf90_put_var npl_varid" ) + call check( nf90_inq_varid(self%ncid, NPL_VARNAME, self%npl_varid), "netcdf_open nf90_inq_varid npl_varid" ) + end if + if (nf90_inq_varid(self%ncid, NTP_VARNAME, self%ntp_varid) /= nf90_noerr) then + call check( nf90_redef(self%ncid), "netcdf_open nf90_redef ntp_varid") + call check( nf90_def_var(self%ncid, NTP_VARNAME, NF90_INT, self%time_dimid, self%ntp_varid), "netcdf_open nf90_def_var ntp_varid" ) + call check( nf90_enddef(self%ncid), "netcdf_open nf90_enddef ntp_varid") + call check( nf90_put_var(self%ncid, self%ntp_varid, count(tpmask(:)), start=[1]), "netcdf_open nf90_put_var ntp_varid" ) + call check( nf90_inq_varid(self%ncid, NTP_VARNAME, self%ntp_varid), "netcdf_open nf90_inq_varid ntp_varid" ) + end if + if ((nf90_inq_varid(self%ncid, NPLM_VARNAME, self%nplm_varid) /= nf90_noerr) .and. (param%integrator == SYMBA)) then + call check( nf90_redef(self%ncid), "netcdf_open nf90_redef nplm_varid") + call check( nf90_def_var(self%ncid, NPLM_VARNAME, NF90_INT, self%time_dimid, self%nplm_varid), "netcdf_open nf90_def_var nplm_varid" ) + call check( nf90_enddef(self%ncid), "netcdf_open nf90_enddef nplm_varid") + call check( nf90_put_var(self%ncid, self%nplm_varid, count(plmmask(:)), start=[1]), "netcdf_open nf90_put_var nplm_varid" ) + call check( nf90_inq_varid(self%ncid, NPLM_VARNAME, self%nplm_varid), "netcdf_open nf90_inq_varid nplm_varid" ) + end if + end if + if ((param%out_form == XV) .or. (param%out_form == XVEL)) then call check( nf90_inq_varid(self%ncid, XHX_VARNAME, self%xhx_varid), "netcdf_open nf90_inq_varid xhx_varid" ) call check( nf90_inq_varid(self%ncid, XHY_VARNAME, self%xhy_varid), "netcdf_open nf90_inq_varid xhy_varid" ) From 1b962e262681385d8dc4430ae8d844b3ffa1b8c7 Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 14:44:45 -0500 Subject: [PATCH 20/75] Fixed typos in getter methods and improved the handling of rmin and rmax reporting in the get_distance_range method --- python/swiftest/swiftest/simulation_class.py | 47 ++++++++++++-------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 21e090747..b72542aaa 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -475,6 +475,26 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None): return time_dict + def set_parameters(self, **kwargs): + """ + Setter for all possible parameters. Calls each of the specialized setters using keyword arguments + Parameters + ---------- + **kwargs : [TODO: write this documentation] + + Returns + ------- + param : A dictionary of all Simulation parameters that changed + + """ + self.set_unit_system(**kwargs) + self.set_distance_range(**kwargs) + self.set_feature(**kwargs) + self.set_init_cond_files(**kwargs) + self.set_output_files(**kwargs) + self.set_simulation_time(**kwargs) + + def set_feature(self, close_encounter_check: bool | None = None, general_relativity: bool | None = None, @@ -800,16 +820,10 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None): # We have to figure out which initial conditions file model we are using (1 vs. 3 files) if arg_list is None: - valid_arg = None - else: - valid_arg = arg_list.copy() - - if valid_arg is None: valid_arg = list(valid_var.keys()) elif type(arg_list) is str: valid_arg = [arg_list] else: - # Only allow arg_lists to be checked if they are valid. Otherwise ignore. valid_arg = [k for k in arg_list if k in list(valid_var.keys())] # Figure out which input file model we need to use @@ -1364,26 +1378,23 @@ def get_distance_range(self, arg_list: str | List[str] | None = None): "qminR" : self.DU_name, } - if "rmin" in arg_list: - arg_list.append("qmin") - if "rmax" in arg_list or "rmin" in arg_list: - arg_list.append("qminR") + if type(arg_list) is str: + arg_list = [arg_list] + if arg_list is not None: + if "rmin" in arg_list: + arg_list.append("qmin") + if "rmax" in arg_list or "rmin" in arg_list: + arg_list.append("qminR") valid_arg, range_dict = self._get_valid_arg_list(arg_list, valid_var) if self.verbose: if "rmin" in valid_arg: - key = valid_arg["rmin"] + key = valid_var["rmin"] print(f"{'rmin':<32} {range_dict[key]} {units['rmin']}") - key = valid_arg["qmin"] - print(f"{'':<32} {range_dict[key]} {units['qmin']}") if "rmax" in valid_arg: - key = valid_arg["rmax"] + key = valid_var["rmax"] print(f"{'rmax':<32} {range_dict[key]} {units['rmax']}") - if "rmax" in valid_arg or "rmin" in valid_arg: - key = valid_arg["qminR"] - print(f"{'':<32} {range_dict[key]} {units['qminR']}") - return range_dict From d592f0e6a1d8f5ae30823276d9408beaf463ab79 Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 17:08:44 -0500 Subject: [PATCH 21/75] Made improvements to type handling by the Swiftest xarray methods --- python/swiftest/swiftest/init_cond.py | 1 + python/swiftest/swiftest/io.py | 80 ++++++++------------ python/swiftest/swiftest/simulation_class.py | 5 ++ 3 files changed, 38 insertions(+), 48 deletions(-) diff --git a/python/swiftest/swiftest/init_cond.py b/python/swiftest/swiftest/init_cond.py index 0166e78a8..bf2720a3c 100644 --- a/python/swiftest/swiftest/init_cond.py +++ b/python/swiftest/swiftest/init_cond.py @@ -417,4 +417,5 @@ def vec2xr(param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, ds_float = da_float.to_dataset(dim="vec") ds_str = da_str.to_dataset(dim="vec") ds = xr.combine_by_coords([ds_float, ds_str,info_ds]) + return ds \ No newline at end of file diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 28303f8f8..dc184b175 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -49,6 +49,7 @@ # This defines Xarray Dataset variables that are strings, which must be processed due to quirks in how NetCDF-Fortran # handles strings differently than Python's Xarray. string_varnames = ["name", "particle_type", "status", "origin_type"] +int_varnames = ["id", "ntp", "npl", "nplm", "discard_body_id", "collision_id"] def bool2yesno(boolval): """ @@ -847,54 +848,14 @@ def swiftest2xr(param, verbose=True): ------- xarray dataset """ - if ((param['OUT_TYPE'] == 'REAL8') or (param['OUT_TYPE'] == 'REAL4')): - dims = ['time', 'id', 'vec'] - cb = [] - pl = [] - tp = [] - cbn = None - try: - with FortranFile(param['BIN_OUT'], 'r') as f: - for t, cbid, cbnames, cvec, clab, \ - npl, plid, plnames, pvec, plab, \ - ntp, tpid, tpnames, tvec, tlab in swiftest_stream(f, param): - # Prepare frames by adding an extra axis for the time coordinate - cbframe = np.expand_dims(cvec, axis=0) - plframe = np.expand_dims(pvec, axis=0) - tpframe = np.expand_dims(tvec, axis=0) - - - # Create xarray DataArrays out of each body type - cbxr = xr.DataArray(cbframe, dims=dims, coords={'time': t, 'id': cbid, 'vec': clab}) - cbxr = cbxr.assign_coords(name=("id", cbnames)) - plxr = xr.DataArray(plframe, dims=dims, coords={'time': t, 'id': plid, 'vec': plab}) - plxr = plxr.assign_coords(name=("id", plnames)) - tpxr = xr.DataArray(tpframe, dims=dims, coords={'time': t, 'id': tpid, 'vec': tlab}) - tpxr = tpxr.assign_coords(name=("id", tpnames)) - - cb.append(cbxr) - pl.append(plxr) - tp.append(tpxr) - - sys.stdout.write('\r' + f"Reading in time {t[0]:.3e}") - sys.stdout.flush() - except IOError: - print(f"Error encountered reading in {param['BIN_OUT']}") - - cbda = xr.concat(cb, dim='time') - plda = xr.concat(pl, dim='time') - tpda = xr.concat(tp, dim='time') - - cbds = cbda.to_dataset(dim='vec') - plds = plda.to_dataset(dim='vec') - tpds = tpda.to_dataset(dim='vec') - if verbose: print('\nCreating Dataset') - ds = xr.combine_by_coords([cbds, plds, tpds]) - elif ((param['OUT_TYPE'] == 'NETCDF_DOUBLE') or (param['OUT_TYPE'] == 'NETCDF_FLOAT')): + if ((param['OUT_TYPE'] == 'NETCDF_DOUBLE') or (param['OUT_TYPE'] == 'NETCDF_FLOAT')): if verbose: print('\nCreating Dataset from NetCDF file') ds = xr.open_dataset(param['BIN_OUT'], mask_and_scale=False) - ds = clean_string_values(ds) + if param['OUT_TYPE'] == "NETCDF_DOUBLE": + ds = fix_types(ds,ftype=np.float64) + elif param['OUT_TYPE'] == "NETCDF_FLOAT": + ds = fix_types(ds,ftype=np.float32) else: print(f"Error encountered. OUT_TYPE {param['OUT_TYPE']} not recognized.") return None @@ -931,7 +892,7 @@ def string_converter(da): """ if da.dtype == np.dtype(object): da = da.astype(' Date: Wed, 9 Nov 2022 17:57:07 -0500 Subject: [PATCH 22/75] Restructured when the param file is read in and fixed typo in bool2str --- examples/Basic_Simulation/param.in | 46 ----------------- python/swiftest/swiftest/io.py | 52 +++++++++----------- python/swiftest/swiftest/simulation_class.py | 21 ++++---- 3 files changed, 34 insertions(+), 85 deletions(-) delete mode 100644 examples/Basic_Simulation/param.in diff --git a/examples/Basic_Simulation/param.in b/examples/Basic_Simulation/param.in deleted file mode 100644 index bec3573de..000000000 --- a/examples/Basic_Simulation/param.in +++ /dev/null @@ -1,46 +0,0 @@ -!! Copyright 2022 - David Minton, Carlisle Wishard, Jennifer Pouplin, Jake Elliott, & Dana Singh -!! This file is part of Swiftest. -!! Swiftest is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License -!! as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -!! Swiftest is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty -!! of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -!! You should have received a copy of the GNU General Public License along with Swiftest. -!! If not, see: https://www.gnu.org/licenses. - -! VERSION Swiftest parameter input -T0 0.0 -TSTOP 10 -DT 0.005 -ISTEP_OUT 200 -ISTEP_DUMP 200 -OUT_FORM XVEL -OUT_TYPE NETCDF_DOUBLE -OUT_STAT REPLACE -IN_TYPE ASCII -PL_IN pl.in -TP_IN tp.in -CB_IN cb.in -BIN_OUT out.nc -CHK_QMIN 0.004650467260962157 -CHK_RMIN 0.004650467260962157 -CHK_RMAX 1000.0 -CHK_EJECT 1000.0 -CHK_QMIN_COORD HELIO -CHK_QMIN_RANGE 0.004650467260962157 1000.0 -MU2KG 1.988409870698051e+30 -TU2S 31557600.0 -DU2M 149597870700.0 -IN_FORM EL -EXTRA_FORCE NO -BIG_DISCARD NO -CHK_CLOSE YES -RHILL_PRESENT YES -FRAGMENTATION YES -ROTATION YES -TIDES NO -ENERGY YES -GR YES -INTERACTION_LOOPS ADAPTIVE -ENCOUNTER_CHECK ADAPTIVE -GMTINY 1e-06 -MIN_GMFRAG 1e-09 diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index dc184b175..50c84751f 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -46,6 +46,12 @@ "YARKOVSKY", "YORP"] +int_param = ["ISTEP_OUT", "ISTEP_DUMP"] +float_param = ["T0", "TSTART", "TSTOP", "DT", "CHK_RMIN", "CHK_RMAX", "CHK_EJECT", "CHK_QMIN", "DU2M", "MU2KG", + "TU2S", "MIN_GMFRAG", "GMTINY"] + +upper_str_param = ["OUT_TYPE","OUT_FORM","OUT_STAT","IN_TYPE","IN_FORM"] + # This defines Xarray Dataset variables that are strings, which must be processed due to quirks in how NetCDF-Fortran # handles strings differently than Python's Xarray. string_varnames = ["name", "particle_type", "status", "origin_type"] @@ -107,10 +113,10 @@ def str2bool(input_str): valid_false = ["NO", "N", "F", "FALSE", ".FALSE."] if input_str.upper() in valid_true: return True - elif input_str.lower() in valid_false: + elif input_str.upper() in valid_false: return False else: - raise ValueError(f"{input_str} cannot is not recognized as boolean") + raise ValueError(f"{input_str} is not recognized as boolean") @@ -147,7 +153,8 @@ def read_swiftest_param(param_file_name, param, verbose=True): A dictionary containing the entries in the user parameter file """ param['! VERSION'] = f"Swiftest parameter input from file {param_file_name}" - + + # Read param.in file if verbose: print(f'Reading Swiftest file {param_file_name}') try: @@ -158,38 +165,23 @@ def read_swiftest_param(param_file_name, param, verbose=True): if fields[0][0] != '!': key = fields[0].upper() param[key] = fields[1] - #for key in param: - # if (key == fields[0].upper()): param[key] = fields[1] # Special case of CHK_QMIN_RANGE requires a second input if fields[0].upper() == 'CHK_QMIN_RANGE': alo = real2float(fields[1]) ahi = real2float(fields[2]) param['CHK_QMIN_RANGE'] = f"{alo} {ahi}" - - param['ISTEP_OUT'] = int(param['ISTEP_OUT']) - param['ISTEP_DUMP'] = int(param['ISTEP_DUMP']) - param['OUT_TYPE'] = param['OUT_TYPE'].upper() - param['OUT_FORM'] = param['OUT_FORM'].upper() - param['OUT_STAT'] = param['OUT_STAT'].upper() - param['IN_TYPE'] = param['IN_TYPE'].upper() - param['IN_FORM'] = param['IN_FORM'].upper() - param['T0'] = real2float(param['T0']) - param['TSTART'] = real2float(param['TSTART']) - param['TSTOP'] = real2float(param['TSTOP']) - param['DT'] = real2float(param['DT']) - param['CHK_RMIN'] = real2float(param['CHK_RMIN']) - param['CHK_RMAX'] = real2float(param['CHK_RMAX']) - param['CHK_EJECT'] = real2float(param['CHK_EJECT']) - param['CHK_QMIN'] = real2float(param['CHK_QMIN']) - param['DU2M'] = real2float(param['DU2M']) - param['MU2KG'] = real2float(param['MU2KG']) - param['TU2S'] = real2float(param['TU2S']) - param['INTERACTION_LOOPS'] = param['INTERACTION_LOOPS'].upper() - param['ENCOUNTER_CHECK'] = param['ENCOUNTER_CHECK'].upper() - if 'GMTINY' in param: - param['GMTINY'] = real2float(param['GMTINY']) - if 'MIN_GMFRAG' in param: - param['MIN_GMFRAG'] = real2float(param['MIN_GMFRAG']) + + for uc in upper_str_param: + if uc in param: + param[uc] = param[uc].upper() + + for i in int_param: + if i in param and type(i) != int: + param[i] = int(param[i]) + + for f in float_param: + if f in param and type(f) is str: + param[f] = real2float(param[f]) for b in bool_param: if b in param: param[b] = str2bool(param[b]) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index a7f7f7a6d..a7b8ed27c 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -240,6 +240,15 @@ def __init__(self, self.verbose = verbose self.restart = restart + # If the parameter file is in a different location than the current working directory, we will need + # to use it to properly open bin files + self.sim_dir = os.path.dirname(os.path.realpath(param_file)) + if read_param: + if os.path.exists(param_file): + self.read_param(param_file, codename=codename, verbose=self.verbose) + else: + print(f"{param_file} not found.") + self.set_distance_range(rmin=rmin, rmax=rmax, verbose = False) self.set_unit_system(MU=MU, DU=DU, TU=TU, @@ -279,14 +288,7 @@ def __init__(self, verbose = False ) - # If the parameter file is in a different location than the current working directory, we will need - # to use it to properly open bin files - self.sim_dir = os.path.dirname(os.path.realpath(param_file)) - if read_param: - if os.path.exists(param_file): - self.read_param(param_file, codename=codename, verbose=self.verbose) - else: - print(f"{param_file} not found.") + if read_old_output_file: binpath = os.path.join(self.sim_dir,self.param['BIN_OUT']) @@ -1487,7 +1489,8 @@ def read_param(self, param_file, codename="Swiftest", verbose=True): self.ds : xarray dataset """ if codename == "Swiftest": - self.param = io.read_swiftest_param(param_file, self.param, verbose=verbose) + param_old = self.param.copy() + self.param = io.read_swiftest_param(param_file, param_old, verbose=verbose) self.codename = "Swiftest" elif codename == "Swifter": self.param = io.read_swifter_param(param_file, verbose=verbose) From c7ab062afeec3b6b7e3121c731e1c49ef61e4d77 Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 19:42:04 -0500 Subject: [PATCH 23/75] Consolidated all the parameter setters into one --- python/swiftest/swiftest/simulation_class.py | 328 +++++++++++-------- 1 file changed, 191 insertions(+), 137 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index b72542aaa..80e1527ec 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -240,45 +240,6 @@ def __init__(self, self.verbose = verbose self.restart = restart - self.set_distance_range(rmin=rmin, rmax=rmax, verbose = False) - - self.set_unit_system(MU=MU, DU=DU, TU=TU, - MU2KG=MU2KG, DU2M=DU2M, TU2S=TU2S, - MU_name=MU_name, DU_name=DU_name, TU_name=TU_name, - recompute_values=True, - verbose = False) - - self.set_init_cond_files(init_cond_file_type=init_cond_file_type, - init_cond_file_name=init_cond_file_name, - init_cond_format=init_cond_format, - verbose = False) - - self.set_output_files(output_file_type=output_file_type, - output_file_name=output_file_name, - output_format=output_format, - verbose = False) - - self.set_feature(close_encounter_check=close_encounter_check, - general_relativity=general_relativity, - fragmentation=fragmentation, - rotation=rotation, - compute_conservation_values=compute_conservation_values, - extra_force=extra_force, - big_discard=big_discard, - rhill_present=rhill_present, - restart=restart, - verbose = False) - - self.set_simulation_time(t0=t0, - tstart=tstart, - tstop=tstop, - dt=dt, - tstep_out=tstep_out, - istep_out=istep_out, - istep_dump=istep_dump, - verbose = False - ) - # If the parameter file is in a different location than the current working directory, we will need # to use it to properly open bin files self.sim_dir = os.path.dirname(os.path.realpath(param_file)) @@ -288,6 +249,37 @@ def __init__(self, else: print(f"{param_file} not found.") + self.set_parameters(t0=t0, + tstart=tstart, + tstop=tstop, + dt=dt, + tstep_out=tstep_out, + istep_out=istep_out, + istep_dump=istep_dump, + rmin=rmin, rmax=rmax, + MU=MU, DU=DU, TU=TU, + MU2KG=MU2KG, DU2M=DU2M, TU2S=TU2S, + MU_name=MU_name, DU_name=DU_name, TU_name=TU_name, + recompute_unit_values=False, + init_cond_file_type=init_cond_file_type, + init_cond_file_name=init_cond_file_name, + init_cond_format=init_cond_format, + output_file_type=output_file_type, + output_file_name=output_file_name, + output_format=output_format, + close_encounter_check=close_encounter_check, + general_relativity=general_relativity, + fragmentation=fragmentation, + rotation=rotation, + compute_conservation_values=compute_conservation_values, + extra_force=extra_force, + big_discard=big_discard, + rhill_present=rhill_present, + restart=restart, + verbose = False) + + + if read_old_output_file: binpath = os.path.join(self.sim_dir,self.param['BIN_OUT']) if os.path.exists(binpath): @@ -296,6 +288,48 @@ def __init__(self, print(f"BIN_OUT file {binpath} not found.") return + def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var: Dict | None = None): + """ + Internal function for getters that extracts subset of arguments that is contained in the dictionary of valid + argument/parameter variable pairs. + + Parameters + ---------- + arg_list : str | List[str], optional + A single string or list of strings containing the Simulation argument. If none are supplied, + then it will create the arg_list out of all keys in the valid_var dictionary. + valid_var : valid_var: Dict + A dictionary where the key is the argument name and the value is the equivalent key in the Simulation + parameter dictionary (i.e. the left-hand column of a param.in file) + + Returns + ------- + valid_arg : [str] + The list of valid arguments that match the keys in valid_var + param : dict + A dictionary that is the subset of the Simulation parameter dictionary corresponding to the arguments listed + in arg_list. + + """ + + if arg_list is None: + valid_arg = None + else: + valid_arg = arg_list.copy() + + if valid_arg is None: + valid_arg = list(valid_var.keys()) + elif type(arg_list) is str: + valid_arg = [arg_list] + else: + # Only allow arg_lists to be checked if they are valid. Otherwise ignore. + valid_arg = [k for k in arg_list if k in list(valid_var.keys())] + + # Extract the arg_list dictionary + param = {valid_var[arg]: self.param[valid_var[arg]] for arg in valid_arg if valid_var[arg] in self.param} + + return valid_arg, param + def set_simulation_time(self, t0: float | None = None, tstart: float | None = None, @@ -335,7 +369,8 @@ def set_simulation_time(self, Returns ------- - Sets the appropriate parameter variables in the parameter dictionary. + time_dict : dict + A dictionary containing the requested parameters """ @@ -409,16 +444,11 @@ def set_simulation_time(self, self.param['ISTEP_DUMP'] = istep_dump update_list.append("istep_dump") + time_dict = self.get_simulation_time(update_list,verbose=verbose) - if verbose is None: - verbose = self.verbose - if verbose: - if len(update_list) > 0: - time_dict = self.get_simulation_time(update_list) - - return + return time_dict - def get_simulation_time(self, arg_list: str | List[str] | None = None): + def get_simulation_time(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): """ Returns a subset of the parameter dictionary containing the current simulation time parameters. @@ -430,6 +460,8 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None): A single string or list of strings containing the names of the simulation time parameters to extract. Default is all of: ["t0", "tstart", "tstop", "dt", "istep_out", "tstep_out", "istep_dump"] + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag Returns ------- @@ -463,7 +495,10 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None): valid_arg, time_dict = self._get_valid_arg_list(arg_list, valid_var) - if self.verbose: + if verbose is None: + verbose = self.verbose + + if verbose: for arg in valid_arg: key = valid_var[arg] if key in time_dict: @@ -506,6 +541,8 @@ def set_feature(self, rhill_present: bool | None = None, restart: bool | None = None, tides: bool | None = None, + interaction_loops: Literal["TRIANGULAR", "FLAT", "ADAPTIVE"] | None = None, + encounter_check_loops: Literal["TRIANGULAR", "SORTSWEEP", "ADAPTIVE"] | None = None, verbose: bool | None = None, **kwargs: Any ): @@ -535,6 +572,28 @@ def set_feature(self, Includes big bodies when performing a discard (Swifter only) rhill_present: bool, optional Include the Hill's radius with the input files. + interaction_loops : {"TRIANGULAR","FLAT","ADAPTIVE"}, default "TRIANGULAR" + *Swiftest Experimental feature* + Specifies which algorithm to use for the computation of body-body gravitational forces. + * "TRIANGULAR" : Upper-triangular double-loops . + * "FLAT" : Body-body interation pairs are flattened into a 1-D array. + * "ADAPTIVE" : Periodically times the TRIANGULAR and FLAT methods and determines which one to use based on + the wall time to complete the loop. *Note:* Using ADAPTIVE means that bit-identical repeatability cannot + be assured, as the choice of algorithm depends on possible external factors that can influence the wall + time calculation. The exact floating-point results of the interaction will be different between the two + algorithm types. + encounter_check_loops : {"TRIANGULAR","SORTSWEEP","ADAPTIVE"}, default "TRIANGULAR" + *Swiftest Experimental feature* + Specifies which algorithm to use for checking whether bodies are in a close encounter state or not. + * "TRIANGULAR" : Upper-triangular double-loops. + * "SORTSWEEP" : A Sort-Sweep algorithm is used to reduce the population of potential close encounter bodies. + This algorithm is still in development, and does not necessarily speed up the encounter checking. + Use with caution. + * "ADAPTIVE" : Periodically times the TRIANGULAR and SORTSWEEP methods and determines which one to use based + on the wall time to complete the loop. *Note:* Using ADAPTIVE means that bit-identical repeatability cannot + be assured, as the choice of algorithm depends on possible external factors that can influence the wall + time calculation. The exact floating-point results of the interaction will be different between the two + algorithm types. tides: bool, optional Turns on tidal model (IN DEVELOPMENT - IGNORED) Yarkovsky: bool, optional @@ -553,7 +612,8 @@ def set_feature(self, Returns ------- - Sets the appropriate parameters in the self.param dictionary + feature_dict : dict + A dictionary containing the requested features. """ @@ -597,17 +657,31 @@ def set_feature(self, self.param["RESTART"] = restart update_list.append("restart") + if interaction_loops is not None: + valid_vals = ["TRIANGULAR","FLAT","ADAPTIVE"] + if interaction_loops not in valid_vals: + print(f"{interaction_loops} is not a valid option for interaction loops.") + print(f"Must be one of {valid_vals}") + self.param["INTERACTION_LOOPS"] = interaction_loops + else: + update_list.append("interaction_loops") + + if encounter_check_loops is not None: + valid_vals = ["TRIANGULAR", "SORTSWEEP", "ADAPTIVE"] + if encounter_check_loops not in valid_vals: + print(f"{encounter_check_loops} is not a valid option for interaction loops.") + print(f"Must be one of {valid_vals}") + else: + self.param["ENCOUNTER_CHECK"] = encounter_check_loops + update_list.append("encounter_check_loops") + self.param["TIDES"] = False - if verbose is None: - verbose = self.verbose - if verbose: - if len(update_list) > 0: - feature_dict = self.get_feature(update_list) - return + feature_dict = self.get_feature(update_list, verbose) + return feature_dict - def get_feature(self, arg_list: str | List[str] | None = None): + def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): """ Returns a subset of the parameter dictionary containing the current value of the feature boolean values. @@ -618,6 +692,8 @@ def get_feature(self, arg_list: str | List[str] | None = None): arg_list: str | List[str], optional A single string or list of strings containing the names of the features to extract. Default is all of: ["close_encounter_check", "general_relativity", "fragmentation", "rotation", "compute_conservation_values"] + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag Returns ------- @@ -634,12 +710,17 @@ def get_feature(self, arg_list: str | List[str] | None = None): "extra_force": "EXTRA_FORCE", "big_discard": "BIG_DISCARD", "rhill_present": "RHILL_PRESENT", - "restart" : "RESTART" + "restart" : "RESTART", + "interaction_loops" : "INTERACTION_LOOPS", + "encounter_check_loops" : "ENCOUNTER_CHECK_LOOPS" } valid_arg, feature_dict = self._get_valid_arg_list(arg_list, valid_var) - if self.verbose: + if verbose is None: + verbose = self.verbose + + if verbose: for arg in valid_arg: key = valid_var[arg] print(f"{arg:<32} {feature_dict[key]}") @@ -688,7 +769,8 @@ def set_init_cond_files(self, Returns ------- - Sets the appropriate initial conditions file parameters inside the self.param dictionary. + init_cond_file_dict : dict + A dictionary containing the requested parameters """ @@ -774,16 +856,12 @@ def ascii_file_input_error_msg(codename): self.param["NC_IN"] = init_cond_file_name - if verbose is None: - verbose = self.verbose - if verbose: - if len(update_list) > 0: - init_cond_file_dict = self.get_init_cond_files(update_list) + init_cond_file_dict = self.get_init_cond_files(update_list,verbose) - return + return init_cond_file_dict - def get_init_cond_files(self, arg_list: str | List[str] | None = None): + def get_init_cond_files(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): """ Returns a subset of the parameter dictionary containing the current initial condition file parameters @@ -795,6 +873,8 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None): A single string or list of strings containing the names of the simulation time parameters to extract. Default is all of: ["init_cond_file_type", "init_cond_file_name", "init_cond_format"] + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag Returns ------- @@ -840,7 +920,10 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None): valid_arg, init_cond_file_dict = self._get_valid_arg_list(valid_arg, valid_var) - if self.verbose: + if verbose is None: + verbose = self.verbose + + if verbose: for arg in valid_arg: key = valid_var[arg] print(f"{arg:<32} {init_cond_file_dict[key]}") @@ -857,7 +940,7 @@ def set_output_files(self, **kwargs: Any ): """ - Sets the output file parameters in the parameters dictionary. + Sets the output file parameters in the parameter dictionary. Parameters ---------- @@ -881,7 +964,8 @@ def set_output_files(self, Returns ------- - Sets the appropriate initial conditions file parameters inside the self.param dictionary. + output_file_dict : dict + A dictionary containing the requested parameters """ update_list = [] @@ -936,16 +1020,12 @@ def set_output_files(self, else: self.param["OUT_STAT"] = "REPLACE" - if verbose is None: - verbose = self.verbose - if verbose: - if len(update_list) > 0: - output_file_dict = self.get_output_files(update_list) + output_file_dict = self.get_output_files(update_list, verbose=verbose) - return + return output_file_dict - def get_output_files(self, arg_list: str | List[str] | None = None): + def get_output_files(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): """ Returns a subset of the parameter dictionary containing the current output file parameters @@ -957,6 +1037,8 @@ def get_output_files(self, arg_list: str | List[str] | None = None): A single string or list of strings containing the names of the simulation time parameters to extract. Default is all of: ["output_file_type", "output_file_name", "output_format"] + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag Returns ------- @@ -972,7 +1054,10 @@ def get_output_files(self, arg_list: str | List[str] | None = None): valid_arg, output_file_dict = self._get_valid_arg_list(arg_list, valid_var) - if self.verbose: + if verbose is None: + verbose = self.verbose + + if verbose: for arg in valid_arg: key = valid_var[arg] print(f"{arg:<32} {output_file_dict[key]}") @@ -990,7 +1075,7 @@ def set_unit_system(self, MU_name: str | None = None, DU_name: str | None = None, TU_name: str | None = None, - recompute_values: bool = True, + recompute_unit_values: bool = True, verbose: bool | None = None, **kwargs: Any): """ @@ -1043,7 +1128,7 @@ def set_unit_system(self, TU_name : str, optional The name of the time unit. When setting one of the standard units via `TU` a name will be automatically set for the unit, so this argument will override the automatic name. - recompute_values : bool, default True + recompute_unit_values : bool, default True Recompute all values into the new unit system. >*Note:* This is a destructive operation, however if not executed then the values contained in the parameter > file and input/output data files computed previously may not be consistent with the new unit conversion @@ -1056,8 +1141,8 @@ def set_unit_system(self, Returns ---------- - Sets the values of MU2KG, DU2M, and TU2S in the param dictionary to the appropriate units. Also computes the - gravitational constant, self.GU and recomput + unit_dict : dict + A dictionary containing the requested unit conversion parameters """ MU2KG_old = None @@ -1149,18 +1234,14 @@ def set_unit_system(self, self.VU_name = f"{self.DU_name}/{self.TU_name}" self.GU = constants.GC * self.param['TU2S']**2 * self.param['MU2KG'] / self.param['DU2M']**3 - if recompute_values: + if recompute_unit_values: self.update_param_units(MU2KG_old, DU2M_old, TU2S_old) - if verbose is None: - verbose = self.verbose - - if verbose: - unit_dict = self.get_unit_system(update_list) + unit_dict = self.get_unit_system(update_list, verbose) - return + return unit_dict - def get_unit_system(self, arg_list: str | List[str] | None = None): + def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): """ Returns a subset of the parameter dictionary containing the current simulation unit system. @@ -1175,8 +1256,8 @@ def get_unit_system(self, arg_list: str | List[str] | None = None): Returns ------- - time_dict : dict - A dictionary containing the requested parameters + unit_dict : dict + A dictionary containing the requested unit conversion parameters """ @@ -1212,7 +1293,10 @@ def get_unit_system(self, arg_list: str | List[str] | None = None): valid_arg, unit_dict = self._get_valid_arg_list(arg_list, valid_var) - if self.verbose: + if verbose is None: + verbose = self.verbose + + if verbose: for arg in valid_arg: key = valid_var[arg] print(f"{arg}: {units1[arg]:<28} {unit_dict[key]} {units2[arg]}") @@ -1268,6 +1352,7 @@ def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): def set_distance_range(self, rmin: float | None = None, rmax: float | None = None, + verbose: bool | None = None, **kwargs: Any): """ Sets the minimum and maximum distances of the simulation. @@ -1284,9 +1369,12 @@ def set_distance_range(self, Returns ------- - Sets the appropriate param dictionary values. + range_dict : dict + A dictionary containing the requested parameters. """ + + update_list = [] CHK_QMIN_RANGE = self.param.pop('CHK_QMIN_RANGE',None) if CHK_QMIN_RANGE is None: CHK_QMIN_RANGE = [-1, -1] @@ -1296,58 +1384,21 @@ def set_distance_range(self, self.param['CHK_QMIN'] = rmin self.param['CHK_RMIN'] = rmin CHK_QMIN_RANGE[0] = rmin + update_list.append("rmin") if rmax is not None: self.param['CHK_RMAX'] = rmax self.param['CHK_EJECT'] = rmax CHK_QMIN_RANGE[1] = rmax + update_list.append("rmax") self.param['CHK_QMIN_RANGE'] =f"{CHK_QMIN_RANGE[0]} {CHK_QMIN_RANGE[1]}" - return - - def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var: Dict | None = None): - """ - Internal function for getters that extracts subset of arguments that is contained in the dictionary of valid - argument/parameter variable pairs. - - Parameters - ---------- - arg_list : str | List[str], optional - A single string or list of strings containing the Simulation argument. If none are supplied, - then it will create the arg_list out of all keys in the valid_var dictionary. - valid_var : valid_var: Dict - A dictionary where the key is the argument name and the value is the equivalent key in the Simulation - parameter dictionary (i.e. the left-hand column of a param.in file) - - Returns - ------- - valid_arg : [str] - The list of valid arguments that match the keys in valid_var - param : dict - A dictionary that is the subset of the Simulation parameter dictionary corresponding to the arguments listed - in arg_list. - - """ - - if arg_list is None: - valid_arg = None - else: - valid_arg = arg_list.copy() + range_dict = self.get_distance_range(update_list,verbose=verbose) - if valid_arg is None: - valid_arg = list(valid_var.keys()) - elif type(arg_list) is str: - valid_arg = [arg_list] - else: - # Only allow arg_lists to be checked if they are valid. Otherwise ignore. - valid_arg = [k for k in arg_list if k in list(valid_var.keys())] + return range_dict - # Extract the arg_list dictionary - param = {valid_var[arg]:self.param[valid_var[arg]] for arg in valid_arg if valid_var[arg] in self.param} - return valid_arg, param - - def get_distance_range(self, arg_list: str | List[str] | None = None): + def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): """ Returns a subset of the parameter dictionary containing the current values of the distance range parameters. @@ -1388,7 +1439,10 @@ def get_distance_range(self, arg_list: str | List[str] | None = None): valid_arg, range_dict = self._get_valid_arg_list(arg_list, valid_var) - if self.verbose: + if verbose is None: + verbose = self.verbose + + if verbose: if "rmin" in valid_arg: key = valid_var["rmin"] print(f"{'rmin':<32} {range_dict[key]} {units['rmin']}") From 69807af2579d53f7c90fa806d38230acc95c2a82 Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 19:52:47 -0500 Subject: [PATCH 24/75] Wrote consolidated get_parameters and set_parameters methods. --- python/swiftest/swiftest/simulation_class.py | 68 ++++++++++++++++---- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 80e1527ec..b7f948d2d 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -279,7 +279,6 @@ def __init__(self, verbose = False) - if read_old_output_file: binpath = os.path.join(self.sim_dir,self.param['BIN_OUT']) if os.path.exists(binpath): @@ -448,7 +447,7 @@ def set_simulation_time(self, return time_dict - def get_simulation_time(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): + def get_simulation_time(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs): """ Returns a subset of the parameter dictionary containing the current simulation time parameters. @@ -462,6 +461,10 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None, verbose: ["t0", "tstart", "tstop", "dt", "istep_out", "tstep_out", "istep_dump"] verbose: bool, optional If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + Returns ------- @@ -522,13 +525,33 @@ def set_parameters(self, **kwargs): param : A dictionary of all Simulation parameters that changed """ - self.set_unit_system(**kwargs) - self.set_distance_range(**kwargs) - self.set_feature(**kwargs) - self.set_init_cond_files(**kwargs) - self.set_output_files(**kwargs) - self.set_simulation_time(**kwargs) + param_dict = self.set_unit_system(**kwargs) + param_dict.update(self.set_distance_range(**kwargs)) + param_dict.update(self.set_feature(**kwargs)) + param_dict.update(self.set_init_cond_files(**kwargs)) + param_dict.update(self.set_output_files(**kwargs)) + param_dict.update(self.set_simulation_time(**kwargs)) + + def get_parameters(self, **kwargs): + """ + Setter for all possible parameters. Calls each of the specialized setters using keyword arguments + Parameters + ---------- + **kwargs : [TODO: write this documentation] + + Returns + ------- + param : A dictionary of all Simulation parameters requested + + """ + param_dict = self.get_simulation_time(**kwargs) + param_dict.update(self.get_init_cond_files(**kwargs)) + param_dict.update(self.get_output_files(**kwargs)) + param_dict.update(self.get_distance_range(**kwargs)) + param_dict.update(self.get_unit_system(**kwargs)) + param_dict.update(self.get_feature(**kwargs)) + return param_dict def set_feature(self, close_encounter_check: bool | None = None, @@ -681,7 +704,7 @@ def set_feature(self, return feature_dict - def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): + def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs: Any): """ Returns a subset of the parameter dictionary containing the current value of the feature boolean values. @@ -694,6 +717,9 @@ def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | N ["close_encounter_check", "general_relativity", "fragmentation", "rotation", "compute_conservation_values"] verbose: bool, optional If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -712,7 +738,7 @@ def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | N "rhill_present": "RHILL_PRESENT", "restart" : "RESTART", "interaction_loops" : "INTERACTION_LOOPS", - "encounter_check_loops" : "ENCOUNTER_CHECK_LOOPS" + "encounter_check_loops" : "ENCOUNTER_CHECK" } valid_arg, feature_dict = self._get_valid_arg_list(arg_list, valid_var) @@ -861,7 +887,7 @@ def ascii_file_input_error_msg(codename): return init_cond_file_dict - def get_init_cond_files(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): + def get_init_cond_files(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs): """ Returns a subset of the parameter dictionary containing the current initial condition file parameters @@ -875,6 +901,10 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None, verbose: ["init_cond_file_type", "init_cond_file_name", "init_cond_format"] verbose: bool, optional If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + Returns ------- @@ -1025,7 +1055,7 @@ def set_output_files(self, return output_file_dict - def get_output_files(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): + def get_output_files(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs): """ Returns a subset of the parameter dictionary containing the current output file parameters @@ -1039,6 +1069,10 @@ def get_output_files(self, arg_list: str | List[str] | None = None, verbose: boo ["output_file_type", "output_file_name", "output_format"] verbose: bool, optional If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + Returns ------- @@ -1241,7 +1275,7 @@ def set_unit_system(self, return unit_dict - def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): + def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs): """ Returns a subset of the parameter dictionary containing the current simulation unit system. @@ -1253,6 +1287,9 @@ def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool A single string or list of strings containing the names of the simulation unit system Default is all of: ["MU", "DU", "TU"] + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -1398,7 +1435,7 @@ def set_distance_range(self, return range_dict - def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: bool | None = None): + def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs: Any): """ Returns a subset of the parameter dictionary containing the current values of the distance range parameters. @@ -1409,6 +1446,9 @@ def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: b arg_list: str | List[str], optional A single string or list of strings containing the names of the features to extract. Default is all of: ["rmin", "rmax"] + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- From 1cc9745477c9dd3b0d86931cf83c5656d4cb88a9 Mon Sep 17 00:00:00 2001 From: David Minton Date: Wed, 9 Nov 2022 19:55:07 -0500 Subject: [PATCH 25/75] return dictionary from set_parameter. Renamed from parameters -> parameter in getter and setter --- python/swiftest/swiftest/simulation_class.py | 32 +++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index b7f948d2d..7b043e8ca 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -249,7 +249,7 @@ def __init__(self, else: print(f"{param_file} not found.") - self.set_parameters(t0=t0, + self.set_parameter(t0=t0, tstart=tstart, tstop=tstop, dt=dt, @@ -364,7 +364,7 @@ def set_simulation_time(self, If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + set_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -463,7 +463,7 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None, verbose: If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns @@ -513,7 +513,7 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None, verbose: return time_dict - def set_parameters(self, **kwargs): + def set_parameter(self, **kwargs): """ Setter for all possible parameters. Calls each of the specialized setters using keyword arguments Parameters @@ -532,7 +532,9 @@ def set_parameters(self, **kwargs): param_dict.update(self.set_output_files(**kwargs)) param_dict.update(self.set_simulation_time(**kwargs)) - def get_parameters(self, **kwargs): + return param_dict + + def get_parameter(self, **kwargs): """ Setter for all possible parameters. Calls each of the specialized setters using keyword arguments Parameters @@ -631,7 +633,7 @@ def set_feature(self, If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + set_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -719,7 +721,7 @@ def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | N If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -791,7 +793,7 @@ def set_init_cond_files(self, If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + set_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -903,7 +905,7 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None, verbose: If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns @@ -990,7 +992,7 @@ def set_output_files(self, If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + set_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -1071,7 +1073,7 @@ def get_output_files(self, arg_list: str | List[str] | None = None, verbose: boo If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns @@ -1171,7 +1173,7 @@ def set_unit_system(self, If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + set_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ---------- @@ -1289,7 +1291,7 @@ def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool ["MU", "DU", "TU"] **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -1402,7 +1404,7 @@ def set_distance_range(self, Maximum distance of the simulation (CHK_RMAX, CHK_QMIN_RANGE[1]) **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - set_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + set_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- @@ -1448,7 +1450,7 @@ def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: b ["rmin", "rmax"] **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general - get_parameters method, which takes all possible Simulation parameters as arguments, so these are ignored. + get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. Returns ------- From a8a2423c5372655ad4301277f333db8509a92dd8 Mon Sep 17 00:00:00 2001 From: David Minton Date: Thu, 10 Nov 2022 11:31:58 -0500 Subject: [PATCH 26/75] Started the process of restructuring the methods for adding bodies. It's partially done, so none of the methods work quite yet. I also added a new method for setting an "ephemeris_date" instance variable that is used when pulling bodies down from JPL Horizons. --- python/swiftest/swiftest/init_cond.py | 4 +- python/swiftest/swiftest/simulation_class.py | 518 ++++++++++++------- 2 files changed, 333 insertions(+), 189 deletions(-) diff --git a/python/swiftest/swiftest/init_cond.py b/python/swiftest/swiftest/init_cond.py index bf2720a3c..641fa2b68 100644 --- a/python/swiftest/swiftest/init_cond.py +++ b/python/swiftest/swiftest/init_cond.py @@ -18,7 +18,7 @@ from datetime import date import xarray as xr -def solar_system_horizons(plname, idval, param, ephemerides_start_date, ds): +def solar_system_horizons(plname, idval, param, ephemerides_start_date): """ Initializes a Swiftest dataset containing the major planets of the Solar System at a particular data from JPL/Horizons @@ -28,8 +28,6 @@ def solar_system_horizons(plname, idval, param, ephemerides_start_date, ds): Swiftest paramuration parameters. This method uses the unit conversion factors to convert from JPL's AU-day system into the system specified in the param file ephemerides_start_date : string Date to use when obtaining the ephemerides in the format YYYY-MM-DD. - ds : xarray Dataset - Dataset to append Returns ------- diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index f3c786af1..0d26ebdf0 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -14,7 +14,7 @@ from swiftest import init_cond from swiftest import tool from swiftest import constants -from datetime import date +import datetime import xarray as xr import numpy as np import os @@ -26,13 +26,15 @@ Any ) + class Simulation: """ This is a class that defines the basic Swift/Swifter/Swiftest simulation object """ + def __init__(self, codename: Literal["Swiftest", "Swifter", "Swift"] = "Swiftest", - param_file: os.PathLike | str ="param.in", + param_file: os.PathLike | str = "param.in", read_param: bool = False, t0: float = 0.0, tstart: float = 0.0, @@ -44,9 +46,10 @@ def __init__(self, init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"] = "NETCDF_DOUBLE", init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None = None, init_cond_format: Literal["EL", "XV"] = "EL", - output_file_type: Literal["NETCDF_DOUBLE","NETCDF_FLOAT","REAL4","REAL8","XDR4","XDR8"] = "NETCDF_DOUBLE", + output_file_type: Literal[ + "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"] = "NETCDF_DOUBLE", output_file_name: os.PathLike | str | None = None, - output_format: Literal["XV","XVEL"] = "XVEL", + output_format: Literal["XV", "XVEL"] = "XVEL", read_old_output_file: bool = False, MU: str = "MSUN", DU: str = "AU", @@ -68,10 +71,11 @@ def __init__(self, big_discard: bool = False, rhill_present: bool = False, restart: bool = False, - interaction_loops: Literal["TRIANGULAR","FLAT","ADAPTIVE"] = "TRIANGULAR", - encounter_check_loops: Literal["TRIANGULAR","SORTSWEEP","ADAPTIVE"] = "TRIANGULAR", + interaction_loops: Literal["TRIANGULAR", "FLAT", "ADAPTIVE"] = "TRIANGULAR", + encounter_check_loops: Literal["TRIANGULAR", "SORTSWEEP", "ADAPTIVE"] = "TRIANGULAR", + ephemeris_date: str = "MBCL", verbose: bool = True - ): + ): """ Parameters @@ -229,6 +233,7 @@ def __init__(self, If set to True, then more information is printed by Simulation methods as they are executed. Setting to False suppresses most messages other than errors. """ + self.ds = xr.Dataset() self.param = { '! VERSION': f"Swiftest parameter input", @@ -240,6 +245,8 @@ def __init__(self, self.verbose = verbose self.restart = restart + # Width of the column in the printed name of the parameter in parameter getters + self._getter_column_width = '32' # If the parameter file is in a different location than the current working directory, we will need # to use it to properly open bin files self.sim_dir = os.path.dirname(os.path.realpath(param_file)) @@ -250,37 +257,37 @@ def __init__(self, print(f"{param_file} not found.") self.set_parameter(t0=t0, - tstart=tstart, - tstop=tstop, - dt=dt, - tstep_out=tstep_out, - istep_out=istep_out, - istep_dump=istep_dump, - rmin=rmin, rmax=rmax, - MU=MU, DU=DU, TU=TU, - MU2KG=MU2KG, DU2M=DU2M, TU2S=TU2S, - MU_name=MU_name, DU_name=DU_name, TU_name=TU_name, - recompute_unit_values=False, - init_cond_file_type=init_cond_file_type, - init_cond_file_name=init_cond_file_name, - init_cond_format=init_cond_format, - output_file_type=output_file_type, - output_file_name=output_file_name, - output_format=output_format, - close_encounter_check=close_encounter_check, - general_relativity=general_relativity, - fragmentation=fragmentation, - rotation=rotation, - compute_conservation_values=compute_conservation_values, - extra_force=extra_force, - big_discard=big_discard, - rhill_present=rhill_present, - restart=restart, - verbose = False) - + tstart=tstart, + tstop=tstop, + dt=dt, + tstep_out=tstep_out, + istep_out=istep_out, + istep_dump=istep_dump, + rmin=rmin, rmax=rmax, + MU=MU, DU=DU, TU=TU, + MU2KG=MU2KG, DU2M=DU2M, TU2S=TU2S, + MU_name=MU_name, DU_name=DU_name, TU_name=TU_name, + recompute_unit_values=False, + init_cond_file_type=init_cond_file_type, + init_cond_file_name=init_cond_file_name, + init_cond_format=init_cond_format, + output_file_type=output_file_type, + output_file_name=output_file_name, + output_format=output_format, + close_encounter_check=close_encounter_check, + general_relativity=general_relativity, + fragmentation=fragmentation, + rotation=rotation, + compute_conservation_values=compute_conservation_values, + extra_force=extra_force, + big_discard=big_discard, + rhill_present=rhill_present, + restart=restart, + ephemeris_date=ephemeris_date, + verbose=False) if read_old_output_file: - binpath = os.path.join(self.sim_dir,self.param['BIN_OUT']) + binpath = os.path.join(self.sim_dir, self.param['BIN_OUT']) if os.path.exists(binpath): self.bin2xr() else: @@ -334,10 +341,10 @@ def set_simulation_time(self, tstart: float | None = None, tstop: float | None = None, dt: float | None = None, - istep_out : int | None = None, - tstep_out : float | None = None, - istep_dump : int | None = None, - verbose : bool | None = None, + istep_out: int | None = None, + tstep_out: float | None = None, + istep_dump: int | None = None, + verbose: bool | None = None, **kwargs: Any ): """ @@ -375,7 +382,6 @@ def set_simulation_time(self, update_list = [] - if t0 is None: t0 = self.param.pop("T0", None) if t0 is None: @@ -414,7 +420,7 @@ def set_simulation_time(self, if dt is not None and tstop is not None: if dt > (tstop - tstart): print("Error! dt must be smaller than tstop-tstart") - print(f"Setting dt = {tstop-tstart} instead of {dt}") + print(f"Setting dt = {tstop - tstart} instead of {dt}") dt = tstop - tstart if dt is not None: @@ -435,7 +441,7 @@ def set_simulation_time(self, update_list.append("istep_out") if istep_dump is None: - istep_dump = self.param.pop("ISTEP_DUMP",None) + istep_dump = self.param.pop("ISTEP_DUMP", None) if istep_dump is None: istep_dump = istep_out @@ -443,7 +449,7 @@ def set_simulation_time(self, self.param['ISTEP_DUMP'] = istep_dump update_list.append("istep_dump") - time_dict = self.get_simulation_time(update_list,verbose=verbose) + time_dict = self.get_simulation_time(update_list, verbose=verbose) return time_dict @@ -481,13 +487,13 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None, verbose: "istep_dump": "ISTEP_DUMP", } - units = {"t0" : self.TU_name, - "tstart" : self.TU_name, - "tstop" : self.TU_name, - "dt" : self.TU_name, - "tstep_out" : self.TU_name, - "istep_out" : "", - "istep_dump" : ""} + units = {"t0": self.TU_name, + "tstart": self.TU_name, + "tstop": self.TU_name, + "dt": self.TU_name, + "tstep_out": self.TU_name, + "istep_out": "", + "istep_dump": ""} tstep_out = None if arg_list is None or "tstep_out" in arg_list or "istep_out" in arg_list: @@ -505,11 +511,11 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None, verbose: for arg in valid_arg: key = valid_var[arg] if key in time_dict: - print(f"{arg:<32} {time_dict[key]} {units[arg]}") + print(f"{arg:<{self._getter_column_width}} {time_dict[key]} {units[arg]}") else: - print(f"{arg:<32} NOT SET") + print(f"{arg:<{self._getter_column_width}} NOT SET") if tstep_out is not None: - print(f"{'tstep_out':<32} {tstep_out} {units['tstep_out']}") + print(f"{'tstep_out':<{self._getter_column_width}} {tstep_out} {units['tstep_out']}") return time_dict @@ -525,6 +531,8 @@ def set_parameter(self, **kwargs): param : A dictionary of all Simulation parameters that changed """ + + # Setters returning parameter dictionary values param_dict = self.set_unit_system(**kwargs) param_dict.update(self.set_distance_range(**kwargs)) param_dict.update(self.set_feature(**kwargs)) @@ -532,6 +540,9 @@ def set_parameter(self, **kwargs): param_dict.update(self.set_output_files(**kwargs)) param_dict.update(self.set_simulation_time(**kwargs)) + # Non-returning setters + self.set_ephemeris_date(**kwargs) + return param_dict def get_parameter(self, **kwargs): @@ -546,6 +557,8 @@ def get_parameter(self, **kwargs): param : A dictionary of all Simulation parameters requested """ + + # Getters returning parameter dictionary values param_dict = self.get_simulation_time(**kwargs) param_dict.update(self.get_init_cond_files(**kwargs)) param_dict.update(self.get_output_files(**kwargs)) @@ -553,6 +566,10 @@ def get_parameter(self, **kwargs): param_dict.update(self.get_unit_system(**kwargs)) param_dict.update(self.get_feature(**kwargs)) + # Non-returning getters + if not bool(kwargs) or "ephemeris_date" in kwargs: + self.get_ephemeris_date(**kwargs) + return param_dict def set_feature(self, @@ -560,7 +577,7 @@ def set_feature(self, general_relativity: bool | None = None, fragmentation: bool | None = None, rotation: bool | None = None, - compute_conservation_values: bool | None=None, + compute_conservation_values: bool | None = None, extra_force: bool | None = None, big_discard: bool | None = None, rhill_present: bool | None = None, @@ -570,7 +587,7 @@ def set_feature(self, encounter_check_loops: Literal["TRIANGULAR", "SORTSWEEP", "ADAPTIVE"] | None = None, verbose: bool | None = None, **kwargs: Any - ): + ): """ Turns on or off various features of a simulation. @@ -683,7 +700,7 @@ def set_feature(self, update_list.append("restart") if interaction_loops is not None: - valid_vals = ["TRIANGULAR","FLAT","ADAPTIVE"] + valid_vals = ["TRIANGULAR", "FLAT", "ADAPTIVE"] if interaction_loops not in valid_vals: print(f"{interaction_loops} is not a valid option for interaction loops.") print(f"Must be one of {valid_vals}") @@ -705,7 +722,6 @@ def set_feature(self, feature_dict = self.get_feature(update_list, verbose) return feature_dict - def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs: Any): """ @@ -738,9 +754,9 @@ def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | N "extra_force": "EXTRA_FORCE", "big_discard": "BIG_DISCARD", "rhill_present": "RHILL_PRESENT", - "restart" : "RESTART", - "interaction_loops" : "INTERACTION_LOOPS", - "encounter_check_loops" : "ENCOUNTER_CHECK" + "restart": "RESTART", + "interaction_loops": "INTERACTION_LOOPS", + "encounter_check_loops": "ENCOUNTER_CHECK" } valid_arg, feature_dict = self._get_valid_arg_list(arg_list, valid_var) @@ -751,13 +767,14 @@ def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | N if verbose: for arg in valid_arg: key = valid_var[arg] - print(f"{arg:<32} {feature_dict[key]}") + print(f"{arg:<{self._getter_column_width}} {feature_dict[key]}") return feature_dict def set_init_cond_files(self, init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"] | None = None, - init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None = None, + init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[ + str, os.PathLike] | None = None, init_cond_format: Literal["EL", "XV"] | None = None, verbose: bool | None = None, **kwargs: Any @@ -838,13 +855,12 @@ def ascii_file_input_error_msg(codename): init_cond_keys = ["PL", "TP"] if init_cond_file_type != "ASCII": print(f"{init_cond_file_type} is not supported by {self.codename}. Using ASCII instead") - init_cond_file_type="ASCII" + init_cond_file_type = "ASCII" if init_cond_format != "XV": print(f"{init_cond_format} is not supported by {self.codename}. Using XV instead") init_cond_format = "XV" - - valid_formats={"EL", "XV"} + valid_formats = {"EL", "XV"} if init_cond_format not in valid_formats: print(f"{init_cond_format} is not a valid input format") else: @@ -883,12 +899,10 @@ def ascii_file_input_error_msg(codename): else: self.param["NC_IN"] = init_cond_file_name - - init_cond_file_dict = self.get_init_cond_files(update_list,verbose) + init_cond_file_dict = self.get_init_cond_files(update_list, verbose) return init_cond_file_dict - def get_init_cond_files(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs): """ @@ -917,10 +931,10 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None, verbose: valid_var = {"init_cond_file_type": "IN_TYPE", "init_cond_format": "IN_FORM", - "init_cond_file_name" : "NC_IN", - "init_cond_file_name['CB']" : "CB_IN", - "init_cond_file_name['PL']" : "PL_IN", - "init_cond_file_name['TP']" : "TP_IN", + "init_cond_file_name": "NC_IN", + "init_cond_file_name['CB']": "CB_IN", + "init_cond_file_name['PL']": "PL_IN", + "init_cond_file_name['TP']": "TP_IN", } three_file_args = ["init_cond_file_name['CB']", @@ -958,14 +972,13 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None, verbose: if verbose: for arg in valid_arg: key = valid_var[arg] - print(f"{arg:<32} {init_cond_file_dict[key]}") + print(f"{arg:<{self._getter_column_width}} {init_cond_file_dict[key]}") return init_cond_file_dict - - def set_output_files(self, - output_file_type: Literal[ "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"] | None = None, + output_file_type: Literal[ + "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"] | None = None, output_file_name: os.PathLike | str | None = None, output_format: Literal["XV", "XVEL"] | None = None, verbose: bool | None = None, @@ -1021,7 +1034,7 @@ def set_output_files(self, output_file_type = self.param.pop("OUT_TYPE", None) if output_file_type is None: output_file_type = "REAL8" - elif output_file_type not in ["REAL4","REAL8","XDR4","XDR8"]: + elif output_file_type not in ["REAL4", "REAL8", "XDR4", "XDR8"]: print(f"{output_file_type} is not compatible with Swifter. Setting to REAL8") output_file_type = "REAL8" elif self.codename == "Swift": @@ -1056,7 +1069,6 @@ def set_output_files(self, return output_file_dict - def get_output_files(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs): """ @@ -1096,11 +1108,10 @@ def get_output_files(self, arg_list: str | List[str] | None = None, verbose: boo if verbose: for arg in valid_arg: key = valid_var[arg] - print(f"{arg:<32} {output_file_dict[key]}") + print(f"{arg:<{self._getter_column_width}} {output_file_dict[key]}") return output_file_dict - def set_unit_system(self, MU: str | None = None, DU: str | None = None, @@ -1194,7 +1205,7 @@ def set_unit_system(self, update_list.append("TU") if MU2KG is not None or MU is not None: - MU2KG_old = self.param.pop('MU2KG',None) + MU2KG_old = self.param.pop('MU2KG', None) if MU2KG is not None: self.param['MU2KG'] = MU2KG self.MU_name = None @@ -1217,7 +1228,7 @@ def set_unit_system(self, self.MU_name = "MSun" if DU2M is not None or DU is not None: - DU2M_old = self.param.pop('DU2M',None) + DU2M_old = self.param.pop('DU2M', None) if DU2M is not None: self.param['DU2M'] = DU2M self.DU_name = None @@ -1240,7 +1251,7 @@ def set_unit_system(self, self.DU_name = "AU" if TU2S is not None or TU is not None: - TU2S_old = self.param.pop('TU2S',None) + TU2S_old = self.param.pop('TU2S', None) if TU2S is not None: self.param['TU2S'] = TU2S self.TU_name = None @@ -1259,7 +1270,6 @@ def set_unit_system(self, self.param['TU2S'] = constants.YR2S self.TU_name = "y" - if MU_name is not None: self.MU_name = MU_name if DU_name is not None: @@ -1268,7 +1278,7 @@ def set_unit_system(self, self.TU_name = TU_name self.VU_name = f"{self.DU_name}/{self.TU_name}" - self.GU = constants.GC * self.param['TU2S']**2 * self.param['MU2KG'] / self.param['DU2M']**3 + self.GU = constants.GC * self.param['TU2S'] ** 2 * self.param['MU2KG'] / self.param['DU2M'] ** 3 if recompute_unit_values: self.update_param_units(MU2KG_old, DU2M_old, TU2S_old) @@ -1301,10 +1311,10 @@ def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool """ valid_var = { - "MU": "MU2KG", - "DU": "DU2M", - "TU": "TU2S", - } + "MU": "MU2KG", + "DU": "DU2M", + "TU": "TU2S", + } if self.MU_name is None: MU_name = "mass unit" @@ -1320,15 +1330,15 @@ def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool TU_name = self.TU_name units1 = { - "MU" : MU_name, - "DU" : DU_name, - "TU" : TU_name - } + "MU": MU_name, + "DU": DU_name, + "TU": TU_name + } units2 = { - "MU" : f"kg / {MU_name}", - "DU" : f"m / {DU_name}", - "TU" : f"s / {TU_name}" - } + "MU": f"kg / {MU_name}", + "DU": f"m / {DU_name}", + "TU": f"s / {TU_name}" + } valid_arg, unit_dict = self._get_valid_arg_list(arg_list, valid_var) @@ -1338,11 +1348,12 @@ def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool if verbose: for arg in valid_arg: key = valid_var[arg] - print(f"{arg}: {units1[arg]:<28} {unit_dict[key]} {units2[arg]}") + col_width = str(int(self._getter_column_width) - 4) + print(f"{arg}: {units1[arg]:<{col_width}} {unit_dict[key]} {units2[arg]}") return unit_dict - def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): + def update_param_units(self, MU2KG_old, DU2M_old, TU2S_old): """ Updates the values of parameters that have units when the units have changed. @@ -1359,8 +1370,8 @@ def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): """ mass_keys = ['GMTINY', 'MIN_GMFRAG'] - distance_keys = ['CHK_QMIN','CHK_RMIN','CHK_RMAX', 'CHK_EJECT'] - time_keys = ['T0','TSTOP','DT'] + distance_keys = ['CHK_QMIN', 'CHK_RMIN', 'CHK_RMAX', 'CHK_EJECT'] + time_keys = ['T0', 'TSTOP', 'DT'] if MU2KG_old is not None: for k in mass_keys: @@ -1383,11 +1394,10 @@ def update_param_units(self,MU2KG_old,DU2M_old,TU2S_old): if TU2S_old is not None: for k in time_keys: if k in self.param: - self.param[k] *= TU2S_old / self.param['TU2S'] + self.param[k] *= TU2S_old / self.param['TU2S'] return - def set_distance_range(self, rmin: float | None = None, rmax: float | None = None, @@ -1414,7 +1424,7 @@ def set_distance_range(self, """ update_list = [] - CHK_QMIN_RANGE = self.param.pop('CHK_QMIN_RANGE',None) + CHK_QMIN_RANGE = self.param.pop('CHK_QMIN_RANGE', None) if CHK_QMIN_RANGE is None: CHK_QMIN_RANGE = [-1, -1] else: @@ -1430,13 +1440,12 @@ def set_distance_range(self, CHK_QMIN_RANGE[1] = rmax update_list.append("rmax") - self.param['CHK_QMIN_RANGE'] =f"{CHK_QMIN_RANGE[0]} {CHK_QMIN_RANGE[1]}" + self.param['CHK_QMIN_RANGE'] = f"{CHK_QMIN_RANGE[0]} {CHK_QMIN_RANGE[1]}" - range_dict = self.get_distance_range(update_list,verbose=verbose) + range_dict = self.get_distance_range(update_list, verbose=verbose) return range_dict - def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs: Any): """ @@ -1448,6 +1457,8 @@ def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: b arg_list: str | List[str], optional A single string or list of strings containing the names of the features to extract. Default is all of: ["rmin", "rmax"] + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. @@ -1462,13 +1473,13 @@ def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: b valid_var = {"rmin": "CHK_RMIN", "rmax": "CHK_RMAX", "qmin": "CHK_QMIN", - "qminR" : "CHK_QMIN_RANGE" + "qminR": "CHK_QMIN_RANGE" } - units = {"rmin" : self.DU_name, - "rmax" : self.DU_name, - "qmin" : self.DU_name, - "qminR" : self.DU_name, + units = {"rmin": self.DU_name, + "rmax": self.DU_name, + "qmin": self.DU_name, + "qminR": self.DU_name, } if type(arg_list) is str: @@ -1487,66 +1498,203 @@ def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: b if verbose: if "rmin" in valid_arg: key = valid_var["rmin"] - print(f"{'rmin':<32} {range_dict[key]} {units['rmin']}") + print(f"{'rmin':<{self._getter_column_width}} {range_dict[key]} {units['rmin']}") if "rmax" in valid_arg: key = valid_var["rmax"] - print(f"{'rmax':<32} {range_dict[key]} {units['rmax']}") + print(f"{'rmax':<{self._getter_column_width}} {range_dict[key]} {units['rmax']}") return range_dict - - def add(self, plname, date=date.today().isoformat(), idval=None): + def add_solar_system_body(self, + name: str | List[str] | None = None, + id: int | List[int] | None = None, + date: str | None = None, + origin_type: str = "initial_conditions", + source: str = "HORIZONS"): """ - Adds a solar system body to an existing simulation DataSet. + Adds a solar system body to an existing simulation Dataset from the JPL Horizons ephemeris service. Parameters ---------- - plname : string - Name of planet to add (e.g. "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" - date : string - Date to use when obtaining the ephemerides in the format YYYY-MM-DD. Defaults to "today" + name : str | List[str], optional + Add solar system body by name. + Currently bodies from the following list will result in fully-massive bodies (they include mass, radius, + and rotation parameters). "Sun" (added as a central body), "Mercury", "Venus", "Earth", "Mars", + "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto" + + Bodies not on this list will be added as test particles, but additional properties can be added later if + desired. + id : int | List[int], optional + Add solar system body by id number. + date : str, optional + ISO-formatted date sto use when obtaining the ephemerides in the format YYYY-MM-DD. Defaults to value + set by `set_ephemeris_date`. + origin_type : str, default "initial_conditions" + The string that will be added to the `origin_type` variable for all bodies added to the list + source : str, default "Horizons" + The source of the ephemerides. + >*Note.* Currently only the JPL Horizons ephemeris is implemented, so this is ignored. Returns ------- - self.ds : xarray dataset + ds : Xarray dataset with body or bodies added. """ - #self.ds = init_cond.solar_system_horizons(plname, idval, self.param, date, self.ds) - self.addp(*init_cond.solar_system_horizons(plname, idval, self.param, date, self.ds)) + + if self.ephemeris_date is None: + self.set_ephemeris_date() + + if date is None: + date = self.ephemeris_date + try: + datetime.datetime.fromisoformat(date) + except: + print(f"{date} is not a valid date format. Must be 'YYYY-MM-DD'. Setting to {self.ephemeris_date}") + date = self.ephemeris_date + + if source.upper() != "HORIZONS": + print("Currently only the JPL Horizons ephemeris service is supported") + + if id is not None and name is not None: + print("Warning! Requesting both id and name could lead to duplicate bodies.") + dsnew = [] + if name is not None: + if type(name) is str: + name = [name] + + if origin_type is None: + origin_type = ['initial_conditions'] * len(name) + + for n in name: + dsnew.append(self.addp(*init_cond.solar_system_horizons(n, self.param, date))) + + + if id is not None: + if type(id) is str: + id = [id] + + if origin_type is None: + origin_type = ['initial_conditions'] * len(id) + + for i in id: + dsnew.append(self.addp(*init_cond.solar_system_horizons(i, self.param, date))) + + return - - - def addp(self, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, rhill=None, Ip1=None, Ip2=None, Ip3=None, rotx=None, roty=None, rotz=None, J2=None,J4=None,t=None): + + + def set_ephemeris_date(self, + ephemeris_date: str | None = None, + verbose: bool | None = None, + **kwargs: Any): + """ + + Parameters + ---------- + ephemeris_date : str, optional + Date to use when obtaining the ephemerides. + Valid options are "today", "MBCL", or date in the format YYYY-MM-DD. + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + set_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. + + Returns + ------- + Sets the `ephemeris_date` instance variable. + + """ + + if ephemeris_date is None: + return + + + # The default value is Prof. Minton's Brimley/Cocoon line crossing date (aka MBCL) + minton_bday = datetime.date.fromisoformat('1976-08-05') + brimley_cocoon_line = datetime.timedelta(days=18530) + minton_bcl = (minton_bday + brimley_cocoon_line).isoformat() + + if ephemeris_date is None or ephemeris_date.upper() == "MBCL": + ephemeris_date = minton_bcl + elif ephemeris_date.upper() == "TODAY": + ephemeris_date = datetime.date.today().isoformat() + else: + try: + datetime.datetime.fromisoformat(ephemeris_date) + except: + valid_date_args = ['"MBCL"', '"TODAY"', '"YYYY-MM-DD"'] + print(f"{ephemeris_date} is not a valid format. Valid options include:", ', '.join(valid_date_args)) + print("Using MBCL for date.") + ephemeris_date = minton_bcl + + self.ephemeris_date = ephemeris_date + + ephemeris_date = self.get_ephemeris_date(verbose=verbose) + + return ephemeris_date + + def get_ephemeris_date(self, verbose: bool | None = None, **kwargs: Any): + """ + + Prints the current value of the ephemeris date + + Parameters + ---------- + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. + + Returns + ------- + ephemeris_date: str + The ISO-formatted date string for the ephemeris computation + + """ + if self.ephemeris_date is None: + print(f"ephemeris_date is not set") + return + + if verbose is None: + verbose = self.verbose + if verbose: + print(f"{'ephemeris_date':<{self._getter_column_width}} {self.ephemeris_date}") + + return self.ephemeris_date + + def add_body(self, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, rhill=None, Ip1=None, Ip2=None, + Ip3=None, rotx=None, roty=None, rotz=None, J2=None, J4=None, t=None): """ Adds a body (test particle or massive body) to the internal DataSet given a set up 6 vectors (orbital elements or cartesian state vectors, depending on the value of self.param). Input all angles in degress Parameters ---------- - idvals : integer - Array of body index values. - v1 : float - xh for param['IN_FORM'] == "XV"; a for param['IN_FORM'] == "EL" - v2 : float - yh for param['IN_FORM'] == "XV"; e for param['IN_FORM'] == "EL" - v3 : float - zh for param['IN_FORM'] == "XV"; inc for param['IN_FORM'] == "EL" - v4 : float - vhxh for param['IN_FORM'] == "XV"; capom for param['IN_FORM'] == "EL" - v5 : float - vhyh for param['IN_FORM'] == "XV"; omega for param['IN_FORM'] == "EL" - v6 : float - vhzh for param['IN_FORM'] == "XV"; capm for param['IN_FORM'] == "EL" - Gmass : float - Optional: Array of G*mass values if these are massive bodies - radius : float - Optional: Array radius values if these are massive bodies - rhill : float - Optional: Array rhill values if these are massive bodies - Ip1,y,z : float - Optional: Principal axes moments of inertia - rotx,y,z: float - Optional: Rotation rate vector components - t : float - Optional: Time at start of simulation + v1 : float + xh for param['IN_FORM'] == "XV"; a for param['IN_FORM'] == "EL" + v2 : float + yh for param['IN_FORM'] == "XV"; e for param['IN_FORM'] == "EL" + v3 : float + zh for param['IN_FORM'] == "XV"; inc for param['IN_FORM'] == "EL" + v4 : float + vhxh for param['IN_FORM'] == "XV"; capom for param['IN_FORM'] == "EL" + v5 : float + vhyh for param['IN_FORM'] == "XV"; omega for param['IN_FORM'] == "EL" + v6 : float + vhzh for param['IN_FORM'] == "XV"; capm for param['IN_FORM'] == "EL" + Gmass : float + Optional: Array of G*mass values if these are massive bodies + radius : float + Optional: Array radius values if these are massive bodies + rhill : float + Optional: Array rhill values if these are massive bodies + Ip1,y,z : float + Optional: Principal axes moments of inertia + rotx,y,z: float + Optional: Rotation rate vector components + t : float + Optional: Time at start of simulation + Returns ------- self.ds : xarray dataset @@ -1554,20 +1702,20 @@ def addp(self, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, rh if t is None: t = self.param['T0'] - dsnew = init_cond.vec2xr(self.param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl, Rpl, rhill, Ip1, Ip2, Ip3, rotx, roty, rotz, J2, J4, t) + dsnew = init_cond.vec2xr(self.param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl, Rpl, rhill, Ip1, Ip2, Ip3, + rotx, roty, rotz, J2, J4, t) if dsnew is not None: self.ds = xr.combine_by_coords([self.ds, dsnew]) self.ds['ntp'] = self.ds['id'].where(np.isnan(self.ds['Gmass'])).count(dim="id") self.ds['npl'] = self.ds['id'].where(np.invert(np.isnan(self.ds['Gmass']))).count(dim="id") - 1 if self.param['OUT_TYPE'] == "NETCDF_DOUBLE": - self.ds = io.fix_types(self.ds,ftype=np.float64) + self.ds = io.fix_types(self.ds, ftype=np.float64) elif self.param['OUT_TYPE'] == "NETCDF_FLOAT": - self.ds = io.fix_types(self.ds,ftype=np.float32) + self.ds = io.fix_types(self.ds, ftype=np.float32) return - - + def read_param(self, param_file, codename="Swiftest", verbose=True): """ Reads in a param.in file and determines whether it is a Swift/Swifter/Swiftest parameter file. @@ -1596,8 +1744,7 @@ def read_param(self, param_file, codename="Swiftest", verbose=True): print(f'{codename} is not a recognized code name. Valid options are "Swiftest", "Swifter", or "Swift".') self.codename = "Unknown" return - - + def write_param(self, param_file, param=None): """ Writes to a param.in file and determines whether the output format needs to be converted between Swift/Swifter/Swiftest. @@ -1618,18 +1765,19 @@ def write_param(self, param_file, param=None): if param['IN_TYPE'] == "ASCII": param.pop("NC_IN", None) else: - param.pop("CB_IN",None) - param.pop("PL_IN",None) - param.pop("TP_IN",None) + param.pop("CB_IN", None) + param.pop("PL_IN", None) + param.pop("TP_IN", None) io.write_labeled_param(param, param_file) elif codename == "Swift": io.write_swift_param(param, param_file) else: - print('Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') + print( + 'Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') return - - - def convert(self, param_file, newcodename="Swiftest", plname="pl.swiftest.in", tpname="tp.swiftest.in", cbname="cb.swiftest.in", conversion_questions={}): + + def convert(self, param_file, newcodename="Swiftest", plname="pl.swiftest.in", tpname="tp.swiftest.in", + cbname="cb.swiftest.in", conversion_questions={}): """ Converts simulation input files from one format to another (Swift, Swifter, or Swiftest). @@ -1675,14 +1823,13 @@ def convert(self, param_file, newcodename="Swiftest", plname="pl.swiftest.in", t goodconversion = False else: goodconversion = False - + if goodconversion: self.write_param(param_file) else: print(f"Conversion from {self.codename} to {newcodename} is not supported.") return oldparam - - + def bin2xr(self): """ Converts simulation output files from a flat binary file to a xarray dataset. @@ -1699,7 +1846,7 @@ def bin2xr(self): # This is done to handle cases where the method is called from a different working directory than the simulation # results param_tmp = self.param.copy() - param_tmp['BIN_OUT'] = os.path.join(self.dir_path,self.param['BIN_OUT']) + param_tmp['BIN_OUT'] = os.path.join(self.dir_path, self.param['BIN_OUT']) if self.codename == "Swiftest": self.ds = io.swiftest2xr(param_tmp, verbose=self.verbose) if self.verbose: print('Swiftest simulation data stored as xarray DataSet .ds') @@ -1709,10 +1856,10 @@ def bin2xr(self): elif self.codename == "Swift": print("Reading Swift simulation data is not implemented yet") else: - print('Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') + print( + 'Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') return - - + def follow(self, codestyle="Swifter"): """ An implementation of the Swift tool_follow algorithm. Under development. Currently only for Swift simulations. @@ -1731,10 +1878,10 @@ def follow(self, codestyle="Swifter"): if codestyle == "Swift": try: with open('follow.in', 'r') as f: - line = f.readline() # Parameter file (ignored because bin2xr already takes care of it - line = f.readline() # PL file (ignored) - line = f.readline() # TP file (ignored) - line = f.readline() # ifol + line = f.readline() # Parameter file (ignored because bin2xr already takes care of it + line = f.readline() # PL file (ignored) + line = f.readline() # TP file (ignored) + line = f.readline() # ifol i_list = [i for i in line.split(" ") if i.strip()] ifol = int(i_list[0]) line = f.readline() # nskp @@ -1747,11 +1894,10 @@ def follow(self, codestyle="Swifter"): fol = tool.follow_swift(self.ds, ifol=ifol, nskp=nskp) else: fol = None - + if self.verbose: print('follow.out written') return fol - - + def save(self, param_file, framenum=-1, codename="Swiftest"): """ Saves an xarray dataset to a set of input files. @@ -1785,7 +1931,8 @@ def save(self, param_file, framenum=-1, codename="Swiftest"): return - def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_file="param.new.in", new_initial_conditions_file="bin_in.nc", restart=False, codename="Swiftest"): + def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_file="param.new.in", + new_initial_conditions_file="bin_in.nc", restart=False, codename="Swiftest"): """ Generates a set of input files from a old output file. @@ -1809,7 +1956,6 @@ def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_fil frame : NetCDF dataset """ - if codename != "Swiftest": self.save(new_param_file, framenum, codename) return @@ -1830,7 +1976,7 @@ def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_fil if self.param['BIN_OUT'] != new_param['BIN_OUT'] and restart: print(f"Restart run with new output file. Copying {self.param['BIN_OUT']} to {new_param['BIN_OUT']}") - shutil.copy2(self.param['BIN_OUT'],new_param['BIN_OUT']) + shutil.copy2(self.param['BIN_OUT'], new_param['BIN_OUT']) new_param['IN_FORM'] = 'XV' if restart: @@ -1842,7 +1988,7 @@ def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_fil new_param.pop('TP_IN', None) new_param.pop('CB_IN', None) print(f"Extracting data from dataset at time frame number {framenum} and saving it to {new_param['NC_IN']}") - frame = io.swiftest_xr2infile(self.ds, self.param, infile_name=new_param['NC_IN'],framenum=framenum) + frame = io.swiftest_xr2infile(self.ds, self.param, infile_name=new_param['NC_IN'], framenum=framenum) print(f"Saving parameter configuration file to {new_param_file}") self.write_param(new_param_file, param=new_param) From f67fda439cc82797b0d2a105a360482b181b3acf Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Thu, 10 Nov 2022 13:58:07 -0500 Subject: [PATCH 27/75] restructured netcdf_open required variables section --- src/netcdf/netcdf.f90 | 65 ++++--------------------------------------- 1 file changed, 5 insertions(+), 60 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index e66498c35..f7a0bb0c9 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -367,51 +367,16 @@ module subroutine netcdf_open(self, param, readonly) call check( nf90_inquire_dimension(self%ncid, max(self%time_dimid,self%id_dimid)+1, name=str_dim_name), "netcdf_open nf90_inquire_dimension str_dim_name" ) call check( nf90_inq_dimid(self%ncid, str_dim_name, self%str_dimid), "netcdf_open nf90_inq_dimid str_dimid" ) + ! Required Variables + call check( nf90_inq_varid(self%ncid, TIME_DIMNAME, self%time_varid), "netcdf_open nf90_inq_varid time_varid" ) call check( nf90_inq_varid(self%ncid, ID_DIMNAME, self%id_varid), "netcdf_open nf90_inq_varid id_varid" ) call check( nf90_inq_varid(self%ncid, NAME_VARNAME, self%name_varid), "netcdf_open nf90_inq_varid name_varid" ) call check( nf90_inq_varid(self%ncid, PTYPE_VARNAME, self%ptype_varid), "netcdf_open nf90_inq_varid ptype_varid" ) call check( nf90_inq_varid(self%ncid, STATUS_VARNAME, self%status_varid), "netcdf_open nf90_inq_varid status_varid" ) call check( nf90_inq_varid(self%ncid, GMASS_VARNAME, self%Gmass_varid), "netcdf_open nf90_inq_varid Gmass_varid" ) - - if ((nf90_inq_varid(self%ncid, NPL_VARNAME, self%npl_varid) /= nf90_noerr) .or. & - (nf90_inq_varid(self%ncid, NTP_VARNAME, self%ntp_varid) /= nf90_noerr) .or. & - ((nf90_inq_varid(self%ncid, NPLM_VARNAME, self%nplm_varid) /= nf90_noerr) .and. (param%integrator == SYMBA))) then - call check( nf90_inquire_dimension(self%ncid, self%id_dimid, len=idmax), "netcdf_open nf90_inquire_dimension id_dimid" ) - allocate(gmtemp(idmax)) - call check( nf90_get_var(self%ncid, self%Gmass_varid, gmtemp, start=[1,1]), "netcdf_open nf90_getvar Gmass_varid" ) - allocate(tpmask(idmax)) - allocate(plmask(idmax)) - allocate(plmmask(idmax)) - plmask(:) = gmtemp(:) == gmtemp(:) - tpmask(:) = .not. plmask(:) - plmask(1) = .false. ! This is the central body - select type (param) - class is (symba_parameters) - plmmask(:) = gmtemp(:) > param%GMTINY .and. plmask(:) - end select - if ((nf90_inq_varid(self%ncid, NPL_VARNAME, self%npl_varid) /= nf90_noerr)) then - call check( nf90_redef(self%ncid), "netcdf_open nf90_redef npl_varid") - call check( nf90_def_var(self%ncid, NPL_VARNAME, NF90_INT, self%time_dimid, self%npl_varid), "netcdf_open nf90_def_var npl_varid" ) - call check( nf90_enddef(self%ncid), "netcdf_open nf90_enddef npl_varid") - call check( nf90_put_var(self%ncid, self%npl_varid, count(plmask(:)), start=[1]), "netcdf_open nf90_put_var npl_varid" ) - call check( nf90_inq_varid(self%ncid, NPL_VARNAME, self%npl_varid), "netcdf_open nf90_inq_varid npl_varid" ) - end if - if (nf90_inq_varid(self%ncid, NTP_VARNAME, self%ntp_varid) /= nf90_noerr) then - call check( nf90_redef(self%ncid), "netcdf_open nf90_redef ntp_varid") - call check( nf90_def_var(self%ncid, NTP_VARNAME, NF90_INT, self%time_dimid, self%ntp_varid), "netcdf_open nf90_def_var ntp_varid" ) - call check( nf90_enddef(self%ncid), "netcdf_open nf90_enddef ntp_varid") - call check( nf90_put_var(self%ncid, self%ntp_varid, count(tpmask(:)), start=[1]), "netcdf_open nf90_put_var ntp_varid" ) - call check( nf90_inq_varid(self%ncid, NTP_VARNAME, self%ntp_varid), "netcdf_open nf90_inq_varid ntp_varid" ) - end if - if ((nf90_inq_varid(self%ncid, NPLM_VARNAME, self%nplm_varid) /= nf90_noerr) .and. (param%integrator == SYMBA)) then - call check( nf90_redef(self%ncid), "netcdf_open nf90_redef nplm_varid") - call check( nf90_def_var(self%ncid, NPLM_VARNAME, NF90_INT, self%time_dimid, self%nplm_varid), "netcdf_open nf90_def_var nplm_varid" ) - call check( nf90_enddef(self%ncid), "netcdf_open nf90_enddef nplm_varid") - call check( nf90_put_var(self%ncid, self%nplm_varid, count(plmmask(:)), start=[1]), "netcdf_open nf90_put_var nplm_varid" ) - call check( nf90_inq_varid(self%ncid, NPLM_VARNAME, self%nplm_varid), "netcdf_open nf90_inq_varid nplm_varid" ) - end if - end if + call check( nf90_inq_varid(self%ncid, J2RP2_VARNAME, self%j2rp2_varid), "netcdf_open nf90_inq_varid j2rp2_varid" ) + call check( nf90_inq_varid(self%ncid, J4RP4_VARNAME, self%j4rp4_varid), "netcdf_open nf90_inq_varid j4rp4_varid" ) if ((param%out_form == XV) .or. (param%out_form == XVEL)) then call check( nf90_inq_varid(self%ncid, XHX_VARNAME, self%xhx_varid), "netcdf_open nf90_inq_varid xhx_varid" ) @@ -450,29 +415,9 @@ module subroutine netcdf_open(self, param, readonly) call check( nf90_inq_varid(self%ncid, CAPM_VARNAME, self%capm_varid), "netcdf_open nf90_inq_varid capm_varid" ) end if - if (param%lrhill_present) call check( nf90_inq_varid(self%ncid, RHILL_VARNAME, self%rhill_varid), "netcdf_open nf90_inq_varid rhill_varid" ) - if (param%lclose) then call check( nf90_inq_varid(self%ncid, RADIUS_VARNAME, self%radius_varid), "netcdf_open nf90_inq_varid radius_varid" ) - call check( nf90_inq_varid(self%ncid, ORIGIN_TYPE_VARNAME, self%origin_type_varid), "netcdf_open nf90_inq_varid origin_type_varid" ) - call check( nf90_inq_varid(self%ncid, ORIGIN_TIME_VARNAME, self%origin_time_varid), "netcdf_open nf90_inq_varid origin_time_varid" ) - call check( nf90_inq_varid(self%ncid, ORIGIN_XHX_VARNAME, self%origin_xhx_varid), "netcdf_open nf90_inq_varid origin_xhx_varid" ) - call check( nf90_inq_varid(self%ncid, ORIGIN_XHY_VARNAME, self%origin_xhy_varid), "netcdf_open nf90_inq_varid origin_xhy_varid" ) - call check( nf90_inq_varid(self%ncid, ORIGIN_XHZ_VARNAME, self%origin_xhz_varid), "netcdf_open nf90_inq_varid origin_xhz_varid" ) - call check( nf90_inq_varid(self%ncid, ORIGIN_VHX_VARNAME, self%origin_vhx_varid), "netcdf_open nf90_inq_varid origin_vhx_varid" ) - call check( nf90_inq_varid(self%ncid, ORIGIN_VHY_VARNAME, self%origin_vhy_varid), "netcdf_open nf90_inq_varid origin_vhy_varid" ) - call check( nf90_inq_varid(self%ncid, ORIGIN_VHZ_VARNAME, self%origin_vhz_varid), "netcdf_open nf90_inq_varid origin_vhz_varid" ) - - call check( nf90_inq_varid(self%ncid, COLLISION_ID_VARNAME, self%collision_id_varid), "netcdf_open nf90_inq_varid collision_id_varid" ) - call check( nf90_inq_varid(self%ncid, DISCARD_TIME_VARNAME, self%discard_time_varid), "netcdf_open nf90_inq_varid discard_time_varid" ) - call check( nf90_inq_varid(self%ncid, DISCARD_XHX_VARNAME, self%discard_xhx_varid), "netcdf_open nf90_inq_varid discard_xhx_varid" ) - call check( nf90_inq_varid(self%ncid, DISCARD_XHY_VARNAME, self%discard_xhy_varid), "netcdf_open nf90_inq_varid discard_xhy_varid" ) - call check( nf90_inq_varid(self%ncid, DISCARD_XHZ_VARNAME, self%discard_xhz_varid), "netcdf_open nf90_inq_varid discard_xhz_varid" ) - call check( nf90_inq_varid(self%ncid, DISCARD_VHX_VARNAME, self%discard_vhx_varid), "netcdf_open nf90_inq_varid discard_vhx_varid" ) - call check( nf90_inq_varid(self%ncid, DISCARD_VHY_VARNAME, self%discard_vhy_varid), "netcdf_open nf90_inq_varid discard_vhy_varid" ) - call check( nf90_inq_varid(self%ncid, DISCARD_VHZ_VARNAME, self%discard_vhz_varid), "netcdf_open nf90_inq_varid discard_vhz_varid" ) - call check( nf90_inq_varid(self%ncid, DISCARD_BODY_ID_VARNAME, self%discard_body_id_varid), "netcdf_open nf90_inq_varid discard_body_id_varid" ) - end if + end if if (param%lrotation) then call check( nf90_inq_varid(self%ncid, IP1_VARNAME, self%Ip1_varid), "netcdf_open nf90_inq_varid Ip1_varid" ) From 71d127b51542b3a7548c8a1d4e9b988c09e759d4 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Thu, 10 Nov 2022 13:59:02 -0500 Subject: [PATCH 28/75] restructured optional variables in netcdf_open --- src/netcdf/netcdf.f90 | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index f7a0bb0c9..013feae8c 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -433,17 +433,17 @@ module subroutine netcdf_open(self, param, readonly) ! call check( nf90_inq_varid(self%ncid, Q_VARNAME, self%Q_varid), "netcdf_open nf90_inq_varid Q_varid" ) ! end if - if (param%lenergy) then - call check( nf90_inq_varid(self%ncid, KE_ORB_VARNAME, self%KE_orb_varid), "netcdf_open nf90_inq_varid KE_orb_varid" ) - call check( nf90_inq_varid(self%ncid, KE_SPIN_VARNAME, self%KE_spin_varid), "netcdf_open nf90_inq_varid KE_spin_varid" ) - call check( nf90_inq_varid(self%ncid, PE_VARNAME, self%PE_varid), "netcdf_open nf90_inq_varid PE_varid" ) - call check( nf90_inq_varid(self%ncid, L_ORBX_VARNAME, self%L_orbx_varid), "netcdf_open nf90_inq_varid L_orbx_varid" ) - call check( nf90_inq_varid(self%ncid, L_ORBY_VARNAME, self%L_orby_varid), "netcdf_open nf90_inq_varid L_orby_varid" ) - call check( nf90_inq_varid(self%ncid, L_ORBZ_VARNAME, self%L_orbz_varid), "netcdf_open nf90_inq_varid L_orbz_varid" ) - call check( nf90_inq_varid(self%ncid, L_SPINX_VARNAME, self%L_spinx_varid), "netcdf_open nf90_inq_varid L_spinx_varid" ) - call check( nf90_inq_varid(self%ncid, L_SPINY_VARNAME, self%L_spiny_varid), "netcdf_open nf90_inq_varid L_spiny_varid" ) - call check( nf90_inq_varid(self%ncid, L_SPINZ_VARNAME, self%L_spinz_varid), "netcdf_open nf90_inq_varid L_spinz_varid" ) - call check( nf90_inq_varid(self%ncid, L_ESCAPEX_VARNAME, self%L_escapex_varid), "netcdf_open nf90_inq_varid L_escapex_varid" ) + ! Optional Variables + + status = nf90_inq_varid(self%ncid, NPL_VARNAME, self%npl_varid) + if (status /= nf90_noerr) write(*,*) "Warning! NPL variable not set in input file. Calculating." + + status = nf90_inq_varid(self%ncid, NTP_VARNAME, self%ntp_varid) + if (status /= nf90_noerr) write(*,*) "Warning! NTP variable not set in input file. Calculating." + + if (param%integrator == SYMBA) then + status = nf90_inq_varid(self%ncid, NPLM_VARNAME, self%nplm_varid) + if (status /= nf90_noerr) write(*,*) "Warning! NPLM variable not set in input file. Calculating." call check( nf90_inq_varid(self%ncid, L_ESCAPEY_VARNAME, self%L_escapey_varid), "netcdf_open nf90_inq_varid L_escapey_varid" ) call check( nf90_inq_varid(self%ncid, L_ESCAPEZ_VARNAME, self%L_escapez_varid), "netcdf_open nf90_inq_varid L_escapez_varid" ) call check( nf90_inq_varid(self%ncid, ECOLLISIONS_VARNAME, self%Ecollisions_varid), "netcdf_open nf90_inq_varid Ecollisions_varid" ) @@ -451,8 +451,10 @@ module subroutine netcdf_open(self, param, readonly) call check( nf90_inq_varid(self%ncid, GMESCAPE_VARNAME, self%GMescape_varid), "netcdf_open nf90_inq_varid GMescape_varid" ) end if - call check( nf90_inq_varid(self%ncid, J2RP2_VARNAME, self%j2rp2_varid), "netcdf_open nf90_inq_varid j2rp2_varid" ) - call check( nf90_inq_varid(self%ncid, J4RP4_VARNAME, self%j4rp4_varid), "netcdf_open nf90_inq_varid j4rp4_varid" ) + if (param%lrhill_present) then + status = nf90_inq_varid(self%ncid, RHILL_VARNAME, self%rhill_varid) + if (status /= nf90_noerr) write(*,*) "Warning! RHILL variable not set in input file. Calculating." + end if return From d36c9a2b69df8c040469b3cf6c8b7439d7feadef Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Thu, 10 Nov 2022 14:00:12 -0500 Subject: [PATCH 29/75] variables that the user doesn't need no longer kill the run --- src/netcdf/netcdf.f90 | 44 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 013feae8c..c2d0d6ee2 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -444,11 +444,6 @@ module subroutine netcdf_open(self, param, readonly) if (param%integrator == SYMBA) then status = nf90_inq_varid(self%ncid, NPLM_VARNAME, self%nplm_varid) if (status /= nf90_noerr) write(*,*) "Warning! NPLM variable not set in input file. Calculating." - call check( nf90_inq_varid(self%ncid, L_ESCAPEY_VARNAME, self%L_escapey_varid), "netcdf_open nf90_inq_varid L_escapey_varid" ) - call check( nf90_inq_varid(self%ncid, L_ESCAPEZ_VARNAME, self%L_escapez_varid), "netcdf_open nf90_inq_varid L_escapez_varid" ) - call check( nf90_inq_varid(self%ncid, ECOLLISIONS_VARNAME, self%Ecollisions_varid), "netcdf_open nf90_inq_varid Ecollisions_varid" ) - call check( nf90_inq_varid(self%ncid, EUNTRACKED_VARNAME, self%Euntracked_varid), "netcdf_open nf90_inq_varid Euntracked_varid" ) - call check( nf90_inq_varid(self%ncid, GMESCAPE_VARNAME, self%GMescape_varid), "netcdf_open nf90_inq_varid GMescape_varid" ) end if if (param%lrhill_present) then @@ -456,6 +451,45 @@ module subroutine netcdf_open(self, param, readonly) if (status /= nf90_noerr) write(*,*) "Warning! RHILL variable not set in input file. Calculating." end if + ! Variables The User Doesn't Need to Know About + + if (param%lclose) then + status = nf90_inq_varid(self%ncid, ORIGIN_TYPE_VARNAME, self%origin_type_varid) + status = nf90_inq_varid(self%ncid, ORIGIN_TIME_VARNAME, self%origin_time_varid) + status = nf90_inq_varid(self%ncid, ORIGIN_XHX_VARNAME, self%origin_xhx_varid) + status = nf90_inq_varid(self%ncid, ORIGIN_XHY_VARNAME, self%origin_xhy_varid) + status = nf90_inq_varid(self%ncid, ORIGIN_XHZ_VARNAME, self%origin_xhz_varid) + status = nf90_inq_varid(self%ncid, ORIGIN_VHX_VARNAME, self%origin_vhx_varid) + status = nf90_inq_varid(self%ncid, ORIGIN_VHY_VARNAME, self%origin_vhy_varid) + status = nf90_inq_varid(self%ncid, ORIGIN_VHZ_VARNAME, self%origin_vhz_varid) + status = nf90_inq_varid(self%ncid, COLLISION_ID_VARNAME, self%collision_id_varid) + status = nf90_inq_varid(self%ncid, DISCARD_TIME_VARNAME, self%discard_time_varid) + status = nf90_inq_varid(self%ncid, DISCARD_XHX_VARNAME, self%discard_xhx_varid) + status = nf90_inq_varid(self%ncid, DISCARD_XHY_VARNAME, self%discard_xhy_varid) + status = nf90_inq_varid(self%ncid, DISCARD_XHZ_VARNAME, self%discard_xhz_varid) + status = nf90_inq_varid(self%ncid, DISCARD_VHX_VARNAME, self%discard_vhx_varid) + status = nf90_inq_varid(self%ncid, DISCARD_VHY_VARNAME, self%discard_vhy_varid) + status = nf90_inq_varid(self%ncid, DISCARD_VHZ_VARNAME, self%discard_vhz_varid) + status = nf90_inq_varid(self%ncid, DISCARD_BODY_ID_VARNAME, self%discard_body_id_varid) + end if + + if (param%lenergy) then + status = nf90_inq_varid(self%ncid, KE_ORB_VARNAME, self%KE_orb_varid) + status = nf90_inq_varid(self%ncid, KE_SPIN_VARNAME, self%KE_spin_varid) + status = nf90_inq_varid(self%ncid, PE_VARNAME, self%PE_varid) + status = nf90_inq_varid(self%ncid, L_ORBX_VARNAME, self%L_orbx_varid) + status = nf90_inq_varid(self%ncid, L_ORBY_VARNAME, self%L_orby_varid) + status = nf90_inq_varid(self%ncid, L_ORBZ_VARNAME, self%L_orbz_varid) + status = nf90_inq_varid(self%ncid, L_SPINX_VARNAME, self%L_spinx_varid) + status = nf90_inq_varid(self%ncid, L_SPINY_VARNAME, self%L_spiny_varid) + status = nf90_inq_varid(self%ncid, L_SPINZ_VARNAME, self%L_spinz_varid) + status = nf90_inq_varid(self%ncid, L_ESCAPEX_VARNAME, self%L_escapex_varid) + status = nf90_inq_varid(self%ncid, L_ESCAPEY_VARNAME, self%L_escapey_varid) + status = nf90_inq_varid(self%ncid, L_ESCAPEZ_VARNAME, self%L_escapez_varid) + status = nf90_inq_varid(self%ncid, ECOLLISIONS_VARNAME, self%Ecollisions_varid) + status = nf90_inq_varid(self%ncid, EUNTRACKED_VARNAME, self%Euntracked_varid) + status = nf90_inq_varid(self%ncid, GMESCAPE_VARNAME, self%GMescape_varid) + end if return end subroutine netcdf_open From 58ac545b3ed97185d64c1364c2b7b853612fce82 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Thu, 10 Nov 2022 14:00:35 -0500 Subject: [PATCH 30/75] deleted unused variables --- src/netcdf/netcdf.f90 | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index c2d0d6ee2..0a9e7cfd8 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -351,9 +351,6 @@ module subroutine netcdf_open(self, param, readonly) ! Internals integer(I4B) :: mode, status character(len=NF90_MAX_NAME) :: str_dim_name - integer(I4B) :: idmax - real(DP), dimension(:), allocatable :: gmtemp - logical, dimension(:), allocatable :: tpmask, plmask, plmmask mode = NF90_WRITE !if (present(readonly)) then From 3ca0ffd274585242cafb3aff937cf2e689043d39 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Thu, 10 Nov 2022 14:00:58 -0500 Subject: [PATCH 31/75] added NF90_NOWRITE check back in --- src/netcdf/netcdf.f90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 0a9e7cfd8..c36408a72 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -353,9 +353,9 @@ module subroutine netcdf_open(self, param, readonly) character(len=NF90_MAX_NAME) :: str_dim_name mode = NF90_WRITE - !if (present(readonly)) then - ! if (readonly) mode = NF90_NOWRITE - !end if + if (present(readonly)) then + if (readonly) mode = NF90_NOWRITE + end if call check( nf90_open(param%outfile, mode, self%ncid), "netcdf_open nf90_open" ) From 9f6337c062eebe12fc5ad93a917dbd9bd8775b94 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Thu, 10 Nov 2022 14:05:29 -0500 Subject: [PATCH 32/75] for variables that have built-in defaults, only get variable from netcdf if provided --- src/netcdf/netcdf.f90 | 45 ++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index c36408a72..60e17fa0c 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -767,21 +767,36 @@ module subroutine netcdf_read_hdr_system(self, iu, param) end select if (param%lenergy) then - call check( nf90_get_var(iu%ncid, iu%KE_orb_varid, self%ke_orbit, start=[tslot]), "netcdf_read_hdr_system nf90_getvar KE_orb_varid" ) - call check( nf90_get_var(iu%ncid, iu%KE_spin_varid, self%ke_spin, start=[tslot]), "netcdf_read_hdr_system nf90_getvar KE_spin_varid" ) - call check( nf90_get_var(iu%ncid, iu%PE_varid, self%pe, start=[tslot]), "netcdf_read_hdr_system nf90_getvar PE_varid" ) - call check( nf90_get_var(iu%ncid, iu%L_orbx_varid, self%Lorbit(1), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_orbx_varid" ) - call check( nf90_get_var(iu%ncid, iu%L_orby_varid, self%Lorbit(2), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_orby_varid" ) - call check( nf90_get_var(iu%ncid, iu%L_orbz_varid, self%Lorbit(3), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_orbz_varid" ) - call check( nf90_get_var(iu%ncid, iu%L_spinx_varid, self%Lspin(1), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_spinx_varid" ) - call check( nf90_get_var(iu%ncid, iu%L_spiny_varid, self%Lspin(2), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_spiny_varid" ) - call check( nf90_get_var(iu%ncid, iu%L_spinz_varid, self%Lspin(3), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_spinz_varid" ) - call check( nf90_get_var(iu%ncid, iu%L_escapex_varid, self%Lescape(1), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_escapex_varid" ) - call check( nf90_get_var(iu%ncid, iu%L_escapey_varid, self%Lescape(2), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_escapey_varid" ) - call check( nf90_get_var(iu%ncid, iu%L_escapez_varid, self%Lescape(3), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_escapez_varid" ) - call check( nf90_get_var(iu%ncid, iu%Ecollisions_varid, self%Ecollisions, start=[tslot]), "netcdf_read_hdr_system nf90_getvar Ecollisions_varid" ) - call check( nf90_get_var(iu%ncid, iu%Euntracked_varid, self%Euntracked, start=[tslot]), "netcdf_read_hdr_system nf90_getvar Euntracked_varid" ) - call check( nf90_get_var(iu%ncid, iu%GMescape_varid, self%GMescape, start=[tslot]), "netcdf_read_hdr_system nf90_getvar GMescape_varid" ) + status = nf90_inq_varid(iu%ncid, KE_ORB_VARNAME, iu%KE_orb_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%KE_orb_varid, self%ke_orbit, start=[tslot]), "netcdf_read_hdr_system nf90_getvar KE_orb_varid" ) + status = nf90_inq_varid(iu%ncid, KE_SPIN_VARNAME, iu%KE_spin_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%KE_spin_varid, self%ke_spin, start=[tslot]), "netcdf_read_hdr_system nf90_getvar KE_spin_varid" ) + status = nf90_inq_varid(iu%ncid, PE_VARNAME, iu%PE_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%PE_varid, self%pe, start=[tslot]), "netcdf_read_hdr_system nf90_getvar PE_varid" ) + status = nf90_inq_varid(iu%ncid, L_ORBX_VARNAME, iu%L_orbx_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%L_orbx_varid, self%Lorbit(1), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_orbx_varid" ) + status = nf90_inq_varid(iu%ncid, L_ORBY_VARNAME, iu%L_orby_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%L_orby_varid, self%Lorbit(2), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_orby_varid" ) + status = nf90_inq_varid(iu%ncid, L_ORBZ_VARNAME, iu%L_orbz_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%L_orbz_varid, self%Lorbit(3), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_orbz_varid" ) + status = nf90_inq_varid(iu%ncid, L_SPINX_VARNAME, iu%L_spinx_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%L_spinx_varid, self%Lspin(1), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_spinx_varid" ) + status = nf90_inq_varid(iu%ncid, L_SPINY_VARNAME, iu%L_spiny_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%L_spiny_varid, self%Lspin(2), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_spiny_varid" ) + status = nf90_inq_varid(iu%ncid, L_SPINZ_VARNAME, iu%L_spinz_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%L_spinz_varid, self%Lspin(3), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_spinz_varid" ) + status = nf90_inq_varid(iu%ncid, L_ESCAPEX_VARNAME, iu%L_escapex_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%L_escapex_varid, self%Lescape(1), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_escapex_varid" ) + status = nf90_inq_varid(iu%ncid, L_ESCAPEY_VARNAME, iu%L_escapey_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%L_escapey_varid, self%Lescape(2), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_escapey_varid" ) + status = nf90_inq_varid(iu%ncid, L_ESCAPEZ_VARNAME, iu%L_escapez_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%L_escapez_varid, self%Lescape(3), start=[tslot]), "netcdf_read_hdr_system nf90_getvar L_escapez_varid" ) + status = nf90_inq_varid(iu%ncid, ECOLLISIONS_VARNAME, iu%Ecollisions_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%Ecollisions_varid, self%Ecollisions, start=[tslot]), "netcdf_read_hdr_system nf90_getvar Ecollisions_varid" ) + status = nf90_inq_varid(iu%ncid, EUNTRACKED_VARNAME, iu%Euntracked_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%Euntracked_varid, self%Euntracked, start=[tslot]), "netcdf_read_hdr_system nf90_getvar Euntracked_varid" ) + status = nf90_inq_varid(iu%ncid, GMESCAPE_VARNAME, iu%GMescape_varid) + if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%GMescape_varid, self%GMescape, start=[tslot]), "netcdf_read_hdr_system nf90_getvar GMescape_varid" ) end if return From ecdbcf42498b8eb34a60741890334c700cf3cf48 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Thu, 10 Nov 2022 14:08:26 -0500 Subject: [PATCH 33/75] only get npl, nplm, ntp if provided in netcdf, otherwise calculate --- src/netcdf/netcdf.f90 | 44 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 60e17fa0c..e31ed0f4f 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -757,15 +757,47 @@ module subroutine netcdf_read_hdr_system(self, iu, param) integer(I4B) :: tslot tslot = int(param%ioutput, kind=I4B) + 1 - + call check( nf90_inquire_dimension(iu%ncid, iu%id_dimid, len=idmax), "netcdf_read_frame_system nf90_inquire_dimension id_dimid" ) call check( nf90_get_var(iu%ncid, iu%time_varid, param%t, start=[tslot]), "netcdf_read_hdr_system nf90_getvar time_varid" ) - call check( nf90_get_var(iu%ncid, iu%npl_varid, self%pl%nbody, start=[tslot]), "netcdf_read_hdr_system nf90_getvar npl_varid" ) - call check( nf90_get_var(iu%ncid, iu%ntp_varid, self%tp%nbody, start=[tslot]), "netcdf_read_hdr_system nf90_getvar ntp_varid" ) - select type(pl => self%pl) - class is (symba_pl) - call check( nf90_get_var(iu%ncid, iu%nplm_varid, pl%nplm, start=[tslot]), "netcdf_read_hdr_system nf90_getvar nplm_varid" ) + call check( nf90_get_var(iu%ncid, iu%Gmass_varid, gmtemp, start=[1,1]), "netcdf_read_frame_system nf90_getvar Gmass_varid" ) + allocate(gmtemp(idmax)) + allocate(tpmask(idmax)) + allocate(plmask(idmax)) + allocate(plmmask(idmax)) + plmask(:) = gmtemp(:) == gmtemp(:) + tpmask(:) = .not. plmask(:) + plmask(1) = .false. ! This is the central body + select type (param) + class is (symba_parameters) + plmmask(:) = gmtemp(:) > param%GMTINY .and. plmask(:) end select + status = nf90_inq_varid(iu%ncid, NPL_VARNAME, iu%npl_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%npl_varid, self%pl%nbody, start=[tslot]), "netcdf_read_hdr_system nf90_getvar npl_varid" ) + else + self%pl%nbody = count(plmask(:)) + end if + + status = nf90_inq_varid(iu%ncid, NTP_VARNAME, iu%ntp_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%ntp_varid, self%tp%nbody, start=[tslot]), "netcdf_read_hdr_system nf90_getvar ntp_varid" ) + else + self%tp%nbody = count(tpmask(:)) + end if + + if (param%integrator == SYMBA) then + status = nf90_inq_varid(iu%ncid, NPLM_VARNAME, iu%nplm_varid) + select type(pl => self%pl) + class is (symba_pl) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%nplm_varid, pl%nplm, start=[tslot]), "netcdf_read_hdr_system nf90_getvar nplm_varid" ) + else + pl%nplm = count(plmmask(:)) + end if + end select + end if + if (param%lenergy) then status = nf90_inq_varid(iu%ncid, KE_ORB_VARNAME, iu%KE_orb_varid) if (status == nf90_noerr) call check( nf90_get_var(iu%ncid, iu%KE_orb_varid, self%ke_orbit, start=[tslot]), "netcdf_read_hdr_system nf90_getvar KE_orb_varid" ) From 4ed41f19a566708f4b3cf0d496e3dfe3e89becf5 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Thu, 10 Nov 2022 14:08:47 -0500 Subject: [PATCH 34/75] add new internal variables --- src/netcdf/netcdf.f90 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index e31ed0f4f..ceb8d6f7b 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -754,7 +754,10 @@ module subroutine netcdf_read_hdr_system(self, iu, param) class(netcdf_parameters), intent(inout) :: iu !! Parameters used to for writing a NetCDF dataset to file class(swiftest_parameters), intent(inout) :: param !! Current run configuration parameters ! Internals - integer(I4B) :: tslot + integer(I4B) :: tslot, status, idmax + real(DP), dimension(:), allocatable :: gmtemp + logical, dimension(:), allocatable :: plmask, tpmask, plmmask + tslot = int(param%ioutput, kind=I4B) + 1 call check( nf90_inquire_dimension(iu%ncid, iu%id_dimid, len=idmax), "netcdf_read_frame_system nf90_inquire_dimension id_dimid" ) From 27f6c00cc737b80c0d20558ac1ca44b3f0cc9e69 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Thu, 10 Nov 2022 15:39:55 -0500 Subject: [PATCH 35/75] check if all optional variables exist, if they do then get them from the netcdf, if they don't set them equal to the default or some reasonable value --- src/netcdf/netcdf.f90 | 138 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 118 insertions(+), 20 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index ceb8d6f7b..788f58beb 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -850,12 +850,12 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma logical, dimension(:), intent(in) :: plmask !! Logical array indicating which index values belong to massive bodies logical, dimension(:), intent(in) :: tpmask !! Logical array indicating which index values belong to test particles ! Internals - integer(I4B) :: i, idmax - real(DP), dimension(:), allocatable :: rtemp + integer(I4B) :: i, idmax, status + real(DP), dimension(:), allocatable :: rtemp real(DP), dimension(:,:), allocatable :: rtemp_arr - integer(I4B), dimension(:), allocatable :: itemp + integer(I4B), dimension(:), allocatable :: itemp character(len=NAMELEN), dimension(:), allocatable :: ctemp - integer(I4B), dimension(:), allocatable :: plind, tpind + integer(I4B), dimension(:), allocatable :: plind, tpind ! This string of spaces of length NAMELEN is used to clear out any old data left behind inside the string variables idmax = size(plmask) @@ -918,7 +918,14 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma end do if (param%lclose) then - call check( nf90_get_var(iu%ncid, iu%origin_type_varid, ctemp, count=[NAMELEN, idmax]), "netcdf_read_particle_info_system nf90_getvar origin_type_varid" ) + + status = nf90_inq_varid(iu%ncid, ORIGIN_TYPE_VARNAME, iu%origin_type_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%origin_type_varid, ctemp, count=[NAMELEN, idmax]), "netcdf_read_particle_info_system nf90_getvar origin_type_varid" ) + else + ctemp = "Initial Conditions" + end if + call cb%info%set_value(origin_type=ctemp(1)) do i = 1, npl call pl%info(i)%set_value(origin_type=ctemp(plind(i))) @@ -927,7 +934,13 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma call tp%info(i)%set_value(origin_type=ctemp(tpind(i))) end do - call check( nf90_get_var(iu%ncid, iu%origin_time_varid, rtemp), "netcdf_read_particle_info_system nf90_getvar origin_time_varid" ) + status = nf90_inq_varid(iu%ncid, ORIGIN_TIME_VARNAME, iu%origin_time_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%origin_time_varid, rtemp), "netcdf_read_particle_info_system nf90_getvar origin_time_varid" ) + else + rtemp = 0.0_DP + end if + call cb%info%set_value(origin_time=rtemp(1)) do i = 1, npl call pl%info(i)%set_value(origin_time=rtemp(plind(i))) @@ -936,9 +949,28 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma call tp%info(i)%set_value(origin_time=rtemp(tpind(i))) end do - call check( nf90_get_var(iu%ncid, iu%origin_xhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar origin_xhx_varid" ) - call check( nf90_get_var(iu%ncid, iu%origin_xhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar origin_xhy_varid" ) - call check( nf90_get_var(iu%ncid, iu%origin_xhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar origin_xhz_varid" ) + + status = nf90_inq_varid(iu%ncid, ORIGIN_XHX_VARNAME, iu%origin_xhx_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%origin_xhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar origin_xhx_varid" ) + else + call check( nf90_get_var(iu%ncid, iu%xhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar xhx_varid" ) + end if + + status = nf90_inq_varid(iu%ncid, ORIGIN_XHY_VARNAME, iu%origin_xhy_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%origin_xhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar origin_xhy_varid" ) + else + call check( nf90_get_var(iu%ncid, iu%xhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar xhy_varid" ) + end if + + status = nf90_inq_varid(iu%ncid, ORIGIN_XHZ_VARNAME, iu%origin_xhz_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%origin_xhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar origin_xhz_varid" ) + else + call check( nf90_get_var(iu%ncid, iu%xhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar xhz_varid" ) + end if + do i = 1, npl call pl%info(i)%set_value(origin_xh=rtemp_arr(:,plind(i))) end do @@ -946,9 +978,27 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma call tp%info(i)%set_value(origin_xh=rtemp_arr(:,tpind(i))) end do - call check( nf90_get_var(iu%ncid, iu%origin_vhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar origin_vhx_varid" ) - call check( nf90_get_var(iu%ncid, iu%origin_vhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar origin_vhy_varid" ) - call check( nf90_get_var(iu%ncid, iu%origin_vhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar origin_vhz_varid" ) + status = nf90_inq_varid(iu%ncid, ORIGIN_VHX_VARNAME, iu%origin_vhx_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%origin_vhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar origin_vhx_varid" ) + else + call check( nf90_get_var(iu%ncid, iu%vhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar vhx_varid" ) + end if + + status = nf90_inq_varid(iu%ncid, ORIGIN_VHY_VARNAME, iu%origin_vhy_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%origin_vhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar origin_vhy_varid" ) + else + call check( nf90_get_var(iu%ncid, iu%vhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar vhy_varid" ) + end if + + status = nf90_inq_varid(iu%ncid, ORIGIN_VHZ_VARNAME, iu%origin_vhz_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%origin_vhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar origin_vhz_varid" ) + else + call check( nf90_get_var(iu%ncid, iu%vhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar vhz_varid" ) + end if + do i = 1, npl call pl%info(i)%set_value(origin_vh=rtemp_arr(:,plind(i))) end do @@ -956,7 +1006,13 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma call tp%info(i)%set_value(origin_vh=rtemp_arr(:,tpind(i))) end do - call check( nf90_get_var(iu%ncid, iu%collision_id_varid, itemp), "netcdf_read_particle_info_system nf90_getvar collision_id_varid" ) + status = nf90_inq_varid(iu%ncid, COLLISION_ID_VARNAME, iu%collision_id_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%collision_id_varid, itemp), "netcdf_read_particle_info_system nf90_getvar collision_id_varid" ) + else + itemp = 0.0_DP + end if + do i = 1, npl call pl%info(i)%set_value(collision_id=itemp(plind(i))) end do @@ -964,7 +1020,13 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma call tp%info(i)%set_value(collision_id=itemp(tpind(i))) end do - call check( nf90_get_var(iu%ncid, iu%discard_time_varid, rtemp), "netcdf_read_particle_info_system nf90_getvar discard_time_varid" ) + status = nf90_inq_varid(iu%ncid, DISCARD_TIME_VARNAME, iu%discard_time_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%discard_time_varid, rtemp), "netcdf_read_particle_info_system nf90_getvar discard_time_varid" ) + else + rtemp = 0.0_DP + end if + call cb%info%set_value(discard_time=rtemp(1)) do i = 1, npl call pl%info(i)%set_value(discard_time=rtemp(plind(i))) @@ -973,9 +1035,27 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma call tp%info(i)%set_value(discard_time=rtemp(tpind(i))) end do - call check( nf90_get_var(iu%ncid, iu%discard_xhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar discard_xhx_varid" ) - call check( nf90_get_var(iu%ncid, iu%discard_xhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar discard_xhy_varid" ) - call check( nf90_get_var(iu%ncid, iu%discard_xhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar discard_xhz_varid" ) + status = nf90_inq_varid(iu%ncid, DISCARD_XHX_VARNAME, iu%discard_xhx_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%discard_xhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar discard_xhx_varid" ) + else + rtemp_arr(1,:) = 0.0_DP + end if + + status = nf90_inq_varid(iu%ncid, DISCARD_XHY_VARNAME, iu%discard_xhy_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%discard_xhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar discard_xhy_varid" ) + else + rtemp_arr(2,:) = 0.0_DP + end if + + status = nf90_inq_varid(iu%ncid, DISCARD_XHZ_VARNAME, iu%discard_xhz_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%discard_xhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar discard_xhz_varid" ) + else + rtemp_arr(3,:) = 0.0_DP + end if + do i = 1, npl call pl%info(i)%set_value(discard_xh=rtemp_arr(:,plind(i))) end do @@ -983,9 +1063,27 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma call tp%info(i)%set_value(discard_xh=rtemp_arr(:,tpind(i))) end do - call check( nf90_get_var(iu%ncid, iu%discard_vhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar discard_vhx_varid" ) - call check( nf90_get_var(iu%ncid, iu%discard_vhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar discard_vhy_varid" ) - call check( nf90_get_var(iu%ncid, iu%discard_vhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar discard_vhz_varid" ) + status = nf90_inq_varid(iu%ncid, DISCARD_VHX_VARNAME, iu%discard_vhx_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%discard_vhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar discard_vhx_varid" ) + else + rtemp_arr(1,:) = 0.0_DP + end if + + status = nf90_inq_varid(iu%ncid, DISCARD_VHY_VARNAME, iu%discard_vhy_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%discard_vhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar discard_vhy_varid" ) + else + rtemp_arr(2,:) = 0.0_DP + end if + + status = nf90_inq_varid(iu%ncid, DISCARD_VHZ_VARNAME, iu%discard_vhz_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%discard_vhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar discard_vhz_varid" ) + else + rtemp_arr(3,:) = 0.0_DP + end if + do i = 1, npl call pl%info(i)%set_value(discard_vh=rtemp_arr(:,plind(i))) end do From d07c603f832e78237b2680cc865560502f0032d5 Mon Sep 17 00:00:00 2001 From: David Minton Date: Thu, 10 Nov 2022 18:10:06 -0500 Subject: [PATCH 36/75] Made improvements to the solar system and body add methods. Still not quite finished, but getting close. --- python/swiftest/swiftest/init_cond.py | 222 ++++++-------- python/swiftest/swiftest/simulation_class.py | 294 +++++++++++++------ 2 files changed, 306 insertions(+), 210 deletions(-) diff --git a/python/swiftest/swiftest/init_cond.py b/python/swiftest/swiftest/init_cond.py index 641fa2b68..c90f1d59b 100644 --- a/python/swiftest/swiftest/init_cond.py +++ b/python/swiftest/swiftest/init_cond.py @@ -8,17 +8,26 @@ You should have received a copy of the GNU General Public License along with Swiftest. If not, see: https://www.gnu.org/licenses. """ +from __future__ import annotations import swiftest import numpy as np +import numpy.typing as npt from astroquery.jplhorizons import Horizons import astropy.units as u from astropy.coordinates import SkyCoord import datetime -from datetime import date import xarray as xr - -def solar_system_horizons(plname, idval, param, ephemerides_start_date): +from typing import ( + Literal, + Dict, + List, + Any +) +def solar_system_horizons(plname: str, + param: Dict, + ephemerides_start_date: str, + idval: int | None = None): """ Initializes a Swiftest dataset containing the major planets of the Solar System at a particular data from JPL/Horizons @@ -118,10 +127,10 @@ def solar_system_horizons(plname, idval, param, ephemerides_start_date): THIRDLONG = np.longdouble(1.0) / np.longdouble(3.0) # Central body value vectors - GMcb = np.array([swiftest.GMSun * param['TU2S'] ** 2 / param['DU2M'] ** 3]) - Rcb = np.array([swiftest.RSun / param['DU2M']]) - J2RP2 = np.array([swiftest.J2Sun * (swiftest.RSun / param['DU2M']) ** 2]) - J4RP4 = np.array([swiftest.J4Sun * (swiftest.RSun / param['DU2M']) ** 4]) + GMcb = swiftest.GMSun * param['TU2S'] ** 2 / param['DU2M'] ** 3 + Rcb = swiftest.RSun / param['DU2M'] + J2RP2 = swiftest.J2Sun * (swiftest.RSun / param['DU2M']) ** 2 + J4RP4 = swiftest.J4Sun * (swiftest.RSun / param['DU2M']) ** 4 solarpole = SkyCoord(ra=286.13 * u.degree, dec=63.87 * u.degree) solarrot = planetrot['Sun'] * param['TU2S'] @@ -145,12 +154,12 @@ def solar_system_horizons(plname, idval, param, ephemerides_start_date): J2 = J2RP2 J4 = J4RP4 if param['ROTATION']: - Ip1 = [Ipsun[0]] - Ip2 = [Ipsun[1]] - Ip3 = [Ipsun[2]] - rotx = [rotcb.x] - roty = [rotcb.y] - rotz = [rotcb.z] + Ip1 = Ipsun[0] + Ip2 = Ipsun[1] + Ip3 = Ipsun[2] + rotx = rotcb.x.value + roty = rotcb.y.value + rotz = rotcb.z.value else: Ip1 = None Ip2 = None @@ -168,12 +177,6 @@ def solar_system_horizons(plname, idval, param, ephemerides_start_date): ephemerides_end_date = tend.isoformat() ephemerides_step = '1d' - v1 = [] - v2 = [] - v3 = [] - v4 = [] - v5 = [] - v6 = [] J2 = None J4 = None @@ -183,42 +186,33 @@ def solar_system_horizons(plname, idval, param, ephemerides_start_date): 'step': ephemerides_step}) if param['IN_FORM'] == 'XV': - v1.append(pldata[plname].vectors()['x'][0] * DCONV) - v2.append(pldata[plname].vectors()['y'][0] * DCONV) - v3.append(pldata[plname].vectors()['z'][0] * DCONV) - v4.append(pldata[plname].vectors()['vx'][0] * VCONV) - v5.append(pldata[plname].vectors()['vy'][0] * VCONV) - v6.append(pldata[plname].vectors()['vz'][0] * VCONV) + v1 = pldata[plname].vectors()['x'][0] * DCONV + v2 = pldata[plname].vectors()['y'][0] * DCONV + v3 = pldata[plname].vectors()['z'][0] * DCONV + v4 = pldata[plname].vectors()['vx'][0] * VCONV + v5 = pldata[plname].vectors()['vy'][0] * VCONV + v6 = pldata[plname].vectors()['vz'][0] * VCONV elif param['IN_FORM'] == 'EL': - v1.append(pldata[plname].elements()['a'][0] * DCONV) - v2.append(pldata[plname].elements()['e'][0]) - v3.append(pldata[plname].elements()['incl'][0]) - v4.append(pldata[plname].elements()['Omega'][0]) - v5.append(pldata[plname].elements()['w'][0]) - v6.append(pldata[plname].elements()['M'][0]) + v1 = pldata[plname].elements()['a'][0] * DCONV + v2 = pldata[plname].elements()['e'][0] + v3 = pldata[plname].elements()['incl'][0] + v4 = pldata[plname].elements()['Omega'][0] + v5 = pldata[plname].elements()['w'][0] + v6 = pldata[plname].elements()['M'][0] if ispl: - GMpl = [] - GMpl.append(GMcb[0] / MSun_over_Mpl[plname]) + GMpl = GMcb / MSun_over_Mpl[plname] if param['CHK_CLOSE']: - Rpl = [] - Rpl.append(planetradius[plname] * DCONV) + Rpl = planetradius[plname] * DCONV else: Rpl = None # Generate planet value vectors if (param['RHILL_PRESENT']): - rhill = [] - rhill.append(pldata[plname].elements()['a'][0] * DCONV * (3 * MSun_over_Mpl[plname]) ** (-THIRDLONG)) + rhill = pldata[plname].elements()['a'][0] * DCONV * (3 * MSun_over_Mpl[plname]) ** (-THIRDLONG) else: rhill = None if (param['ROTATION']): - Ip1 = [] - Ip2 = [] - Ip3 = [] - rotx = [] - roty = [] - rotz = [] RA = pldata[plname].ephemerides()['NPole_RA'][0] DEC = pldata[plname].ephemerides()['NPole_DEC'][0] @@ -226,12 +220,12 @@ def solar_system_horizons(plname, idval, param, ephemerides_start_date): rotrate = planetrot[plname] * param['TU2S'] rot = rotpole.cartesian * rotrate Ip = np.array([0.0, 0.0, planetIpz[plname]]) - Ip1.append(Ip[0]) - Ip2.append(Ip[1]) - Ip3.append(Ip[2]) - rotx.append(rot.x) - roty.append(rot.y) - rotz.append(rot.z) + Ip1 = Ip[0] + Ip2 = Ip[1] + Ip3 = Ip[2] + rotx = rot.x.value + roty = rot.y.value + rotz = rot.z.value else: Ip1 = None Ip2 = None @@ -243,13 +237,33 @@ def solar_system_horizons(plname, idval, param, ephemerides_start_date): GMpl = None if idval is None: - plid = np.array([planetid[plname]], dtype=int) + plid = planetid[plname] else: - plid = np.array([idval], dtype=int) + plid = idval - return plid,[plname],v1,v2,v3,v4,v5,v6,GMpl,Rpl,rhill,Ip1,Ip2,Ip3,rotx,roty,rotz,J2,J4 + return plname,v1,v2,v3,v4,v5,v6,idval,GMpl,Rpl,rhill,Ip1,Ip2,Ip3,rotx,roty,rotz,J2,J4 -def vec2xr(param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, rhill=None, Ip1=None, Ip2=None, Ip3=None, rotx=None, roty=None, rotz=None, J2=None, J4=None,t=0.0): +def vec2xr(param: Dict, + namevals: npt.NDArray[np.str_], + v1: npt.NDArray[np.float_], + v2: npt.NDArray[np.float_], + v3: npt.NDArray[np.float_], + v4: npt.NDArray[np.float_], + v5: npt.NDArray[np.float_], + v6: npt.NDArray[np.float_], + idvals: npt.NDArray[np.int_], + GMpl: npt.NDArray[np.float_] | None=None, + Rpl: npt.NDArray[np.float_] | None=None, + rhill: npt.NDArray[np.float_] | None=None, + Ip1: npt.NDArray[np.float_] | None=None, + Ip2: npt.NDArray[np.float_] | None=None, + Ip3: npt.NDArray[np.float_] | None=None, + rotx: npt.NDArray[np.float_] | None=None, + roty: npt.NDArray[np.float_] | None=None, + rotz: npt.NDArray[np.float_] | None=None, + J2: npt.NDArray[np.float_] | None=None, + J4: npt.NDArray[np.float_] | None=None, + t: float=0.0): """ Converts and stores the variables of all bodies in an xarray dataset. @@ -297,11 +311,6 @@ def vec2xr(param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, ------- ds : xarray dataset """ - if v1 is None: # This is the central body - iscb = True - else: - iscb = False - if param['ROTATION']: if Ip1 is None: Ip1 = np.full_like(v1, 0.4) @@ -318,11 +327,18 @@ def vec2xr(param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, dims = ['time', 'id', 'vec'] infodims = ['id', 'vec'] - if not iscb and GMpl is not None: + + # The central body is always given id 0 + icb = idvals == 0 + iscb = any(icb) + + if GMpl is not None: ispl = True + ipl = ~np.isnan(GMpl) + itp = np.isnan(GMpl) else: ispl = False - + if ispl and param['CHK_CLOSE'] and Rpl is None: print("Massive bodies need a radius value.") return None @@ -337,83 +353,33 @@ def vec2xr(param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, param['OUT_FORM'] = old_out_form vec_str = np.vstack([namevals]) label_str = ["name"] + particle_type = np.empty_like(namevals) if iscb: label_float = clab.copy() - vec_float = np.vstack([GMpl,Rpl,J2,J4]) + vec_float = np.vstack([GMpl[icb],Rpl[icb],J2[icb],J4[icb]]) + if param['ROTATION']: + vec_float = np.vstack([vec_float, Ip1[icb], Ip2[icb], Ip3[icb], rotx[icb], roty[icb], rotz[icb]]) + particle_type[icb] = "Central Body" + # vec_float = np.vstack([v1, v2, v3, v4, v5, v6]) + if ispl: + label_float = plab.copy() + vec_float = np.vstack([vec_float, GMpl]) + if param['CHK_CLOSE']: + vec_float = np.vstack([vec_float, Rpl]) + if param['RHILL_PRESENT']: + vec_float = np.vstack([vec_float, rhill]) if param['ROTATION']: vec_float = np.vstack([vec_float, Ip1, Ip2, Ip3, rotx, roty, rotz]) - particle_type = "Central Body" + particle_type[ipl] = np.repeat("Massive Body",idvals.size) else: - vec_float = np.vstack([v1, v2, v3, v4, v5, v6]) - if ispl: - label_float = plab.copy() - vec_float = np.vstack([vec_float, GMpl]) - if param['CHK_CLOSE']: - vec_float = np.vstack([vec_float, Rpl]) - if param['RHILL_PRESENT']: - vec_float = np.vstack([vec_float, rhill]) - if param['ROTATION']: - vec_float = np.vstack([vec_float, Ip1, Ip2, Ip3, rotx, roty, rotz]) - particle_type = np.repeat("Massive Body",idvals.size) - else: - label_float = tlab.copy() - particle_type = np.repeat("Test Particle",idvals.size) - origin_type = np.repeat("User Added Body",idvals.size) - origin_time = np.full_like(v1,t) - collision_id = np.full_like(idvals,0) - origin_xhx = v1 - origin_xhy = v2 - origin_xhz = v3 - origin_vhx = v4 - origin_vhy = v5 - origin_vhz = v6 - discard_time = np.full_like(v1,-1.0) - status = np.repeat("ACTIVE",idvals.size) - discard_xhx = np.zeros_like(v1) - discard_xhy = np.zeros_like(v1) - discard_xhz = np.zeros_like(v1) - discard_vhx = np.zeros_like(v1) - discard_vhy = np.zeros_like(v1) - discard_vhz = np.zeros_like(v1) - discard_body_id = np.full_like(idvals,-1) - info_vec_float = np.vstack([ - origin_time, - origin_xhx, - origin_xhy, - origin_xhz, - origin_vhx, - origin_vhy, - origin_vhz, - discard_time, - discard_xhx, - discard_xhy, - discard_xhz, - discard_vhx, - discard_vhy, - discard_vhz]) - info_vec_int = np.vstack([collision_id, discard_body_id]) - info_vec_str = np.vstack([particle_type, origin_type, status]) - frame_float = info_vec_float.T - frame_int = info_vec_int.T - frame_str = info_vec_str.T - if param['IN_TYPE'] == 'NETCDF_FLOAT': - ftype=np.float32 - elif param['IN_TYPE'] == 'NETCDF_DOUBLE' or param['IN_TYPE'] == 'ASCII': - ftype=np.float64 - da_float = xr.DataArray(frame_float, dims=infodims, coords={'id': idvals, 'vec': infolab_float}).astype(ftype) - da_int = xr.DataArray(frame_int, dims=infodims, coords={'id': idvals, 'vec': infolab_int}) - da_str = xr.DataArray(frame_str, dims=infodims, coords={'id': idvals, 'vec': infolab_str}) - ds_float = da_float.to_dataset(dim="vec") - ds_int = da_int.to_dataset(dim="vec") - ds_str = da_str.to_dataset(dim="vec") - info_ds = xr.combine_by_coords([ds_float, ds_int, ds_str]) - + label_float = tlab.copy() + particle_type[itp] = np.repeat("Test Particle",idvals.size) frame_float = np.expand_dims(vec_float.T, axis=0) frame_str = vec_str.T - da_float = xr.DataArray(frame_float, dims=dims, coords={'time': [t], 'id': idvals, 'vec': label_float}).astype(ftype) + da_float = xr.DataArray(frame_float, dims=dims, coords={'time': [t], 'id': idvals, 'vec': label_float}) da_str= xr.DataArray(frame_str, dims=infodims, coords={'id': idvals, 'vec': label_str}) ds_float = da_float.to_dataset(dim="vec") ds_str = da_str.to_dataset(dim="vec") - ds = xr.combine_by_coords([ds_float, ds_str,info_ds]) + ds = xr.combine_by_coords([ds_float, ds_str]) return ds \ No newline at end of file diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 0d26ebdf0..01c3b8414 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -14,10 +14,11 @@ from swiftest import init_cond from swiftest import tool from swiftest import constants +import os import datetime import xarray as xr import numpy as np -import os +import numpy.typing as npt import shutil from typing import ( Literal, @@ -1506,39 +1507,58 @@ def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: b return range_dict def add_solar_system_body(self, - name: str | List[str] | None = None, - id: int | List[int] | None = None, + name: str | List[str], + ephemeris_id: int | List[int] | None = None, date: str | None = None, - origin_type: str = "initial_conditions", source: str = "HORIZONS"): """ Adds a solar system body to an existing simulation Dataset from the JPL Horizons ephemeris service. - + + The following are name/ephemeris_id pairs that are currently known to Swiftest, and therefore have + physical properties that can be used to make massive bodies. + + Sun : 0 + Mercury : 1 + Venus : 2 + Earth : 3 + Mars : 4 + Jupiter : 5 + Saturn : 6 + Uranus : 7 + Neptune : 8 + Pluto : 9 + Parameters ---------- - name : str | List[str], optional - Add solar system body by name. - Currently bodies from the following list will result in fully-massive bodies (they include mass, radius, - and rotation parameters). "Sun" (added as a central body), "Mercury", "Venus", "Earth", "Mars", - "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto" - - Bodies not on this list will be added as test particles, but additional properties can be added later if - desired. - id : int | List[int], optional - Add solar system body by id number. - date : str, optional - ISO-formatted date sto use when obtaining the ephemerides in the format YYYY-MM-DD. Defaults to value - set by `set_ephemeris_date`. - origin_type : str, default "initial_conditions" - The string that will be added to the `origin_type` variable for all bodies added to the list - source : str, default "Horizons" - The source of the ephemerides. - >*Note.* Currently only the JPL Horizons ephemeris is implemented, so this is ignored. + name : str | List[str] + Add solar system body by name. + Bodies not on this list will be added as test particles, but additional properties can be added later if + desired. + ephemeris_id : int | List[int], optional but must be the same length as `name` if passed. + Use id if the body you wish to add is recognized by Swiftest. In that case, the id is passed to the + ephemeris service and the name is used. The body specified by `id` supercedes that given by `name`. + date : str, optional + ISO-formatted date sto use when obtaining the ephemerides in the format YYYY-MM-DD. Defaults to value + set by `set_ephemeris_date`. + source : str, default "Horizons" + The source of the ephemerides. + >*Note.* Currently only the JPL Horizons ephemeris is implemented, so this is ignored. Returns ------- ds : Xarray dataset with body or bodies added. """ + if type(name) is str: + name = [name] + if ephemeris_id is not None: + if type(ephemeris_id) is int: + ephemeris_id = [ephemeris_id] + if len(ephemeris_id) != len(name): + print(f"Error! The length of ephemeris_id ({len(ephemeris_id)}) does not match the length of name ({len(name)})") + return None + else: + ephemeris_id = [None] * len(name) + if self.ephemeris_date is None: self.set_ephemeris_date() @@ -1553,32 +1573,59 @@ def add_solar_system_body(self, if source.upper() != "HORIZONS": print("Currently only the JPL Horizons ephemeris service is supported") - if id is not None and name is not None: - print("Warning! Requesting both id and name could lead to duplicate bodies.") - dsnew = [] - if name is not None: - if type(name) is str: - name = [name] - - if origin_type is None: - origin_type = ['initial_conditions'] * len(name) - - for n in name: - dsnew.append(self.addp(*init_cond.solar_system_horizons(n, self.param, date))) - - - if id is not None: - if type(id) is str: - id = [id] - - if origin_type is None: - origin_type = ['initial_conditions'] * len(id) - - for i in id: - dsnew.append(self.addp(*init_cond.solar_system_horizons(i, self.param, date))) - - - return + body_list = [] + for i,n in enumerate(name): + body_list.append(init_cond.solar_system_horizons(n, self.param, date, idval=ephemeris_id[i])) + + #Convert the list receieved from the solar_system_horizons output and turn it into arguments to vec2xr + name,v1,v2,v3,v4,v5,v6,ephemeris_id,GMpl,Rpl,rhill,Ip1,Ip2,Ip3,rotx,roty,rotz,J2,J4 = tuple(np.squeeze(np.hsplit(np.array(body_list),19))) + + v1 = v1.astype(np.float64) + v2 = v2.astype(np.float64) + v3 = v3.astype(np.float64) + v4 = v4.astype(np.float64) + v5 = v5.astype(np.float64) + v6 = v6.astype(np.float64) + ephemeris_id = ephemeris_id.astype(int) + GMpl = GMpl.astype(np.float64) + Rpl = Rpl.astype(np.float64) + rhill = rhill.astype(np.float64) + Ip1 = Ip1.astype(np.float64) + Ip2 = Ip2.astype(np.float64) + Ip3 = Ip3.astype(np.float64) + rotx = rotx.astype(np.float64) + roty = roty.astype(np.float64) + rotz = rotz.astype(np.float64) + J2 = J2.astype(np.float64) + J4 = J4.astype(np.float64) + + if all(np.isnan(GMpl)): + GMpl = None + if all(np.isnan(Rpl)): + Rpl = None + if all(np.isnan(rhill)): + rhill = None + if all(np.isnan(Ip1)): + Ip1 = None + if all(np.isnan(Ip2)): + Ip2 = None + if all(np.isnan(Ip3)): + Ip3 = None + if all(np.isnan(rotx)): + rotx = None + if all(np.isnan(roty)): + roty = None + if all(np.isnan(rotz)): + rotz = None + if all(np.isnan(J2)): + J2 = None + if all(np.isnan(J4)): + J4 = None + + + dsnew = init_cond.vec2xr(self.param,name,v1,v2,v3,v4,v5,v6,ephemeris_id,GMpl,Rpl,rhill,Ip1,Ip2,Ip3,rotx,roty,rotz,J2,J4) + + return body_list def set_ephemeris_date(self, @@ -1662,48 +1709,131 @@ def get_ephemeris_date(self, verbose: bool | None = None, **kwargs: Any): return self.ephemeris_date - def add_body(self, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, rhill=None, Ip1=None, Ip2=None, - Ip3=None, rotx=None, roty=None, rotz=None, J2=None, J4=None, t=None): + def add_body(self, + name: str | List[str] | npt.NDArray[np.str_], + v1: float | List[float] | npt.NDArray[np.float_], + v2: float | List[float] | npt.NDArray[np.float_], + v3: float | List[float] | npt.NDArray[np.float_], + v4: float | List[float] | npt.NDArray[np.float_], + v5: float | List[float] | npt.NDArray[np.float_], + v6: float | List[float] | npt.NDArray[np.float_], + idvals: int | list[int] | npt.NDArray[np.int_] | None=None, + GMpl: float | List[float] | npt.NDArray[np.float_] | None=None, + Rpl: float | List[float] | npt.NDArray[np.float_] | None=None, + rhill: float | List[float] | npt.NDArray[np.float_] | None=None, + Ip1: float | List[float] | npt.NDArray[np.float_] | None=None, + Ip2: float | List[float] | npt.NDArray[np.float_] | None=None, + Ip3: float | List[float] | npt.NDArray[np.float_] | None=None, + rotx: float | List[float] | npt.NDArray[np.float_] | None=None, + roty: float | List[float] | npt.NDArray[np.float_] | None=None, + rotz: float | List[float] | npt.NDArray[np.float_] | None=None, + J2: float | List[float] | npt.NDArray[np.float_] | None=None, + J4: float | List[float] | npt.NDArray[np.float_] | None=None): """ Adds a body (test particle or massive body) to the internal DataSet given a set up 6 vectors (orbital elements - or cartesian state vectors, depending on the value of self.param). Input all angles in degress + or cartesian state vectors, depending on the value of self.param). Input all angles in degress. + + This method will update self.ds with the new body or bodies added to the existing Dataset. Parameters ---------- - v1 : float - xh for param['IN_FORM'] == "XV"; a for param['IN_FORM'] == "EL" - v2 : float - yh for param['IN_FORM'] == "XV"; e for param['IN_FORM'] == "EL" - v3 : float - zh for param['IN_FORM'] == "XV"; inc for param['IN_FORM'] == "EL" - v4 : float - vhxh for param['IN_FORM'] == "XV"; capom for param['IN_FORM'] == "EL" - v5 : float - vhyh for param['IN_FORM'] == "XV"; omega for param['IN_FORM'] == "EL" - v6 : float - vhzh for param['IN_FORM'] == "XV"; capm for param['IN_FORM'] == "EL" - Gmass : float - Optional: Array of G*mass values if these are massive bodies - radius : float - Optional: Array radius values if these are massive bodies - rhill : float - Optional: Array rhill values if these are massive bodies - Ip1,y,z : float - Optional: Principal axes moments of inertia - rotx,y,z: float - Optional: Rotation rate vector components - t : float - Optional: Time at start of simulation + name : str or array-like of str + Name or names of + v1 : float or array-like of float + xhx for param['IN_FORM'] == "XV"; a for param['IN_FORM'] == "EL" + v2 : float or array-like of float + xhy for param['IN_FORM'] == "XV"; e for param['IN_FORM'] == "EL" + v3 : float or array-like of float + xhz for param['IN_FORM'] == "XV"; inc for param['IN_FORM'] == "EL" + v4 : float or array-like of float + vhx for param['IN_FORM'] == "XV"; capom for param['IN_FORM'] == "EL" + v5 : float or array-like of float + vhy for param['IN_FORM'] == "XV"; omega for param['IN_FORM'] == "EL" + v6 : float or array-like of float + vhz for param['IN_FORM'] == "XV"; capm for param['IN_FORM'] == "EL" + idvals : int or array-like of int, optional + Unique id values. If not passed, this will be computed based on the pre-existing Dataset ids. + Gmass : float or array-like of float, optional + G*mass values if these are massive bodies + radius : float or array-like of float, optional + Radius values if these are massive bodies + rhill : float, optional + Hill's radius values if these are massive bodies + Ip1,y,z : float, optional + Principal axes moments of inertia these are massive bodies with rotation enabled + rotx,y,z: float, optional + Rotation rate vector components if these are massive bodies with rotation enabled Returns ------- - self.ds : xarray dataset + ds : Xarray Dataset + Dasaset containing the body or bodies that were added + """ - if t is None: - t = self.param['T0'] - dsnew = init_cond.vec2xr(self.param, idvals, namevals, v1, v2, v3, v4, v5, v6, GMpl, Rpl, rhill, Ip1, Ip2, Ip3, - rotx, roty, rotz, J2, J4, t) + #convert all inputs to numpy arrays + def input_to_array(val,t,n=None): + if t == "f": + t = np.float64 + elif t == "i": + t = np.int64 + elif t == "s": + t = np.str + if val is None: + return None + elif np.isscalar(val): + val = np.array([val],dtype=t) + elif type(val) is list: + val = np.array(val,dtype=t) + + if n is None: + return val, len(val) + else: + if n != len(val): + raise ValueError(f"Error! Mismatched array lengths in add_body. Got {len(val)} when expecting {n}") + return val + + + name,nbodies = input_to_array(name,"s") + v1 = input_to_array(v1,"f",nbodies) + v2 = input_to_array(v2,"f",nbodies) + v3 = input_to_array(v3,"f",nbodies) + v4 = input_to_array(v4,"f",nbodies) + v5 = input_to_array(v5,"f",nbodies) + v6 = input_to_array(v6,"f",nbodies) + idvals = input_to_array(idvals,"i",nbodies) + GMpl = input_to_array(GMpl,"f",nbodies) + rhill = input_to_array(rhill,"f",nbodies) + Rpl = input_to_array(Rpl,"f",nbodies) + Ip1 = input_to_array(Ip1,"f",nbodies) + Ip2 = input_to_array(Ip2,"f",nbodies) + Ip3 = input_to_array(Ip3,"f",nbodies) + rotx = input_to_array(rotx,"f",nbodies) + roty = input_to_array(roty,"f",nbodies) + rotz = input_to_array(rotz,"f",nbodies) + J2 = input_to_array(J2,"f",nbodies) + J4 = input_to_array(J4,"f",nbodies) + + if len(self.ds) == 0: + maxid = -1 + else: + maxid = self.ds.id.max().values[()] + + if idvals is None: + idvals = np.arange(start=maxid+1,stop=maxid+1+nbodies,dtype=int) + + if len(self.ds) > 0: + dup_id = np.in1d(idvals,self.ds.id) + if any(dup_id): + raise ValueError(f"Duplicate ids detected: ", *idvals[dup_id]) + + t = self.param['TSTART'] + + dsnew = init_cond.vec2xr(self.param, idvals, name, v1, v2, v3, v4, v5, v6, + GMpl=GMpl, Rpl=Rpl, rhill=rhill, + Ip1=Ip1, Ip2=Ip2, Ip3=Ip3, + rotx=rotx, roty=roty, rotz=rotz, + J2=J2, J4=J4, t=t) if dsnew is not None: self.ds = xr.combine_by_coords([self.ds, dsnew]) self.ds['ntp'] = self.ds['id'].where(np.isnan(self.ds['Gmass'])).count(dim="id") @@ -1714,7 +1844,7 @@ def add_body(self, namevals, v1, v2, v3, v4, v5, v6, GMpl=None, Rpl=None, rhill= elif self.param['OUT_TYPE'] == "NETCDF_FLOAT": self.ds = io.fix_types(self.ds, ftype=np.float32) - return + return dsnew def read_param(self, param_file, codename="Swiftest", verbose=True): """ From 3c4c2b638ea3cc606695145644a67d30a9337a2b Mon Sep 17 00:00:00 2001 From: David Minton Date: Thu, 10 Nov 2022 19:56:39 -0500 Subject: [PATCH 37/75] Finished add_solar_system_body method --- python/swiftest/swiftest/init_cond.py | 65 ++++++++++++-------- python/swiftest/swiftest/simulation_class.py | 4 +- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/python/swiftest/swiftest/init_cond.py b/python/swiftest/swiftest/init_cond.py index c90f1d59b..a5bf89c2b 100644 --- a/python/swiftest/swiftest/init_cond.py +++ b/python/swiftest/swiftest/init_cond.py @@ -329,15 +329,21 @@ def vec2xr(param: Dict, infodims = ['id', 'vec'] # The central body is always given id 0 - icb = idvals == 0 - iscb = any(icb) if GMpl is not None: - ispl = True - ipl = ~np.isnan(GMpl) - itp = np.isnan(GMpl) + icb = (~np.isnan(GMpl)) & (idvals == 0) + ipl = (~np.isnan(GMpl)) & (idvals != 0) + itp = (np.isnan(GMpl)) & (idvals != 0) + iscb = any(icb) + ispl = any(ipl) + istp = any(itp) else: + icb = np.full_like(idvals,False) + ipl = np.full_like(idvals,False) + itp = idvals != 0 + iscb = False ispl = False + istp = any(itp) if ispl and param['CHK_CLOSE'] and Rpl is None: print("Massive bodies need a radius value.") @@ -351,35 +357,42 @@ def vec2xr(param: Dict, param['OUT_FORM'] = param['IN_FORM'] clab, plab, tlab, infolab_float, infolab_int, infolab_str = swiftest.io.make_swiftest_labels(param) param['OUT_FORM'] = old_out_form - vec_str = np.vstack([namevals]) - label_str = ["name"] particle_type = np.empty_like(namevals) + vec = np.vstack([v1,v2,v3,v4,v5,v6]) + if iscb: - label_float = clab.copy() - vec_float = np.vstack([GMpl[icb],Rpl[icb],J2[icb],J4[icb]]) + lab_cb = clab.copy() + vec_cb = np.vstack([GMpl[icb],Rpl[icb],J2[icb],J4[icb]]) if param['ROTATION']: - vec_float = np.vstack([vec_float, Ip1[icb], Ip2[icb], Ip3[icb], rotx[icb], roty[icb], rotz[icb]]) + vec_cb = np.vstack([vec_cb, Ip1[icb], Ip2[icb], Ip3[icb], rotx[icb], roty[icb], rotz[icb]]) particle_type[icb] = "Central Body" - # vec_float = np.vstack([v1, v2, v3, v4, v5, v6]) + vec_cb = np.expand_dims(vec_cb.T,axis=0) # Make way for the time dimension! + ds_cb = xr.DataArray(vec_cb, dims=dims, coords={'time': [t], 'id': idvals[icb], 'vec': lab_cb}).to_dataset(dim='vec') + else: + ds_cb = xr.Dataset() if ispl: - label_float = plab.copy() - vec_float = np.vstack([vec_float, GMpl]) + lab_pl = plab.copy() + vec_pl = np.vstack([vec[:,ipl], GMpl[ipl]]) if param['CHK_CLOSE']: - vec_float = np.vstack([vec_float, Rpl]) + vec_pl = np.vstack([vec_pl, Rpl[ipl]]) if param['RHILL_PRESENT']: - vec_float = np.vstack([vec_float, rhill]) + vec_pl = np.vstack([vec_pl, rhill[ipl]]) if param['ROTATION']: - vec_float = np.vstack([vec_float, Ip1, Ip2, Ip3, rotx, roty, rotz]) - particle_type[ipl] = np.repeat("Massive Body",idvals.size) + vec_pl = np.vstack([vec_pl, Ip1[ipl], Ip2[ipl], Ip3[ipl], rotx[ipl], roty[ipl], rotz[ipl]]) + particle_type[ipl] = np.repeat("Massive Body",idvals[ipl].size) + vec_pl = np.expand_dims(vec_pl.T,axis=0) # Make way for the time dimension! + ds_pl = xr.DataArray(vec_pl, dims=dims, coords={'time': [t], 'id': idvals[ipl], 'vec': lab_pl}).to_dataset(dim='vec') else: - label_float = tlab.copy() - particle_type[itp] = np.repeat("Test Particle",idvals.size) - frame_float = np.expand_dims(vec_float.T, axis=0) - frame_str = vec_str.T - da_float = xr.DataArray(frame_float, dims=dims, coords={'time': [t], 'id': idvals, 'vec': label_float}) - da_str= xr.DataArray(frame_str, dims=infodims, coords={'id': idvals, 'vec': label_str}) - ds_float = da_float.to_dataset(dim="vec") - ds_str = da_str.to_dataset(dim="vec") - ds = xr.combine_by_coords([ds_float, ds_str]) + ds_pl = xr.Dataset() + if istp: + lab_tp = tlab.copy() + vec_tp = np.expand_dims(vec[:,itp].T,axis=0) # Make way for the time dimension! + ds_tp = xr.DataArray(vec_tp, dims=dims, coords={'time': [t], 'id': idvals[itp], 'vec': lab_tp}).to_dataset(dim='vec') + particle_type[itp] = np.repeat("Test Particle",idvals[itp].size) + else: + ds_tp = xr.Dataset() + + ds_info = xr.DataArray(np.vstack([namevals,particle_type]).T, dims=infodims, coords={'id': idvals, 'vec' : ["name", "particle_type"]}).to_dataset(dim='vec') + ds = xr.combine_by_coords([ds_cb, ds_pl, ds_tp, ds_info]) return ds \ No newline at end of file diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 01c3b8414..9ecbc1e51 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -1625,7 +1625,9 @@ def add_solar_system_body(self, dsnew = init_cond.vec2xr(self.param,name,v1,v2,v3,v4,v5,v6,ephemeris_id,GMpl,Rpl,rhill,Ip1,Ip2,Ip3,rotx,roty,rotz,J2,J4) - return body_list + self.ds = xr.combine_by_coords([self.ds,dsnew]) + + return dsnew def set_ephemeris_date(self, From d166719e3b44a2a05fcbe96e8f23088395439512 Mon Sep 17 00:00:00 2001 From: David Minton Date: Thu, 10 Nov 2022 20:33:35 -0500 Subject: [PATCH 38/75] Finished the new add_body and add_solar_system_body methods --- python/swiftest/swiftest/init_cond.py | 13 +++-- python/swiftest/swiftest/simulation_class.py | 52 +++++++++++++++++--- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/python/swiftest/swiftest/init_cond.py b/python/swiftest/swiftest/init_cond.py index a5bf89c2b..ea28bcb8c 100644 --- a/python/swiftest/swiftest/init_cond.py +++ b/python/swiftest/swiftest/init_cond.py @@ -369,7 +369,7 @@ def vec2xr(param: Dict, vec_cb = np.expand_dims(vec_cb.T,axis=0) # Make way for the time dimension! ds_cb = xr.DataArray(vec_cb, dims=dims, coords={'time': [t], 'id': idvals[icb], 'vec': lab_cb}).to_dataset(dim='vec') else: - ds_cb = xr.Dataset() + ds_cb = None if ispl: lab_pl = plab.copy() vec_pl = np.vstack([vec[:,ipl], GMpl[ipl]]) @@ -383,16 +383,21 @@ def vec2xr(param: Dict, vec_pl = np.expand_dims(vec_pl.T,axis=0) # Make way for the time dimension! ds_pl = xr.DataArray(vec_pl, dims=dims, coords={'time': [t], 'id': idvals[ipl], 'vec': lab_pl}).to_dataset(dim='vec') else: - ds_pl = xr.Dataset() + ds_pl = None if istp: lab_tp = tlab.copy() vec_tp = np.expand_dims(vec[:,itp].T,axis=0) # Make way for the time dimension! ds_tp = xr.DataArray(vec_tp, dims=dims, coords={'time': [t], 'id': idvals[itp], 'vec': lab_tp}).to_dataset(dim='vec') particle_type[itp] = np.repeat("Test Particle",idvals[itp].size) else: - ds_tp = xr.Dataset() + ds_tp = None ds_info = xr.DataArray(np.vstack([namevals,particle_type]).T, dims=infodims, coords={'id': idvals, 'vec' : ["name", "particle_type"]}).to_dataset(dim='vec') - ds = xr.combine_by_coords([ds_cb, ds_pl, ds_tp, ds_info]) + ds = [d for d in [ds_cb, ds_pl, ds_tp] if d is not None] + if len(ds) > 1: + ds = xr.combine_by_coords(ds) + else: + ds = ds[0] + ds = xr.merge([ds_info,ds]) return ds \ No newline at end of file diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 9ecbc1e51..8e1ac24e0 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -1622,10 +1622,15 @@ def add_solar_system_body(self, if all(np.isnan(J4)): J4 = None + t = self.param['TSTART'] - dsnew = init_cond.vec2xr(self.param,name,v1,v2,v3,v4,v5,v6,ephemeris_id,GMpl,Rpl,rhill,Ip1,Ip2,Ip3,rotx,roty,rotz,J2,J4) + dsnew = init_cond.vec2xr(self.param,name,v1,v2,v3,v4,v5,v6,ephemeris_id, + GMpl=GMpl, Rpl=Rpl, rhill=rhill, + Ip1=Ip1, Ip2=Ip2, Ip3=Ip3, + rotx=rotx, roty=roty, rotz=rotz, + J2=J2, J4=J4, t=t) - self.ds = xr.combine_by_coords([self.ds,dsnew]) + dsnew = self._combine_and_fix_dsnew(dsnew) return dsnew @@ -1831,19 +1836,50 @@ def input_to_array(val,t,n=None): t = self.param['TSTART'] - dsnew = init_cond.vec2xr(self.param, idvals, name, v1, v2, v3, v4, v5, v6, + dsnew = init_cond.vec2xr(self.param, name, v1, v2, v3, v4, v5, v6, idvals, GMpl=GMpl, Rpl=Rpl, rhill=rhill, Ip1=Ip1, Ip2=Ip2, Ip3=Ip3, rotx=rotx, roty=roty, rotz=rotz, - J2=J2, J4=J4, t=t) - if dsnew is not None: - self.ds = xr.combine_by_coords([self.ds, dsnew]) - self.ds['ntp'] = self.ds['id'].where(np.isnan(self.ds['Gmass'])).count(dim="id") - self.ds['npl'] = self.ds['id'].where(np.invert(np.isnan(self.ds['Gmass']))).count(dim="id") - 1 + J2=J2, J4=J4,t=t) + + dsnew = self._combine_and_fix_dsnew(dsnew) + + return dsnew + + def _combine_and_fix_dsnew(self,dsnew): + """ + Combines the new Dataset with the old one. Also computes the values of ntp and npl and sets the proper types. + Parameters + ---------- + dsnew : xarray Dataset + Dataset with new bodies + + Returns + ------- + dsnew : xarray Dataset + Updated Dataset with ntp, npl values and types fixed. + + """ + + self.ds = xr.combine_by_coords([self.ds, dsnew]) + + def get_nvals(ds): + if "Gmass" in dsnew: + ds['ntp'] = ds['id'].where(np.isnan(ds['Gmass'])).count(dim="id") + ds['npl'] = ds['id'].where(np.invert(np.isnan(ds['Gmass']))).count(dim="id") - 1 + else: + ds['ntp'] = ds['id'].count(dim="id") + ds['npl'] = xr.full_like(ds['ntp'],0) + return ds + + dsnew = get_nvals(dsnew) + self.ds = get_nvals(self.ds) if self.param['OUT_TYPE'] == "NETCDF_DOUBLE": + dsnew = io.fix_types(dsnew, ftype=np.float64) self.ds = io.fix_types(self.ds, ftype=np.float64) elif self.param['OUT_TYPE'] == "NETCDF_FLOAT": + dsnew = io.fix_types(dsnew, ftype=np.float32) self.ds = io.fix_types(self.ds, ftype=np.float32) return dsnew From 4bd9b6a30f099249a5ae37f0753090bf65d114f0 Mon Sep 17 00:00:00 2001 From: David Minton Date: Thu, 10 Nov 2022 20:37:38 -0500 Subject: [PATCH 39/75] Fixed typo that gave wrong npl and ntp values --- python/swiftest/swiftest/simulation_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 8e1ac24e0..d98a4ec1b 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -1864,9 +1864,9 @@ def _combine_and_fix_dsnew(self,dsnew): self.ds = xr.combine_by_coords([self.ds, dsnew]) def get_nvals(ds): - if "Gmass" in dsnew: + if "Gmass" in ds: ds['ntp'] = ds['id'].where(np.isnan(ds['Gmass'])).count(dim="id") - ds['npl'] = ds['id'].where(np.invert(np.isnan(ds['Gmass']))).count(dim="id") - 1 + ds['npl'] = ds['id'].where(~(np.isnan(ds['Gmass']))).count(dim="id") - 1 else: ds['ntp'] = ds['id'].count(dim="id") ds['npl'] = xr.full_like(ds['ntp'],0) From ff6da0f6188a8222e774060ebdd814a6a15ba531 Mon Sep 17 00:00:00 2001 From: David Minton Date: Thu, 10 Nov 2022 20:38:49 -0500 Subject: [PATCH 40/75] Updated example for the Basic Simulation --- examples/Basic_Simulation/cb.in | 9 - .../Basic_Simulation/initial_conditions.py | 34 ++-- examples/Basic_Simulation/param.in | 37 ++++ examples/Basic_Simulation/pl.in | 163 +++++++++--------- examples/Basic_Simulation/tp.in | 49 +++--- 5 files changed, 148 insertions(+), 144 deletions(-) create mode 100644 examples/Basic_Simulation/param.in diff --git a/examples/Basic_Simulation/cb.in b/examples/Basic_Simulation/cb.in index 29c8a7fca..b2cb85c35 100644 --- a/examples/Basic_Simulation/cb.in +++ b/examples/Basic_Simulation/cb.in @@ -1,12 +1,3 @@ -!! Copyright 2022 - David Minton, Carlisle Wishard, Jennifer Pouplin, Jake Elliott, & Dana Singh -!! This file is part of Swiftest. -!! Swiftest is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License -!! as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -!! Swiftest is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty -!! of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -!! You should have received a copy of the GNU General Public License along with Swiftest. -!! If not, see: https://www.gnu.org/licenses. - Sun 39.476926408897626 0.004650467260962157 diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index c9d0823af..f12bf6e07 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -30,31 +30,20 @@ from numpy.random import default_rng # Initialize the simulation object as a variable -sim = swiftest.Simulation(init_cond_file_type="ASCII") - +sim = swiftest.Simulation() sim.set_simulation_time(tstart=0.0, tstop=10.0, dt=0.005, tstep_out=1.0) - # Add parameter attributes to the simulation object sim.param['GMTINY'] = 1e-6 sim.param['MIN_GMFRAG'] = 1e-9 # Add the modern planets and the Sun using the JPL Horizons Database -sim.add("Sun", idval=0, date="2022-08-08") -sim.add("Mercury", idval=1, date="2022-08-08") -sim.add("Venus", idval=2, date="2022-08-08") -sim.add("Earth", idval=3, date="2022-08-08") -sim.add("Mars", idval=4, date="2022-08-08") -sim.add("Jupiter", idval=5, date="2022-08-08") -sim.add("Saturn", idval=6, date="2022-08-08") -sim.add("Uranus", idval=7, date="2022-08-08") -sim.add("Neptune", idval=8, date="2022-08-08") +sim.add_solar_system_body(["Sun","Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto"]) # Add 5 user-defined massive bodies npl = 5 density_pl = 3000.0 / (sim.param['MU2KG'] / sim.param['DU2M'] ** 3) -id_pl = np.array([9, 10, 11, 12, 13]) -name_pl = np.array(["MassiveBody_01", "MassiveBody_02", "MassiveBody_03", "MassiveBody_04", "MassiveBody_05"]) +name_pl = ["MassiveBody_01", "MassiveBody_02", "MassiveBody_03", "MassiveBody_04", "MassiveBody_05"] a_pl = default_rng().uniform(0.3, 1.5, npl) e_pl = default_rng().uniform(0.0, 0.3, npl) inc_pl = default_rng().uniform(0.0, 90, npl) @@ -64,19 +53,18 @@ GM_pl = (np.array([6e23, 8e23, 1e24, 3e24, 5e24]) / sim.param['MU2KG']) * sim.GU R_pl = np.full(npl, (3 * (GM_pl / sim.GU) / (4 * np.pi * density_pl)) ** (1.0 / 3.0)) Rh_pl = a_pl * ((GM_pl) / (3 * sim.GU)) ** (1.0 / 3.0) -Ip1_pl = np.array([0.4, 0.4, 0.4, 0.4, 0.4]) -Ip2_pl = np.array([0.4, 0.4, 0.4, 0.4, 0.4]) -Ip3_pl = np.array([0.4, 0.4, 0.4, 0.4, 0.4]) -rotx_pl = np.array([0.0, 0.0, 0.0, 0.0, 0.0]) -roty_pl = np.array([0.0, 0.0, 0.0, 0.0, 0.0]) -rotz_pl = np.array([0.0, 0.0, 0.0, 0.0, 0.0]) +Ip1_pl = [0.4, 0.4, 0.4, 0.4, 0.4] +Ip2_pl = [0.4, 0.4, 0.4, 0.4, 0.4] +Ip3_pl = [0.4, 0.4, 0.4, 0.4, 0.4] +rotx_pl = [0.0, 0.0, 0.0, 0.0, 0.0] +roty_pl = [0.0, 0.0, 0.0, 0.0, 0.0] +rotz_pl = [0.0, 0.0, 0.0, 0.0, 0.0] -sim.addp(id_pl, name_pl, a_pl, e_pl, inc_pl, capom_pl, omega_pl, capm_pl, GMpl=GM_pl, Rpl=R_pl, rhill=Rh_pl, Ip1=Ip1_pl, Ip2=Ip2_pl, Ip3=Ip3_pl, rotx=rotx_pl, roty=roty_pl, rotz=rotz_pl) +sim.add_body(name_pl, a_pl, e_pl, inc_pl, capom_pl, omega_pl, capm_pl, GMpl=GM_pl, Rpl=R_pl, rhill=Rh_pl, Ip1=Ip1_pl, Ip2=Ip2_pl, Ip3=Ip3_pl, rotx=rotx_pl, roty=roty_pl, rotz=rotz_pl) # Add 10 user-defined test particles ntp = 10 -id_tp = np.array([14, 15, 16, 17, 18, 19, 20, 21, 22, 23]) name_tp = np.array(["TestParticle_01", "TestParticle_02", "TestParticle_03", "TestParticle_04", "TestParticle_05", "TestParticle_06", "TestParticle_07", "TestParticle_08", "TestParticle_09", "TestParticle_10"]) a_tp = default_rng().uniform(0.3, 1.5, ntp) e_tp = default_rng().uniform(0.0, 0.3, ntp) @@ -85,7 +73,7 @@ omega_tp = default_rng().uniform(0.0, 360.0, ntp) capm_tp = default_rng().uniform(0.0, 360.0, ntp) -sim.addp(id_tp, name_tp, a_tp, e_tp, inc_tp, capom_tp, omega_tp, capm_tp) +sim.add_body(name_tp, a_tp, e_tp, inc_tp, capom_tp, omega_tp, capm_tp) # Save everything to a set of initial conditions files sim.save('param.in') diff --git a/examples/Basic_Simulation/param.in b/examples/Basic_Simulation/param.in new file mode 100644 index 000000000..47498726c --- /dev/null +++ b/examples/Basic_Simulation/param.in @@ -0,0 +1,37 @@ +! VERSION Swiftest parameter input +T0 0.0 +TSTOP 10.0 +DT 0.005 +ISTEP_OUT 200 +ISTEP_DUMP 200 +OUT_FORM XVEL +OUT_TYPE NETCDF_DOUBLE +OUT_STAT REPLACE +IN_TYPE NETCDF_DOUBLE +BIN_OUT bin.nc +CHK_QMIN 0.004650467260962157 +CHK_RMIN 0.004650467260962157 +CHK_RMAX 10000.0 +CHK_EJECT 10000.0 +CHK_QMIN_COORD HELIO +CHK_QMIN_RANGE 0.004650467260962157 10000.0 +MU2KG 1.988409870698051e+30 +TU2S 31557600.0 +DU2M 149597870700.0 +RESTART NO +INTERACTION_LOOPS TRIANGULAR +ENCOUNTER_CHECK TRIANGULAR +CHK_CLOSE YES +GR YES +FRAGMENTATION YES +ROTATION YES +ENERGY NO +EXTRA_FORCE NO +BIG_DISCARD NO +RHILL_PRESENT NO +TIDES NO +IN_FORM EL +NC_IN init_cond.nc +TSTART 0.0 +GMTINY 1e-06 +MIN_GMFRAG 1e-09 diff --git a/examples/Basic_Simulation/pl.in b/examples/Basic_Simulation/pl.in index 3f18aca0e..215b91f1d 100644 --- a/examples/Basic_Simulation/pl.in +++ b/examples/Basic_Simulation/pl.in @@ -1,88 +1,85 @@ -!! Copyright 2022 - David Minton, Carlisle Wishard, Jennifer Pouplin, Jake Elliott, & Dana Singh -!! This file is part of Swiftest. -!! Swiftest is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License -!! as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -!! Swiftest is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty -!! of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -!! You should have received a copy of the GNU General Public License along with Swiftest. -!! If not, see: https://www.gnu.org/licenses. - -13 -Mercury 6.553709809565314146e-06 0.0014751262621647182575 -1.6306381826061645943e-05 -0.38709864823618972407 0.20562513690973019398 7.0036250456070128223 -48.30204974520415817 29.18823342267911869 114.9720047697775982 -0.0 0.0 0.34599999999999997424 -3.5734889863322150192 -18.38008501876561206 34.361513668512199956 -Venus 9.6633133995815381836e-05 0.006759085242739840662 -4.0453784346544178454e-05 -0.72332603580811538624 0.0067486079191121668003 3.394406261149808035 -76.61738837179606776 54.777760273590551776 315.37095689555837907 -0.0 0.0 0.4000000000000000222 -0.17650282045605921225 -3.6612475825356215592 8.702866268072763821 -Earth 0.000120026935827952456416 0.010044668314295209318 -4.25875607065040958e-05 -0.9999943732711822353 0.016707309394717231171 0.0028984767436730000077 -174.05498211951208987 289.04709044403989537 213.07530468023790604 -0.0 0.0 0.33069999999999999396 -5.002093202481912218 0.055213529850334066125 2301.2110537292529557 -Mars 1.2739802010675941808e-05 0.007246950762048707243 -2.265740805092889601e-05 -1.5237812078483019551 0.0935087708803710449 1.8479353068000929916 -49.489305419773351957 286.70300191753761965 24.878418068365981242 -0.0 0.0 0.3644000000000000017 -997.9357048213454125 -909.4072592492943007 1783.4501726537997323 -Jupiter 0.03769225108898567778 0.3552222491747608486 -0.00046732617030490929307 -5.2028063728088866924 0.048395118271449058533 1.3035670146561249005 -100.516498130230701236 273.44233262595901124 346.26538105843917492 -0.0 0.0 0.27560000000000001164 --80.96619889339111482 -2388.0060524649362916 5008.7314931237953832 -Saturn 0.01128589982009127331 0.43757948578866074266 -0.00038925687730393611812 -9.580020069168169172 0.053193613750490406633 2.4864365613724639381 -113.597044717589099605 335.10179422401358806 237.66485199561481068 -0.0 0.0 0.22000000000000000111 -441.93538182505989814 378.5284220382117538 5135.9110455622733884 -Uranus 0.001723658947826773068 0.4705353566089944894 -0.00016953449859497231466 -19.272143108769419939 0.043779687288749750962 0.7707536154556786645 -74.077748995180698444 93.42271392662131291 242.37685081109759722 -0.0 0.0 0.23000000000000000999 --677.3000258209181323 -3008.109907190578637 -836.301326618569835 -Neptune 0.0020336100526728302882 0.78184929587893868845 -0.000164587904124493665 -30.305539399096510067 0.014544938874222059638 1.7686697746048700708 -131.73604731224671127 249.9779420269553043 332.54824537252648042 -0.0 0.0 0.23000000000000000999 -1231.1804455066093229 -2178.0887091151860042 2329.6411363603121418 -MassiveBody_01 1.1912109366578087428e-05 0.0016092923734511708263 -2.425055692051244981e-05 -0.3460404950890429432 0.2093906512182220625 0.11109012870384793459 -114.31328763688792094 347.82259114762894114 96.0534391561842682 -0.4000000000000000222 0.4000000000000000222 0.4000000000000000222 +14 +Mercury 6.553709809565314e-06 +1.6306381826061646e-05 +0.3870985843095394 0.2056234010897001 7.003302508001384 +48.29611837378607 29.20442403952454 338.3394874682879 +0.0 0.0 0.346 +3.573018077015318 -18.380317085416586 34.36143850429876 +Venus 9.663313399581537e-05 +4.0453784346544176e-05 +0.7233297579736101 0.006717605698865438 3.394439273342282 +76.60235891771119 54.96037946082961 200.4789339550648 +0.0 0.0 0.4 +0.17650282045605922 -3.6612475825356214 8.702866268072764 +Earth 0.00012002693582795245 +4.25875607065041e-05 +0.9999904874543223 0.01671400376545858 0.003637862608863003 +175.025172600231 287.9619628812575 114.3482934042427 +0.0 0.0 0.3307 +6.157239621449141 0.057224133705898524 2301.2082528350797 +Mars 1.2739802010675942e-05 +2.2657408050928896e-05 +1.523711925589535 0.09344151133508208 1.847441673557901 +49.4728572124747 286.7379771285891 209.3396773477138 +0.0 0.0 0.3644 +997.9224351226384 -909.5549030011778 1783.3823046046184 +Jupiter 0.037692251088985676 +0.0004673261703049093 +5.2027278008516 0.04824497711637968 1.303631134570075 +100.5192588433081 273.5898402882514 129.5536700659942 +0.0 0.0 0.2756 +-80.9864396731672 -2388.0246092955053 5008.722318533006 +Saturn 0.011285899820091273 +0.00038925687730393614 +9.532011952667288 0.05486329870433341 2.487906363280301 +113.6305781676206 339.5467356402391 290.8995806568904 +0.0 0.0 0.22 +441.95954822014636 378.52638822638795 5135.909115928892 +Uranus 0.001723658947826773 +0.00016953449859497232 +19.24498838290236 0.04796174942301296 0.7730102596086205 +74.0125809801658 93.59554912280227 262.8658637277515 +0.0 0.0 0.23 +-677.3000258209181 -3008.1099071905787 -836.3013266185699 +Neptune 0.0020336100526728304 +0.00016458790412449367 +30.03895991152209 0.008955570138096731 1.771119354296142 +131.8221159748827 284.4748429674216 308.4513720536233 +0.0 0.0 0.23 +1232.224106980634 -2177.3040821077648 2329.8227878119233 +Pluto 2.924216771029454e-07 +7.943294877391593e-06 +39.36791814672583 0.2487178537481577 17.1705505990969 +110.3314332962701 113.0826635900664 55.11416408345664 +0.0 0.0 0.4 +-243.59404988903637 261.28663002814227 -38.57352022187049 +MassiveBody_01 1.1912109366578089e-05 +2.425055692051245e-05 +0.7452368716298337 0.0633011418780484 0.11151363780595558 +203.01823417718037 284.9353898127118 266.79344592519305 +0.4 0.4 0.4 0.0 0.0 0.0 -MassiveBody_02 1.5882812488770779849e-05 0.0016802531895603555184 -2.6691191565570073646e-05 -0.32826188156947710972 0.27866488696288682636 77.21223337306255985 -251.99014895640269174 53.772702227560969845 165.6085387284213084 -0.4000000000000000222 0.4000000000000000222 0.4000000000000000222 +MassiveBody_02 1.5882812488770783e-05 +2.6691191565570074e-05 +0.7671203280602826 0.10149029388964753 53.46706656938751 +61.74738152068808 68.20565593722856 271.3352706902475 +0.4 0.4 0.4 0.0 0.0 0.0 -MassiveBody_03 1.9853515610963475658e-05 0.004324919007577881884 -2.8752214513575297366e-05 -0.7843689028022314824 0.06176128116947356833 78.74144231136708072 -286.63765100951468412 347.55933571120488068 266.36960496595537506 -0.4000000000000000222 0.4000000000000000222 0.4000000000000000222 +MassiveBody_03 1.985351561096348e-05 +2.8752214513575297e-05 +1.4698824276418418 0.13621250684495437 23.635498327264845 +38.071905339231236 283.134612455057 250.67457601578352 +0.4 0.4 0.4 0.0 0.0 0.0 -MassiveBody_04 5.9560546832890430362e-05 0.0056765613035874530265 -4.146786902759040254e-05 -0.7138176832994267418 0.28016098557400787028 22.725690778108500467 -203.41845532080247949 219.74297850728484605 14.730732982803269593 -0.4000000000000000222 0.4000000000000000222 0.4000000000000000222 +MassiveBody_04 5.9560546832890444e-05 +4.14678690275904e-05 +0.9741731590760117 0.1519713326893784 20.51335588582416 +350.53780805624825 304.05941264938997 142.62713592644738 +0.4 0.4 0.4 0.0 0.0 0.0 -MassiveBody_05 9.9267578054817388455e-05 0.010382929139161686458 -4.916559523190238318e-05 -1.101215402063684401 0.076651567404004070094 52.41961577462824806 -142.90070862650665617 293.70542448390904156 318.5666754758643151 -0.4000000000000000222 0.4000000000000000222 0.4000000000000000222 +MassiveBody_05 9.926757805481742e-05 +4.916559523190238e-05 +0.6633500122731075 0.13550930562584215 10.680323453316653 +328.45970148163457 49.93948991697533 316.2109831817007 +0.4 0.4 0.4 0.0 0.0 0.0 diff --git a/examples/Basic_Simulation/tp.in b/examples/Basic_Simulation/tp.in index 162f8c0af..9e3c6b9bc 100644 --- a/examples/Basic_Simulation/tp.in +++ b/examples/Basic_Simulation/tp.in @@ -1,40 +1,31 @@ -!! Copyright 2022 - David Minton, Carlisle Wishard, Jennifer Pouplin, Jake Elliott, & Dana Singh -!! This file is part of Swiftest. -!! Swiftest is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License -!! as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -!! Swiftest is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty -!! of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -!! You should have received a copy of the GNU General Public License along with Swiftest. -!! If not, see: https://www.gnu.org/licenses. - 10 TestParticle_01 -1.0288790290699558749 0.088535376967516773994 39.679062610233010844 -51.107327480220099858 20.90477961082723013 183.92936958121950397 +0.5697767200549144 0.11834332057809717 80.87229166892182 +222.5373287698991 111.76944690551065 270.3754252090885 TestParticle_02 -0.54831541717225318333 0.20048853000030755767 32.784952066779297297 -66.59436607119042151 196.2609764918545352 76.40623742948525887 +1.0092273242276735 0.05399051523964523 82.35601343884242 +117.598050656921 116.26893959032198 208.13367102381633 TestParticle_03 -0.9815578696795972391 0.25717438840188583393 38.959194313128776344 -158.31201212846775661 93.86793546512863884 126.96040079919036714 +0.7590606700221183 0.027936243833100036 88.12926503625694 +188.5414923780248 169.44578516513008 282.4333664924941 TestParticle_04 -0.70510786000431613374 0.068740260615181125736 39.981235917453354034 -26.668314027440779057 12.507089902982141183 53.606668142734086757 +0.9972940668463142 0.2569338531459909 22.75744046640894 +199.388648818177 152.8772634782716 307.54746324357103 TestParticle_05 -1.336737159289705712 0.25044351028750111432 74.189544264066626056 -313.1647935522670423 152.39951433565357775 322.52271715518395467 +0.341620427452812 0.052524493325270837 18.839916665085173 +327.2732232808312 334.4396604858765 359.4553699087638 TestParticle_06 -0.64018428687513928566 0.1478972702874425671 73.39555666508663023 -74.03379498826825511 124.22185942531125136 106.36095293685497154 +1.1670239341669624 0.07778574626824612 22.716135789279228 +196.30730270195494 154.1907148406917 159.6860762761786 TestParticle_07 -1.2738161048947760356 0.17129911220230967239 78.723790909435408025 -111.971184037421835455 315.6294407604535195 108.74538288631850946 +1.0954005977667731 0.10128096358705894 9.878980088486013 +101.85525076444998 124.8686145955563 254.7757898831765 TestParticle_08 -0.5196197141250760154 0.26607581023277782073 79.221465395061358095 -240.07768052067768849 177.4793327047061382 189.85775920180086018 +0.39125485873865873 0.17327367123519463 22.276414655828646 +105.99598258123197 235.2803528505754 348.706620365582 TestParticle_09 -0.7160151851892424535 0.29584187625470087513 58.1214116614385361 -24.22606869850931588 338.4678256522021229 136.32070882113120547 +0.48720268138208256 0.29707768397862033 39.717820434869466 +307.9106242922398 221.91704762104942 333.56626647648994 TestParticle_10 -1.0782431797945351004 0.11096856177737297877 54.729767236739554903 -280.85362091874827684 116.780853088052467115 286.99855363222661708 +0.6458552755486036 0.07522818043309405 45.225887389492826 +74.40348173261458 92.77221707157337 260.30279498657796 From 0be34922757a59eb9d0f20166bac6b00097acf27 Mon Sep 17 00:00:00 2001 From: David Minton Date: Thu, 10 Nov 2022 20:41:02 -0500 Subject: [PATCH 41/75] switched the test particle names in the Basic_Simulation example to be a list rather than a numpy array just to make it cleaner (it accepts either) --- examples/Basic_Simulation/initial_conditions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index f12bf6e07..1edc13879 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -65,7 +65,7 @@ # Add 10 user-defined test particles ntp = 10 -name_tp = np.array(["TestParticle_01", "TestParticle_02", "TestParticle_03", "TestParticle_04", "TestParticle_05", "TestParticle_06", "TestParticle_07", "TestParticle_08", "TestParticle_09", "TestParticle_10"]) +name_tp = ["TestParticle_01", "TestParticle_02", "TestParticle_03", "TestParticle_04", "TestParticle_05", "TestParticle_06", "TestParticle_07", "TestParticle_08", "TestParticle_09", "TestParticle_10"] a_tp = default_rng().uniform(0.3, 1.5, ntp) e_tp = default_rng().uniform(0.0, 0.3, ntp) inc_tp = default_rng().uniform(0.0, 90, ntp) From 9b31c06a6fb456aa4df0b687589ef03475508caf Mon Sep 17 00:00:00 2001 From: David Minton Date: Fri, 11 Nov 2022 06:00:43 -0500 Subject: [PATCH 42/75] Moved the CHK_QMIN_HELIO parameter setting into the distance_range setters and getters. Removed the loop style parameters out of the initiation of the param dictionary --- python/swiftest/swiftest/simulation_class.py | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index d98a4ec1b..3f2aaeab6 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -63,6 +63,7 @@ def __init__(self, TU_name: str | None = None, rmin: float = constants.RSun / constants.AU2M, rmax: float = 10000.0, + qmin_coord: Literal["HELIO","BARY"] = "HELIO", close_encounter_check: bool = True, general_relativity: bool = True, fragmentation: bool = True, @@ -183,6 +184,8 @@ def __init__(self, Minimum distance of the simulation (CHK_QMIN, CHK_RMIN, CHK_QMIN_RANGE[0]) rmax : float, default value is 10000 AU in the unit system defined by the unit input arguments. Maximum distance of the simulation (CHK_RMAX, CHK_QMIN_RANGE[1]) + qmin_coord : str, {"HELIO", "BARY"}, default "HELIO" + coordinate frame to use for CHK_QMIN close_encounter_check : bool, default True Check for close encounters between bodies. If set to True, then the radii of massive bodies must be included in initial conditions. @@ -238,9 +241,6 @@ def __init__(self, self.ds = xr.Dataset() self.param = { '! VERSION': f"Swiftest parameter input", - 'CHK_QMIN_COORD': "HELIO", - 'INTERACTION_LOOPS': interaction_loops, - 'ENCOUNTER_CHECK': encounter_check_loops } self.codename = codename self.verbose = verbose @@ -1402,6 +1402,7 @@ def update_param_units(self, MU2KG_old, DU2M_old, TU2S_old): def set_distance_range(self, rmin: float | None = None, rmax: float | None = None, + qmin_coord: Literal["HELIO","BARY"] | None = None, verbose: bool | None = None, **kwargs: Any): """ @@ -1413,6 +1414,8 @@ def set_distance_range(self, Minimum distance of the simulation (CHK_QMIN, CHK_RMIN, CHK_QMIN_RANGE[0]) rmax : float Maximum distance of the simulation (CHK_RMAX, CHK_QMIN_RANGE[1]) + qmin_coord : str, {"HELIO", "BARY"} + coordinate frame to use for CHK_QMIN **kwargs A dictionary of additional keyword argument. This allows this method to be called by the more general set_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. @@ -1440,6 +1443,14 @@ def set_distance_range(self, self.param['CHK_EJECT'] = rmax CHK_QMIN_RANGE[1] = rmax update_list.append("rmax") + if qmin_coord is not None: + valid_qmin_coord = ["HELIO","BARY"] + if qmin_coord.upper() not in valid_qmin_coord: + print(f"qmin_coord = {qmin_coord} is not a valid option. Must be one of",','.join(valid_qmin_coord)) + self.param['CHK_QMIN_COORD'] = valid_qmin_coord[0] + else: + self.param['CHK_QMIN_COORD'] = qmin_coord.upper() + update_list.append("qmin_coord") self.param['CHK_QMIN_RANGE'] = f"{CHK_QMIN_RANGE[0]} {CHK_QMIN_RANGE[1]}" @@ -1473,6 +1484,7 @@ def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: b valid_var = {"rmin": "CHK_RMIN", "rmax": "CHK_RMAX", + "qmin_coord": "CHK_QMIN_COORD", "qmin": "CHK_QMIN", "qminR": "CHK_QMIN_RANGE" } @@ -1503,6 +1515,9 @@ def get_distance_range(self, arg_list: str | List[str] | None = None, verbose: b if "rmax" in valid_arg: key = valid_var["rmax"] print(f"{'rmax':<{self._getter_column_width}} {range_dict[key]} {units['rmax']}") + if "qmin_coord" in valid_arg: + key = valid_var["qmin_coord"] + print(f"{'qmin_coord':<{self._getter_column_width}} {range_dict[key]}") return range_dict From f36cb5ae393439aa88b8bf72a7b16e9d1171f1a8 Mon Sep 17 00:00:00 2001 From: David Minton Date: Fri, 11 Nov 2022 07:08:44 -0500 Subject: [PATCH 43/75] Fixed a bunch of bugs in the setters and getters. --- python/swiftest/swiftest/simulation_class.py | 192 +++++++++++++++++-- 1 file changed, 173 insertions(+), 19 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 3f2aaeab6..1b6509976 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -35,6 +35,7 @@ class Simulation: def __init__(self, codename: Literal["Swiftest", "Swifter", "Swift"] = "Swiftest", + integrator: Literal["symba","rmvs","whm","helio"] = "symba", param_file: os.PathLike | str = "param.in", read_param: bool = False, t0: float = 0.0, @@ -63,6 +64,10 @@ def __init__(self, TU_name: str | None = None, rmin: float = constants.RSun / constants.AU2M, rmax: float = 10000.0, + gmtiny: float | None = None, + mtiny: float | None = None, + min_fragment_mass: float | None = None, + min_fragment_gmass: float | None = None, qmin_coord: Literal["HELIO","BARY"] = "HELIO", close_encounter_check: bool = True, general_relativity: bool = True, @@ -83,7 +88,9 @@ def __init__(self, Parameters ---------- codename : {"Swiftest", "Swifter", "Swift"}, default "Swiftest" - Name of the n-body integrator that will be used. + Name of the n-body code that will be used. + integrator : {"symba","rmvs","whm","helio"}, default "symba" + Name of the n-body integrator that will be used when executing a run. param_file : str, path-like, or file-lke, default "param.in" Name of the parameter input file that will be passed to the integrator. read_param : bool, default False @@ -92,7 +99,7 @@ def __init__(self, > *Note:* If set to true, the parameters defined in the input file will override any passed into the > arguments of Simulation. t0 : float, default 0.0 - The reference time for the start of the simulation. Defaults is 0.0 + The reference time for the start of the simulation. Defaults is 0.0. tstart : float, default 0.0 The start time for a restarted simulation. For a new simulation, tstart will be set to t0 automatically. tstop : float, optional @@ -106,11 +113,11 @@ def __init__(self, `istep_out = floor(tstep_out/dt)`. *Note*: only `istep_out` or `toutput` can be set. istep_dump : int, optional The anumber of time steps between outputs to dump file. If not set, this will be set to the value of - `istep_out` (or the equivalent value determined by `tstep_out`) + `istep_out` (or the equivalent value determined by `tstep_out`). init_cond_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"}, default "NETCDF_DOUBLE" The file type containing initial conditions for the simulation: - * NETCDF_DOUBLE: A single initial conditions input file in NetCDF file format of type NETCDF_DOUBLE - * NETCDF_FLOAT: A single initial conditions input file in NetCDF file format of type NETCDF_FLOAT + * NETCDF_DOUBLE: A single initial conditions input file in NetCDF file format of type NETCDF_DOUBLE. + * NETCDF_FLOAT: A single initial conditions input file in NetCDF file format of type NETCDF_FLOAT. * ASCII : Three initial conditions files in ASCII format. The individual files define the central body, massive body, and test particle initial conditions. init_cond_file_name : str, path-like, or dict, optional @@ -237,11 +244,8 @@ def __init__(self, If set to True, then more information is printed by Simulation methods as they are executed. Setting to False suppresses most messages other than errors. """ - + self.param = {} self.ds = xr.Dataset() - self.param = { - '! VERSION': f"Swiftest parameter input", - } self.codename = codename self.verbose = verbose self.restart = restart @@ -257,7 +261,9 @@ def __init__(self, else: print(f"{param_file} not found.") - self.set_parameter(t0=t0, + self.set_parameter(codename=codename, + integrator=integrator, + t0=t0, tstart=tstart, tstop=tstop, dt=dt, @@ -265,6 +271,7 @@ def __init__(self, istep_out=istep_out, istep_dump=istep_dump, rmin=rmin, rmax=rmax, + qmin_coord=qmin_coord, MU=MU, DU=DU, TU=TU, MU2KG=MU2KG, DU2M=DU2M, TU2S=TU2S, MU_name=MU_name, DU_name=DU_name, TU_name=TU_name, @@ -284,6 +291,8 @@ def __init__(self, big_discard=big_discard, rhill_present=rhill_present, restart=restart, + interaction_loops=interaction_loops, + encounter_check_loops=encounter_check_loops, ephemeris_date=ephemeris_date, verbose=False) @@ -543,6 +552,7 @@ def set_parameter(self, **kwargs): # Non-returning setters self.set_ephemeris_date(**kwargs) + self.set_integrator(**kwargs) return param_dict @@ -559,6 +569,8 @@ def get_parameter(self, **kwargs): """ + self.get_integrator(**kwargs) + # Getters returning parameter dictionary values param_dict = self.get_simulation_time(**kwargs) param_dict.update(self.get_init_cond_files(**kwargs)) @@ -567,12 +579,109 @@ def get_parameter(self, **kwargs): param_dict.update(self.get_unit_system(**kwargs)) param_dict.update(self.get_feature(**kwargs)) - # Non-returning getters - if not bool(kwargs) or "ephemeris_date" in kwargs: - self.get_ephemeris_date(**kwargs) + self.get_ephemeris_date(**kwargs) return param_dict + def set_integrator(self, + codename: Literal["swiftest", "swifter", "swift"] | None = None, + integrator: Literal["symba","rmvs","whm","helio"] | None = None, + verbose: bool | None = None, + **kwargs: Any + ): + """ + + Parameters + ---------- + codename : {"swiftest", "swifter", "swift"}, optional + integrator : {"symba","rmvs","whm","helio"}, optional + Name of the n-body integrator that will be used when executing a run. + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + set_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. + + Returns + ------- + None + + """ + + if integrator is None and codename is None: + return + + if codename is not None: + valid_codename = ["swiftest", "swifter", "swift"] + if codename.lower() not in valid_codename: + print(f"{codename} is not a valid codename. Valid options are ",",".join(valid_codename)) + try: + self.codename + except: + self.codename = valid_codename[0] + else: + self.codename = codename.lower() + + self.param['! VERSION'] = f"{self.codename.title()} parameter input" + + if integrator is not None: + valid_integrator = ["symba","rmvs","whm","helio"] + if integrator.lower() not in valid_integrator: + print(f"{integrator} is not a valid integrator. Valid options are ",",".join(valid_integrator)) + try: + self.integrator + except: + self.integrator = valid_integrator[0] + else: + self.integrator = integrator.lower() + + self.get_integrator("integrator", verbose) + + return + + def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs: Any): + """ + + Returns a subset of the parameter dictionary containing the current values of the distance range parameters. + If the verbose option is set in the Simulation object, then it will also print the values. + + Parameters + ---------- + arg_list: str | List[str], optional + A single string or list of strings containing the names of the features to extract. Default is all of: + ["integrator"] + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. + + Returns + ------- + integrator: str, + The integrator name. + """ + + try: + self.integrator + except: + print(f"integrator is not set") + return + + try: + self.codename + except: + print(f"codename is not set") + return + + valid_arg = {"integrator": self.integrator, + "codename": self.codename} + + if not bool(kwargs) and arg_list is None: + arg_list = list(valid_arg.keys()) + ephemeris_date = self._get_instance_var(arg_list, valid_arg, verbose, **kwargs) + return + def set_feature(self, close_encounter_check: bool | None = None, general_relativity: bool | None = None, @@ -705,8 +814,10 @@ def set_feature(self, if interaction_loops not in valid_vals: print(f"{interaction_loops} is not a valid option for interaction loops.") print(f"Must be one of {valid_vals}") - self.param["INTERACTION_LOOPS"] = interaction_loops + if "INTERACTION_LOOPS" not in self.param: + self.param["INTERACTION_LOOPS"] = valid_vals[0] else: + self.param["INTERACTION_LOOPS"] = interaction_loops update_list.append("interaction_loops") if encounter_check_loops is not None: @@ -714,6 +825,8 @@ def set_feature(self, if encounter_check_loops not in valid_vals: print(f"{encounter_check_loops} is not a valid option for interaction loops.") print(f"Must be one of {valid_vals}") + if "ENCOUNTER_CHECK" not in self.param: + self.param["ENCOUNTER_CHECK"] = valid_vals[0] else: self.param["ENCOUNTER_CHECK"] = encounter_check_loops update_list.append("encounter_check_loops") @@ -1701,13 +1814,16 @@ def set_ephemeris_date(self, return ephemeris_date - def get_ephemeris_date(self, verbose: bool | None = None, **kwargs: Any): + def get_ephemeris_date(self, arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs: Any): """ Prints the current value of the ephemeris date Parameters ---------- + arg_list: str | List[str], optional + A single string or list of strings containing the names of the features to extract. Default is all of: + ["integrator"] verbose: bool, optional If passed, it will override the Simulation object's verbose flag **kwargs @@ -1720,16 +1836,54 @@ def get_ephemeris_date(self, verbose: bool | None = None, **kwargs: Any): The ISO-formatted date string for the ephemeris computation """ - if self.ephemeris_date is None: + + try: + self.ephemeris_date + except: print(f"ephemeris_date is not set") return + valid_arg = {"ephemeris_date": self.ephemeris_date} + + ephemeris_date = self._get_instance_var(arg_list, valid_arg,verbose, **kwargs) + + return ephemeris_date + + def _get_instance_var(self, arg_list: str | List[str], valid_arg: Dict, verbose: bool | None = None, **kwargs: Any): + """ + + Prints the current value of an instance variable. + + Parameters + ---------- + arg_list: str | List[str] + A single string or list of strings containing the names of the the instance variable to get. + valid_arg: dict + A dictionary where the key is the parameter argument and the value is the equivalent instance variable value. + verbose: bool, optional + If passed, it will override the Simulation object's verbose flag + **kwargs + A dictionary of additional keyword argument. This allows this method to be called by the more general + get_parameter method, which takes all possible Simulation parameters as arguments, so these are ignored. + + Returns + ------- + Tuple of instance variable values given by the arg_list + + """ + + arg_vals = [] if verbose is None: verbose = self.verbose if verbose: - print(f"{'ephemeris_date':<{self._getter_column_width}} {self.ephemeris_date}") - - return self.ephemeris_date + if arg_list is None: + arg_list = list(valid_arg.keys()) + for arg in arg_list: + if arg in valid_arg: + print(f"{arg:<{self._getter_column_width}} {valid_arg[arg]}") + arg_vals.append(valid_arg[arg]) + + return tuple(arg_vals) def add_body(self, name: str | List[str] | npt.NDArray[np.str_], From e8e3dafcc36725d2bb35c7eac7b85fb2d96ee7d2 Mon Sep 17 00:00:00 2001 From: David Minton Date: Fri, 11 Nov 2022 08:35:51 -0500 Subject: [PATCH 44/75] Improvements to API handling of integrators and codes. --- python/swiftest/swiftest/simulation_class.py | 66 +++++++++++++------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 1b6509976..a731603ce 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -246,7 +246,6 @@ def __init__(self, """ self.param = {} self.ds = xr.Dataset() - self.codename = codename self.verbose = verbose self.restart = restart @@ -257,7 +256,7 @@ def __init__(self, self.sim_dir = os.path.dirname(os.path.realpath(param_file)) if read_param: if os.path.exists(param_file): - self.read_param(param_file, codename=codename, verbose=self.verbose) + self.read_param(param_file, codename=codename.title(), verbose=self.verbose) else: print(f"{param_file} not found.") @@ -543,7 +542,9 @@ def set_parameter(self, **kwargs): """ # Setters returning parameter dictionary values - param_dict = self.set_unit_system(**kwargs) + param_dict = {} + param_dict.update(self.set_integrator(**kwargs)) + param_dict.update(self.set_unit_system(**kwargs)) param_dict.update(self.set_distance_range(**kwargs)) param_dict.update(self.set_feature(**kwargs)) param_dict.update(self.set_init_cond_files(**kwargs)) @@ -552,7 +553,6 @@ def set_parameter(self, **kwargs): # Non-returning setters self.set_ephemeris_date(**kwargs) - self.set_integrator(**kwargs) return param_dict @@ -569,10 +569,11 @@ def get_parameter(self, **kwargs): """ - self.get_integrator(**kwargs) # Getters returning parameter dictionary values - param_dict = self.get_simulation_time(**kwargs) + param_dict = {} + param_dict.update(self.get_integrator(**kwargs)) + param_dict.update(self.get_simulation_time(**kwargs)) param_dict.update(self.get_init_cond_files(**kwargs)) param_dict.update(self.get_output_files(**kwargs)) param_dict.update(self.get_distance_range(**kwargs)) @@ -611,18 +612,21 @@ def set_integrator(self, if integrator is None and codename is None: return + update_list = [] + if codename is not None: - valid_codename = ["swiftest", "swifter", "swift"] - if codename.lower() not in valid_codename: + valid_codename = ["Swiftest", "Swifter", "Swift"] + if codename.title() not in valid_codename: print(f"{codename} is not a valid codename. Valid options are ",",".join(valid_codename)) try: self.codename except: self.codename = valid_codename[0] else: - self.codename = codename.lower() + self.codename = codename.title() - self.param['! VERSION'] = f"{self.codename.title()} parameter input" + self.param['! VERSION'] = f"{self.codename} parameter input" + update_list.append("codename") if integrator is not None: valid_integrator = ["symba","rmvs","whm","helio"] @@ -634,10 +638,11 @@ def set_integrator(self, self.integrator = valid_integrator[0] else: self.integrator = integrator.lower() + update_list.append("integrator") - self.get_integrator("integrator", verbose) + integrator_dict = self.get_integrator(update_list, verbose) - return + return integrator_dict def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | None = None, **kwargs: Any): """ @@ -658,29 +663,45 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | Returns ------- - integrator: str, - The integrator name. + integrator_dict : dict + The subset of the dictionary containing the code name if codename is selected """ + valid_var = {"codename": "! VERSION"} + + valid_instance_vars = {"integrator": self.integrator, + "codename": self.codename} + try: self.integrator except: print(f"integrator is not set") - return + return {} try: self.codename except: print(f"codename is not set") - return + return {} - valid_arg = {"integrator": self.integrator, - "codename": self.codename} if not bool(kwargs) and arg_list is None: - arg_list = list(valid_arg.keys()) - ephemeris_date = self._get_instance_var(arg_list, valid_arg, verbose, **kwargs) - return + arg_list = list(valid_instance_vars.keys()) + integrator = self._get_instance_var(arg_list, valid_instance_vars, verbose, **kwargs) + + valid_arg, integrator_dict = self._get_valid_arg_list(arg_list, valid_var) + + if verbose is None: + verbose = self.verbose + + if verbose: + for arg in arg_list: + if arg in valid_arg: + key = valid_var[arg] + print(f"{arg:<{self._getter_column_width}} {integrator_dict[key]}") + elif arg in valid_instance_vars: + print(f"{arg:<{self._getter_column_width}} {valid_instance_vars[arg]}") + return integrator_dict def set_feature(self, close_encounter_check: bool | None = None, @@ -963,7 +984,7 @@ def ascii_file_input_error_msg(codename): else: init_cond_file_type = "NETCDF_DOUBLE" - if self.codename == "Swiftest": + if self.codename.title() == "Swiftest": init_cond_keys = ["CB", "PL", "TP"] else: init_cond_keys = ["PL", "TP"] @@ -1057,6 +1078,7 @@ def get_init_cond_files(self, arg_list: str | List[str] | None = None, verbose: if self.codename == "Swifter": three_file_args.remove("init_cond_file_name['CB']") + valid_var.pop("init_cond_file_name['CB']",None) # We have to figure out which initial conditions file model we are using (1 vs. 3 files) if arg_list is None: From eee8bc6c1fbdacd226411cd0c4e82317919201ff Mon Sep 17 00:00:00 2001 From: David Minton Date: Fri, 11 Nov 2022 09:56:18 -0500 Subject: [PATCH 45/75] Started the process of adding the executable to the Simulation class so that simulations can actually be run from inside Python --- python/swiftest/swiftest/simulation_class.py | 27 ++++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index a731603ce..ff8806c6b 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -14,6 +14,7 @@ from swiftest import init_cond from swiftest import tool from swiftest import constants +from swiftest import __file__ as _pyfile import os import datetime import xarray as xr @@ -605,9 +606,11 @@ def set_integrator(self, Returns ------- - None + integrator_dict: dict + A dictionary containing the subset of the parameter dictonary that was updated by this setter """ + # TODO: Improve how it finds the executable binary if integrator is None and codename is None: return @@ -627,6 +630,13 @@ def set_integrator(self, self.param['! VERSION'] = f"{self.codename} parameter input" update_list.append("codename") + if self.codename == "Swiftest": + self.binary_path = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(_pyfile)),os.pardir,os.pardir,os.pardir,"build")) + self.driver_executable = os.path.join(self.binary_path,"swiftest_driver") + else: + self.binary_path = "NOT SET" + self.driver_executable = "NOT SET" + update_list.append("driver_executable") if integrator is not None: valid_integrator = ["symba","rmvs","whm","helio"] @@ -670,7 +680,8 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | valid_var = {"codename": "! VERSION"} valid_instance_vars = {"integrator": self.integrator, - "codename": self.codename} + "codename": self.codename, + "driver_executable": self.driver_executable} try: self.integrator @@ -684,23 +695,17 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | print(f"codename is not set") return {} + if verbose is None: + verbose = self.verbose if not bool(kwargs) and arg_list is None: arg_list = list(valid_instance_vars.keys()) + integrator = self._get_instance_var(arg_list, valid_instance_vars, verbose, **kwargs) valid_arg, integrator_dict = self._get_valid_arg_list(arg_list, valid_var) - if verbose is None: - verbose = self.verbose - if verbose: - for arg in arg_list: - if arg in valid_arg: - key = valid_var[arg] - print(f"{arg:<{self._getter_column_width}} {integrator_dict[key]}") - elif arg in valid_instance_vars: - print(f"{arg:<{self._getter_column_width}} {valid_instance_vars[arg]}") return integrator_dict def set_feature(self, From cd0d93f3b52ec2dd515a096697be55749bd321eb Mon Sep 17 00:00:00 2001 From: David Minton Date: Fri, 11 Nov 2022 16:10:06 -0500 Subject: [PATCH 46/75] Updated test Notebook --- examples/Basic_Simulation/test_io.ipynb | 143 ++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 examples/Basic_Simulation/test_io.ipynb diff --git a/examples/Basic_Simulation/test_io.ipynb b/examples/Basic_Simulation/test_io.ipynb new file mode 100644 index 000000000..ce92a8229 --- /dev/null +++ b/examples/Basic_Simulation/test_io.ipynb @@ -0,0 +1,143 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "86c845ce-1801-46ca-8a8a-1cabb266e6a6", + "metadata": {}, + "outputs": [], + "source": [ + "import swiftest\n", + "import xarray as xr\n", + "import numpy as np\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d716c371-8eb4-4fc1-82af-8b5c444c831e", + "metadata": {}, + "outputs": [], + "source": [ + "sim = swiftest.Simulation()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "83cebbc1-387b-4ef5-b96e-76856b6672e5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "integrator symba\n", + "codename Swiftest\n", + "driver_executable /Users/daminton/git/swiftest/build/swiftest_driver\n", + "t0 0.0 y\n", + "tstart 0.0 y\n", + "tstop NOT SET\n", + "dt NOT SET\n", + "istep_out NOT SET\n", + "istep_dump NOT SET\n", + "init_cond_file_type NETCDF_DOUBLE\n", + "init_cond_format EL\n", + "init_cond_file_name init_cond.nc\n", + "output_file_type NETCDF_DOUBLE\n", + "output_file_name bin.nc\n", + "output_format XVEL\n", + "rmin 0.004650467260962157 AU\n", + "rmax 10000.0 AU\n", + "qmin_coord HELIO\n", + "MU: MSun 1.988409870698051e+30 kg / MSun\n", + "DU: AU 149597870700.0 m / AU\n", + "TU: y 31557600.0 s / y\n", + "close_encounter_check True\n", + "general_relativity True\n", + "fragmentation True\n", + "rotation True\n", + "compute_conservation_values False\n", + "extra_force False\n", + "big_discard False\n", + "rhill_present False\n", + "restart False\n", + "interaction_loops TRIANGULAR\n", + "encounter_check_loops TRIANGULAR\n", + "ephemeris_date 2027-04-30\n" + ] + }, + { + "data": { + "text/plain": [ + "{'! VERSION': 'Swiftest parameter input',\n", + " 'T0': 0.0,\n", + " 'TSTART': 0.0,\n", + " 'IN_TYPE': 'NETCDF_DOUBLE',\n", + " 'IN_FORM': 'EL',\n", + " 'NC_IN': 'init_cond.nc',\n", + " 'OUT_TYPE': 'NETCDF_DOUBLE',\n", + " 'BIN_OUT': 'bin.nc',\n", + " 'OUT_FORM': 'XVEL',\n", + " 'CHK_RMIN': 0.004650467260962157,\n", + " 'CHK_RMAX': 10000.0,\n", + " 'CHK_QMIN_COORD': 'HELIO',\n", + " 'CHK_QMIN': 0.004650467260962157,\n", + " 'CHK_QMIN_RANGE': '0.004650467260962157 10000.0',\n", + " 'MU2KG': 1.988409870698051e+30,\n", + " 'DU2M': 149597870700.0,\n", + " 'TU2S': 31557600.0,\n", + " 'CHK_CLOSE': True,\n", + " 'GR': True,\n", + " 'FRAGMENTATION': True,\n", + " 'ROTATION': True,\n", + " 'ENERGY': False,\n", + " 'EXTRA_FORCE': False,\n", + " 'BIG_DISCARD': False,\n", + " 'RHILL_PRESENT': False,\n", + " 'RESTART': False,\n", + " 'INTERACTION_LOOPS': 'TRIANGULAR',\n", + " 'ENCOUNTER_CHECK': 'TRIANGULAR'}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sim.get_parameter()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec7452d6-4c9b-4df3-acc0-b11c32264b91", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From f5abde23052bb3fb49d0f50a47c5fb90459be715 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Fri, 11 Nov 2022 17:37:33 -0500 Subject: [PATCH 47/75] Added minimum fragment mass to feature setter and getter when enabling fragmentation --- .../Basic_Simulation/initial_conditions.py | 5 +- python/swiftest/swiftest/simulation_class.py | 59 ++++++++++++++++--- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index 1edc13879..b7c6a323c 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -30,11 +30,10 @@ from numpy.random import default_rng # Initialize the simulation object as a variable -sim = swiftest.Simulation() -sim.set_simulation_time(tstart=0.0, tstop=10.0, dt=0.005, tstep_out=1.0) +sim = swiftest.Simulation(tstart=0.0, tstop=10.0, dt=0.005, tstep_out=1.0, fragmentation=True, minimum_fragment_gmass = 1e-9) +sim.get_parameter() # Add parameter attributes to the simulation object sim.param['GMTINY'] = 1e-6 -sim.param['MIN_GMFRAG'] = 1e-9 # Add the modern planets and the Sun using the JPL Horizons Database sim.add_solar_system_body(["Sun","Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto"]) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index ff8806c6b..a359f8caa 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -67,12 +67,12 @@ def __init__(self, rmax: float = 10000.0, gmtiny: float | None = None, mtiny: float | None = None, - min_fragment_mass: float | None = None, - min_fragment_gmass: float | None = None, qmin_coord: Literal["HELIO","BARY"] = "HELIO", close_encounter_check: bool = True, general_relativity: bool = True, - fragmentation: bool = True, + fragmentation: bool = False, + minimum_fragment_mass: float | None = None, + minimum_fragment_gmass: float | None = None, rotation: bool = True, compute_conservation_values: bool = False, extra_force: bool = False, @@ -202,6 +202,12 @@ def __init__(self, fragmentation : bool, default True If set to True, this turns on the Fraggle fragment generation code and `rotation` must also be True. This argument only applies to Swiftest-SyMBA simulations. It will be ignored otherwise. + minimum_fragment_gmass : float, optional + If fragmentation is turned on, this sets the mimimum G*mass of a collisional fragment that can be generated. + *Note.* Only set one of minimum_fragment_gmass or minimum_fragment_mass + minimum_fragment_mass : float, optional + If fragmentation is turned on, this sets the mimimum mass of a collisional fragment that can be generated. + *Note.* Only set one of minimum_fragment_gmass or minimum_fragment_mass rotation : bool, default True If set to True, this turns on rotation tracking and radius, rotation vector, and moments of inertia values must be included in the initial conditions. @@ -285,6 +291,8 @@ def __init__(self, close_encounter_check=close_encounter_check, general_relativity=general_relativity, fragmentation=fragmentation, + minimum_fragment_gmass=minimum_fragment_gmass, + minimum_fragment_mass=minimum_fragment_mass, rotation=rotation, compute_conservation_values=compute_conservation_values, extra_force=extra_force, @@ -712,6 +720,8 @@ def set_feature(self, close_encounter_check: bool | None = None, general_relativity: bool | None = None, fragmentation: bool | None = None, + minimum_fragment_gmass: float | None = None, + minimum_fragment_mass: float | None = None, rotation: bool | None = None, compute_conservation_values: bool | None = None, extra_force: bool | None = None, @@ -737,6 +747,12 @@ def set_feature(self, fragmentation : bool, optional If set to True, this turns on the Fraggle fragment generation code and `rotation` must also be True. This argument only applies to Swiftest-SyMBA simulations. It will be ignored otherwise. + minimum_fragment_gmass : float, optional + If fragmentation is turned on, this sets the mimimum G*mass of a collisional fragment that can be generated. + *Note.* Only set one of minimum_fragment_gmass or minimum_fragment_mass + minimum_fragment_mass : float, optional + If fragmentation is turned on, this sets the mimimum mass of a collisional fragment that can be generated. + *Note.* Only set one of minimum_fragment_gmass or minimum_fragment_mass rotation : bool, optional If set to True, this turns on rotation tracking and radius, rotation vector, and moments of inertia values must be included in the initial conditions. @@ -805,8 +821,27 @@ def set_feature(self, update_list.append("general_relativity") if fragmentation is not None: - self.param['FRAGMENTATION'] = fragmentation - update_list.append("fragmentation") + if self.codename != "Swiftest" and self.integrator != "symba" and fragmentation: + print("Fragmentation is only available on Swiftest SyMBA.") + self.param['FRAGMENTATION'] = False + else: + self.param['FRAGMENTATION'] = fragmentation + update_list.append("fragmentation") + if fragmentation: + if "MIN_GMFRAG" not in self.param and minimum_fragment_mass is None and minimum_fragment_gmass is None: + print("Minimum fragment mass is not set. Set it using minimum_fragment_gmass or minimum_fragment_mass") + else: + update_list.append("minimum_fragment_gmass") + update_list.append("minimum_fragment_mass") + if minimum_fragment_gmass is not None and minimum_fragment_mass is not None: + print("Warning! Only set either minimum_fragment_mass or minimum_fragment_gmass, but not both!") + + if minimum_fragment_gmass is not None: + self.param["MIN_GMFRAG"] = minimum_fragment_gmass + update_list.append("minimum_fragment_gmass") + elif minimum_fragment_mass is not None: + self.param["MIN_GMFRAG"] = minimum_fragment_mass * self.GU + update_list.append("minimum_fragment_gmass") if rotation is not None: self.param['ROTATION'] = rotation @@ -896,7 +931,8 @@ def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | N "rhill_present": "RHILL_PRESENT", "restart": "RESTART", "interaction_loops": "INTERACTION_LOOPS", - "encounter_check_loops": "ENCOUNTER_CHECK" + "encounter_check_loops": "ENCOUNTER_CHECK", + "minimum_fragment_gmass" : "MIN_GMFRAG" } valid_arg, feature_dict = self._get_valid_arg_list(arg_list, valid_var) @@ -907,7 +943,16 @@ def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | N if verbose: for arg in valid_arg: key = valid_var[arg] - print(f"{arg:<{self._getter_column_width}} {feature_dict[key]}") + if key in feature_dict: + if arg == "minimum_fragment_gmass": + if self.param['FRAGMENTATION']: + print(f"{arg:<{self._getter_column_width}} {feature_dict[key]} {self.DU_name}^3 / {self.TU_name}^2 ") + print(f"{'minimum_fragment_mass':<{self._getter_column_width}} {feature_dict[key] / self.GU} {self.MU_name}") + else: + print(f"{arg:<{self._getter_column_width}} {feature_dict[key]}") + else: + print(f"{arg:<{self._getter_column_width}} NOT SET") + return feature_dict From c6e5f4537ec8b322b9ff1c9de9dccebfb8c32455 Mon Sep 17 00:00:00 2001 From: David Minton Date: Fri, 11 Nov 2022 19:38:31 -0500 Subject: [PATCH 48/75] Added gmtiny and mtiny as parameter options. changed the initial conditions generator to use mass instead of G*mass values. --- .../Basic_Simulation/initial_conditions.py | 4 +- python/swiftest/swiftest/simulation_class.py | 51 +++++++++++++++++-- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index b7c6a323c..91471fd7f 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -30,10 +30,8 @@ from numpy.random import default_rng # Initialize the simulation object as a variable -sim = swiftest.Simulation(tstart=0.0, tstop=10.0, dt=0.005, tstep_out=1.0, fragmentation=True, minimum_fragment_gmass = 1e-9) +sim = swiftest.Simulation(tstart=0.0, tstop=10.0, dt=0.005, tstep_out=1.0, fragmentation=True, minimum_fragment_mass = 2.5e-11, mtiny=2.5e-8) sim.get_parameter() -# Add parameter attributes to the simulation object -sim.param['GMTINY'] = 1e-6 # Add the modern planets and the Sun using the JPL Horizons Database sim.add_solar_system_body(["Sun","Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto"]) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index a359f8caa..c194729f8 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -65,9 +65,9 @@ def __init__(self, TU_name: str | None = None, rmin: float = constants.RSun / constants.AU2M, rmax: float = 10000.0, + qmin_coord: Literal["HELIO","BARY"] = "HELIO", gmtiny: float | None = None, mtiny: float | None = None, - qmin_coord: Literal["HELIO","BARY"] = "HELIO", close_encounter_check: bool = True, general_relativity: bool = True, fragmentation: bool = False, @@ -194,6 +194,12 @@ def __init__(self, Maximum distance of the simulation (CHK_RMAX, CHK_QMIN_RANGE[1]) qmin_coord : str, {"HELIO", "BARY"}, default "HELIO" coordinate frame to use for CHK_QMIN + mtiny : float, optional + The minimum mass of fully interacting bodies. Bodies below this mass interact with the larger bodies, + but not each other (SyMBA only). *Note.* Only mtiny or gmtiny is accepted, not both. + gmtiny : float, optional + The minimum G*mass of fully interacting bodies. Bodies below this mass interact with the larger bodies, + but not each other (SyMBA only). *Note.* Only mtiny or gmtiny is accepted, not both. close_encounter_check : bool, default True Check for close encounters between bodies. If set to True, then the radii of massive bodies must be included in initial conditions. @@ -278,6 +284,8 @@ def __init__(self, istep_dump=istep_dump, rmin=rmin, rmax=rmax, qmin_coord=qmin_coord, + gmtiny=gmtiny, + mtiny=mtiny, MU=MU, DU=DU, TU=TU, MU2KG=MU2KG, DU2M=DU2M, TU2S=TU2S, MU_name=MU_name, DU_name=DU_name, TU_name=TU_name, @@ -552,8 +560,8 @@ def set_parameter(self, **kwargs): # Setters returning parameter dictionary values param_dict = {} - param_dict.update(self.set_integrator(**kwargs)) param_dict.update(self.set_unit_system(**kwargs)) + param_dict.update(self.set_integrator(**kwargs)) param_dict.update(self.set_distance_range(**kwargs)) param_dict.update(self.set_feature(**kwargs)) param_dict.update(self.set_init_cond_files(**kwargs)) @@ -596,6 +604,8 @@ def get_parameter(self, **kwargs): def set_integrator(self, codename: Literal["swiftest", "swifter", "swift"] | None = None, integrator: Literal["symba","rmvs","whm","helio"] | None = None, + mtiny: float | None = None, + gmtiny: float | None = None, verbose: bool | None = None, **kwargs: Any ): @@ -606,6 +616,12 @@ def set_integrator(self, codename : {"swiftest", "swifter", "swift"}, optional integrator : {"symba","rmvs","whm","helio"}, optional Name of the n-body integrator that will be used when executing a run. + mtiny : float, optional + The minimum mass of fully interacting bodies. Bodies below this mass interact with the larger bodies, + but not each other (SyMBA only). *Note.* Only mtiny or gmtiny is accepted, not both. + gmtiny : float, optional + The minimum G*mass of fully interacting bodies. Bodies below this mass interact with the larger bodies, + but not each other (SyMBA only). *Note.* Only mtiny or gmtiny is accepted, not both. verbose: bool, optional If passed, it will override the Simulation object's verbose flag **kwargs @@ -658,6 +674,18 @@ def set_integrator(self, self.integrator = integrator.lower() update_list.append("integrator") + if mtiny is not None or gmtiny is not None: + if self.integrator != "symba": + print("mtiny and gmtiny are only used by SyMBA.") + if mtiny is not None and gmtiny is not None: + print("Only set mtiny or gmtiny, not both!") + elif gmtiny is not None: + self.param['GMTINY'] = gmtiny + update_list.append("gmtiny") + elif mtiny is not None: + self.param['GMTINY'] = self.GU * mtiny + update_list.append("gmtiny") + integrator_dict = self.get_integrator(update_list, verbose) return integrator_dict @@ -685,7 +713,8 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | The subset of the dictionary containing the code name if codename is selected """ - valid_var = {"codename": "! VERSION"} + valid_var = {"codename": "! VERSION", + "gmtiny" : "GMTINY"} valid_instance_vars = {"integrator": self.integrator, "codename": self.codename, @@ -708,11 +737,25 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | if not bool(kwargs) and arg_list is None: arg_list = list(valid_instance_vars.keys()) - + arg_list.append(*[a for a in valid_var.keys() if a not in valid_instance_vars]) + integrator = self._get_instance_var(arg_list, valid_instance_vars, verbose, **kwargs) valid_arg, integrator_dict = self._get_valid_arg_list(arg_list, valid_var) + if verbose: + for arg in valid_arg: + key = valid_var[arg] + if key in integrator_dict: + if arg == "gmtiny": + if self.integrator == "symba": + print(f"{arg:<{self._getter_column_width}} {integrator_dict[key]} {self.DU_name}^3 / {self.TU_name}^2 ") + print(f"{'mtiny':<{self._getter_column_width}} {integrator_dict[key] / self.GU} {self.MU_name}") + else: + print(f"{arg:<{self._getter_column_width}} {integrator_dict[key]}") + else: + print(f"{arg:<{self._getter_column_width}} NOT SET") + return integrator_dict From dc508d4706c3e97c3fdcd27d1e4d2ed49ba84137 Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Mon, 14 Nov 2022 10:28:08 -0500 Subject: [PATCH 49/75] moved j2rp2 and j4rp4 from required to variables that the user doesn't need to know about in netcdf_open --- src/netcdf/netcdf.f90 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 788f58beb..156d0caac 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -372,8 +372,6 @@ module subroutine netcdf_open(self, param, readonly) call check( nf90_inq_varid(self%ncid, PTYPE_VARNAME, self%ptype_varid), "netcdf_open nf90_inq_varid ptype_varid" ) call check( nf90_inq_varid(self%ncid, STATUS_VARNAME, self%status_varid), "netcdf_open nf90_inq_varid status_varid" ) call check( nf90_inq_varid(self%ncid, GMASS_VARNAME, self%Gmass_varid), "netcdf_open nf90_inq_varid Gmass_varid" ) - call check( nf90_inq_varid(self%ncid, J2RP2_VARNAME, self%j2rp2_varid), "netcdf_open nf90_inq_varid j2rp2_varid" ) - call check( nf90_inq_varid(self%ncid, J4RP4_VARNAME, self%j4rp4_varid), "netcdf_open nf90_inq_varid j4rp4_varid" ) if ((param%out_form == XV) .or. (param%out_form == XVEL)) then call check( nf90_inq_varid(self%ncid, XHX_VARNAME, self%xhx_varid), "netcdf_open nf90_inq_varid xhx_varid" ) @@ -450,6 +448,9 @@ module subroutine netcdf_open(self, param, readonly) ! Variables The User Doesn't Need to Know About + status = nf90_inq_varid(self%ncid, J2RP2_VARNAME, self%j2rp2_varid) + status = nf90_inq_varid(self%ncid, J4RP4_VARNAME, self%j4rp4_varid) + if (param%lclose) then status = nf90_inq_varid(self%ncid, ORIGIN_TYPE_VARNAME, self%origin_type_varid) status = nf90_inq_varid(self%ncid, ORIGIN_TIME_VARNAME, self%origin_time_varid) From d8209242d5cbb85a01faa2b6f6d5a37ddafa847e Mon Sep 17 00:00:00 2001 From: Carlisle Wishard Date: Mon, 14 Nov 2022 10:28:50 -0500 Subject: [PATCH 50/75] if j2rp2 or j4rp4 are not found, set them to 0. if they are found, get the value --- src/netcdf/netcdf.f90 | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 156d0caac..c4cd12042 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -505,7 +505,7 @@ module function netcdf_read_frame_system(self, iu, param) result(ierr) ! Return integer(I4B) :: ierr !! Error code: returns 0 if the read is successful ! Internals - integer(I4B) :: tslot, idmax, npl_check, ntp_check, nplm_check, t_max, str_max + integer(I4B) :: tslot, idmax, npl_check, ntp_check, nplm_check, t_max, str_max, status real(DP), dimension(:), allocatable :: rtemp integer(I4B), dimension(:), allocatable :: itemp logical, dimension(:), allocatable :: validmask, tpmask, plmask @@ -717,8 +717,19 @@ module function netcdf_read_frame_system(self, iu, param) result(ierr) ! if (npl > 0) pl%Q(:) = pack(rtemp, plmask) ! end if - call check( nf90_get_var(iu%ncid, iu%j2rp2_varid, cb%j2rp2, start=[tslot]), "netcdf_read_frame_system nf90_getvar j2rp2_varid" ) - call check( nf90_get_var(iu%ncid, iu%j4rp4_varid, cb%j4rp4, start=[tslot]), "netcdf_read_frame_system nf90_getvar j4rp4_varid" ) + status = nf90_inq_varid(iu%ncid, J2RP2_VARNAME, iu%j2rp2_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%j2rp2_varid, cb%j2rp2, start=[tslot]), "netcdf_read_frame_system nf90_getvar j2rp2_varid" ) + else + cb%j2rp2 = 0.0_DP + end if + + status = nf90_inq_varid(iu%ncid, J4RP4_VARNAME, iu%j4rp4_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%j4rp4_varid, cb%j4rp4, start=[tslot]), "netcdf_read_frame_system nf90_getvar j4rp4_varid" ) + else + cb%j4rp4 = 0.0_DP + end if call self%read_particle_info(iu, param, plmask, tpmask) From 5c44911d57527510f7842eb831cb168ca216b96c Mon Sep 17 00:00:00 2001 From: David Minton Date: Mon, 14 Nov 2022 16:07:49 -0500 Subject: [PATCH 51/75] Fixed bugs in set_parameter and get_parameter that prevented it from being called before a bunch of parameters got set. Added "param_file" as an instance variable and began writing the run method. --- python/swiftest/swiftest/simulation_class.py | 101 +++++++++++++++---- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index c194729f8..5f3f01aa4 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -21,6 +21,7 @@ import numpy as np import numpy.typing as npt import shutil +import subprocess from typing import ( Literal, Dict, @@ -38,7 +39,7 @@ def __init__(self, codename: Literal["Swiftest", "Swifter", "Swift"] = "Swiftest", integrator: Literal["symba","rmvs","whm","helio"] = "symba", param_file: os.PathLike | str = "param.in", - read_param: bool = False, + read_param: bool = True, t0: float = 0.0, tstart: float = 0.0, tstop: float | None = None, @@ -266,12 +267,12 @@ def __init__(self, self._getter_column_width = '32' # If the parameter file is in a different location than the current working directory, we will need # to use it to properly open bin files - self.sim_dir = os.path.dirname(os.path.realpath(param_file)) + self.set_parameter(param_file=param_file) if read_param: - if os.path.exists(param_file): - self.read_param(param_file, codename=codename.title(), verbose=self.verbose) + if os.path.exists(self.param_file): + self.read_param(self.param_file, codename=codename.title(), verbose=self.verbose) else: - print(f"{param_file} not found.") + print(f"{self.param_file} not found.") self.set_parameter(codename=codename, integrator=integrator, @@ -320,6 +321,34 @@ def __init__(self, print(f"BIN_OUT file {binpath} not found.") return + def run(self,**kwargs): + """ + Runs a Swiftest integration. Uses the parameters set by the `param` dictionary unless overridden by keyword + arguments. Accepts any keyword arguments that can be passed to `set_parameter`. + + Parameters + ---------- + kwargs : Any valid keyword arguments accepted by `set_parameter`. + + Returns + ------- + None + + """ + + self.set_parameter(**kwargs) + if self.codename != "Swiftest": + print(f"Running an integration is not yet supported for {self.codename}") + return + + + term_output = subprocess.run([self.driver_executable, self.integrator, self.param_file], capture_output=True) + + # print(parameters['ncount'], ' Calling FORTRAN routine') + # with subprocess.Popen([parameters['workingdir']+'CTEM'], stdout=subprocess.PIPE, bufsize=1,universal_newlines=True) as p: + # for line in p.stdout: + # print(line, end='') + def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var: Dict | None = None): """ Internal function for getters that extracts subset of arguments that is contained in the dictionary of valid @@ -433,7 +462,7 @@ def set_simulation_time(self, if tstop is not None: if tstop <= tstart: print("Error! tstop must be greater than tstart.") - return + return {} if tstop is not None: self.param['TSTOP'] = tstop @@ -457,7 +486,7 @@ def set_simulation_time(self, if istep_out is not None and tstep_out is not None: print("Error! istep_out and tstep_out cannot both be set") - return + return {} if tstep_out is not None and dt is not None: istep_out = int(np.floor(tstep_out / dt)) @@ -586,7 +615,6 @@ def get_parameter(self, **kwargs): """ - # Getters returning parameter dictionary values param_dict = {} param_dict.update(self.get_integrator(**kwargs)) @@ -604,6 +632,7 @@ def get_parameter(self, **kwargs): def set_integrator(self, codename: Literal["swiftest", "swifter", "swift"] | None = None, integrator: Literal["symba","rmvs","whm","helio"] | None = None, + param_file: PathLike | str | None = None, mtiny: float | None = None, gmtiny: float | None = None, verbose: bool | None = None, @@ -616,6 +645,8 @@ def set_integrator(self, codename : {"swiftest", "swifter", "swift"}, optional integrator : {"symba","rmvs","whm","helio"}, optional Name of the n-body integrator that will be used when executing a run. + param_file: PathLike | str | None = None, + Name of the input parameter file to use to read in parameter values. mtiny : float, optional The minimum mass of fully interacting bodies. Bodies below this mass interact with the larger bodies, but not each other (SyMBA only). *Note.* Only mtiny or gmtiny is accepted, not both. @@ -636,11 +667,16 @@ def set_integrator(self, """ # TODO: Improve how it finds the executable binary - if integrator is None and codename is None: - return - update_list = [] + # Set defaults + if "codename" not in dir(self): + self.codename = "Swiftest" + if "integerator" not in dir(self): + self.integrator = "symba" + if "driver_executable" not in dir(self): + self.driver_executable = None + if codename is not None: valid_codename = ["Swiftest", "Swifter", "Swift"] if codename.title() not in valid_codename: @@ -674,6 +710,10 @@ def set_integrator(self, self.integrator = integrator.lower() update_list.append("integrator") + if param_file is not None: + self.param_file = os.path.realpath(param_file) + self.sim_dir = os.path.dirname(self.param_file) + if mtiny is not None or gmtiny is not None: if self.integrator != "symba": print("mtiny and gmtiny are only used by SyMBA.") @@ -716,7 +756,9 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | valid_var = {"codename": "! VERSION", "gmtiny" : "GMTINY"} - valid_instance_vars = {"integrator": self.integrator, + valid_instance_vars = {"param_file": self.param_file, + "sim_dir" : self.sim_dir, + "integrator": self.integrator, "codename": self.codename, "driver_executable": self.driver_executable} @@ -1001,8 +1043,8 @@ def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | N def set_init_cond_files(self, init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"] | None = None, - init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[ - str, os.PathLike] | None = None, + init_cond_file_name: str | os.PathLike | Dict[str, str] | + Dict[ str, os.PathLike] | None = None, init_cond_format: Literal["EL", "XV"] | None = None, verbose: bool | None = None, **kwargs: Any @@ -1055,6 +1097,9 @@ def set_init_cond_files(self, if init_cond_format is not None: update_list.append("init_cond_format") + if len(update_list) == 0: + return {} + def ascii_file_input_error_msg(codename): print(f"Error in set_init_cond_files: init_cond_file_name must be a dictionary of the form: ") print('{') @@ -1063,7 +1108,7 @@ def ascii_file_input_error_msg(codename): print('"PL" : *path to massive body initial conditions file*,') print('"TP" : *path to test particle initial conditions file*') print('}') - return + return {} if init_cond_format is None: if "IN_FORM" in self.param: @@ -1249,6 +1294,8 @@ def set_output_files(self, update_list.append("output_file_name") if output_format is not None: update_list.append("output_format") + if len(update_list) == 0: + return {} if self.codename == "Swiftest": if output_file_type is None: @@ -1425,6 +1472,13 @@ def set_unit_system(self, DU2M_old = None TU2S_old = None + if "MU_name" not in dir(self): + self.MU_name = None + if "DU_name" not in dir(self): + self.DU_name = None + if "TU_name" not in dir(self): + self.TU_name = None + update_list = [] if MU is not None or MU2KG is not None: update_list.append("MU") @@ -1506,8 +1560,10 @@ def set_unit_system(self, if TU_name is not None: self.TU_name = TU_name - self.VU_name = f"{self.DU_name}/{self.TU_name}" - self.GU = constants.GC * self.param['TU2S'] ** 2 * self.param['MU2KG'] / self.param['DU2M'] ** 3 + if "DU_name" in dir(self) and "TU_name" in dir(self): + self.VU_name = f"{self.DU_name}/{self.TU_name}" + if all(key in self.param for key in ["MU2KG","DU2M","TU2S"]): + self.GU = constants.GC * self.param["TU2S"] ** 2 * self.param["MU2KG"] / self.param["DU2M"] ** 3 if recompute_unit_values: self.update_param_units(MU2KG_old, DU2M_old, TU2S_old) @@ -1539,21 +1595,23 @@ def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool """ + + valid_var = { "MU": "MU2KG", "DU": "DU2M", "TU": "TU2S", } - if self.MU_name is None: + if "MU_name" not in dir(self) or self.MU_name is None: MU_name = "mass unit" else: MU_name = self.MU_name - if self.DU_name is None: + if "DU_name" not in dir(self) or self.DU_name is None: DU_name = "distance unit" else: DU_name = self.DU_name - if self.TU_name is None: + if "TU_name" not in dir(self) or self.TU_name is None: TU_name = "time unit" else: TU_name = self.TU_name @@ -1654,6 +1712,8 @@ def set_distance_range(self, A dictionary containing the requested parameters. """ + if rmax is None and rmin is None and qmin_coord is None: + return {} update_list = [] CHK_QMIN_RANGE = self.param.pop('CHK_QMIN_RANGE', None) @@ -1904,7 +1964,6 @@ def set_ephemeris_date(self, if ephemeris_date is None: return - # The default value is Prof. Minton's Brimley/Cocoon line crossing date (aka MBCL) minton_bday = datetime.date.fromisoformat('1976-08-05') brimley_cocoon_line = datetime.timedelta(days=18530) From 37230e0f0c5472f59c9f06d6aa5f0dae00cedeb1 Mon Sep 17 00:00:00 2001 From: David Minton Date: Mon, 14 Nov 2022 16:35:18 -0500 Subject: [PATCH 52/75] Cleaned up the methods so that the defaults make more sense and less has to be explicitly specified with each method call --- .../Basic_Simulation/initial_conditions.py | 4 +- python/swiftest/swiftest/simulation_class.py | 77 ++++++++++++++----- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index 91471fd7f..de5a8fcad 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -73,4 +73,6 @@ sim.add_body(name_tp, a_tp, e_tp, inc_tp, capom_tp, omega_tp, capm_tp) # Save everything to a set of initial conditions files -sim.save('param.in') +sim.run() + +print("All done!") diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 5f3f01aa4..52f9321a2 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -328,7 +328,7 @@ def run(self,**kwargs): Parameters ---------- - kwargs : Any valid keyword arguments accepted by `set_parameter`. + **kwargs : Any valid keyword arguments accepted by `set_parameter` or `save' Returns ------- @@ -337,12 +337,14 @@ def run(self,**kwargs): """ self.set_parameter(**kwargs) + self.save(**kwargs) if self.codename != "Swiftest": print(f"Running an integration is not yet supported for {self.codename}") return + print(f"Running a simulation from tstart={self.param['TSTART']} {self.TU_name} to tstop={self.param['TSTOP']} {self.TU_name}") - term_output = subprocess.run([self.driver_executable, self.integrator, self.param_file], capture_output=True) + #term_output = subprocess.run([self.driver_executable, self.integrator, self.param_file], capture_output=True) # print(parameters['ncount'], ' Calling FORTRAN routine') # with subprocess.Popen([parameters['workingdir']+'CTEM'], stdout=subprocess.PIPE, bufsize=1,universal_newlines=True) as p: @@ -645,7 +647,7 @@ def set_integrator(self, codename : {"swiftest", "swifter", "swift"}, optional integrator : {"symba","rmvs","whm","helio"}, optional Name of the n-body integrator that will be used when executing a run. - param_file: PathLike | str | None = None, + param_file: str or path-like, optional Name of the input parameter file to use to read in parameter values. mtiny : float, optional The minimum mass of fully interacting bodies. Bodies below this mass interact with the larger bodies, @@ -2256,22 +2258,39 @@ def read_param(self, param_file, codename="Swiftest", verbose=True): self.codename = "Unknown" return - def write_param(self, param_file, param=None): + def write_param(self, + codename: Literal["Swiftest", "Swifter", "Swift"] | None = None, + param_file: str | PathLike | None = None, + param: Dict | None = None, + **kwargs: Any): """ Writes to a param.in file and determines whether the output format needs to be converted between Swift/Swifter/Swiftest. Parameters ---------- - param_file : string - File name of the input parameter file + codename : {"Swiftest", "Swifter", "Swift"}, optional + Alternative name of the n-body code that the parameter file will be formatted for. Defaults to current instance + variable self.codename + param_file : str or path-like, optional + Alternative file name of the input parameter file. Defaults to current instance variable self.param_file + param: Dict, optional + An alternative parameter dictionary to write out. Defaults to the current instance variable self.param + **kwargs + A dictionary of additional keyword argument. These are ignored. + Returns ------- - self.ds : xarray dataset + None """ + + if codename is None: + codename = self.codename + if param_file is None: + param_file = self.param_file if param is None: param = self.param + # Check to see if the parameter type matches the output type. If not, we need to convert - codename = param['! VERSION'].split()[0] if codename == "Swifter" or codename == "Swiftest": if param['IN_TYPE'] == "ASCII": param.pop("NC_IN", None) @@ -2409,32 +2428,48 @@ def follow(self, codestyle="Swifter"): if self.verbose: print('follow.out written') return fol - def save(self, param_file, framenum=-1, codename="Swiftest"): + def save(self, + codename: Literal["Swiftest", "Swifter", "Swift"] | None = None, + param_file: str | PathLike | None = None, + param: Dict | None = None, + framenum: int = -1, + **kwargs: Any): """ Saves an xarray dataset to a set of input files. Parameters ---------- - param_file : string - Name of the parameter input file - framenum : integer (default=-1) - Time frame to use to generate the initial conditions. If this argument is not passed, the default is to use the last frame in the dataset. - codename : string - Name of the desired format (Swift/Swifter/Swiftest) + codename : {"Swiftest", "Swifter", "Swift"}, optional + Alternative name of the n-body code that the parameter file will be formatted for. Defaults to current instance + variable self.codename + param_file : str or path-like, optional + Alternative file name of the input parameter file. Defaults to current instance variable self.param_file + param: Dict, optional + An alternative parameter dictionary to write out. Defaults to the current instance variable self.param + framenum : int Default=-1 + Time frame to use to generate the initial conditions. If this argument is not passed, the default is to use the last frame in the dataset. + **kwargs + A dictionary of additional keyword argument. These are ignored. Returns ------- - self.ds : xarray dataset + None """ + if codename is None: + codename = self.codename + if param_file is None: + param_file = self.param_file + if param is None: + param = self.param if codename == "Swiftest": - io.swiftest_xr2infile(ds=self.ds, param=self.param, in_type=self.param['IN_TYPE'], framenum=framenum) - self.write_param(param_file) + io.swiftest_xr2infile(ds=self.ds, param=param, in_type=self.param['IN_TYPE'], framenum=framenum) + self.write_param(param_file=param_file) elif codename == "Swifter": - if self.codename == "Swiftest": - swifter_param = io.swiftest2swifter_param(self.param) + if codename == "Swiftest": + swifter_param = io.swiftest2swifter_param(param) else: - swifter_param = self.param + swifter_param = param io.swifter_xr2infile(self.ds, swifter_param, framenum) self.write_param(param_file, param=swifter_param) else: From 18dd938319e35055488228169ea672d2a73e2b78 Mon Sep 17 00:00:00 2001 From: David Minton Date: Mon, 14 Nov 2022 16:48:34 -0500 Subject: [PATCH 53/75] Cleaned up more methods to make ready to run a simulationj --- examples/Basic_Simulation/param.in | 20 ++++++++++---------- python/swiftest/swiftest/io.py | 13 +++++++++---- python/swiftest/swiftest/simulation_class.py | 9 ++++++--- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/examples/Basic_Simulation/param.in b/examples/Basic_Simulation/param.in index 47498726c..03fbfc38f 100644 --- a/examples/Basic_Simulation/param.in +++ b/examples/Basic_Simulation/param.in @@ -1,14 +1,17 @@ ! VERSION Swiftest parameter input T0 0.0 +TSTART 0.0 TSTOP 10.0 DT 0.005 ISTEP_OUT 200 ISTEP_DUMP 200 +NC_IN init_cond.nc +IN_TYPE NETCDF_DOUBLE +IN_FORM EL +BIN_OUT bin.nc OUT_FORM XVEL OUT_TYPE NETCDF_DOUBLE OUT_STAT REPLACE -IN_TYPE NETCDF_DOUBLE -BIN_OUT bin.nc CHK_QMIN 0.004650467260962157 CHK_RMIN 0.004650467260962157 CHK_RMAX 10000.0 @@ -18,9 +21,10 @@ CHK_QMIN_RANGE 0.004650467260962157 10000.0 MU2KG 1.988409870698051e+30 TU2S 31557600.0 DU2M 149597870700.0 +GMTINY 9.869231602224408e-07 +MIN_GMFRAG 9.869231602224408e-10 RESTART NO -INTERACTION_LOOPS TRIANGULAR -ENCOUNTER_CHECK TRIANGULAR +TIDES NO CHK_CLOSE YES GR YES FRAGMENTATION YES @@ -29,9 +33,5 @@ ENERGY NO EXTRA_FORCE NO BIG_DISCARD NO RHILL_PRESENT NO -TIDES NO -IN_FORM EL -NC_IN init_cond.nc -TSTART 0.0 -GMTINY 1e-06 -MIN_GMFRAG 1e-09 +INTERACTION_LOOPS TRIANGULAR +ENCOUNTER_CHECK TRIANGULAR diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 50c84751f..8c93e2e7d 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -404,18 +404,21 @@ def write_labeled_param(param, param_file_name): outfile = open(param_file_name, 'w') keylist = ['! VERSION', 'T0', + 'TSTART', 'TSTOP', 'DT', 'ISTEP_OUT', 'ISTEP_DUMP', - 'OUT_FORM', - 'OUT_TYPE', - 'OUT_STAT', - 'IN_TYPE', + 'NC_IN', 'PL_IN', 'TP_IN', 'CB_IN', + 'IN_TYPE', + 'IN_FORM', 'BIN_OUT', + 'OUT_FORM', + 'OUT_TYPE', + 'OUT_STAT', 'CHK_QMIN', 'CHK_RMIN', 'CHK_RMAX', @@ -425,6 +428,8 @@ def write_labeled_param(param, param_file_name): 'MU2KG', 'TU2S', 'DU2M', + 'GMTINY', + 'MIN_GMFRAG', 'RESTART'] ptmp = param.copy() # Print the list of key/value pairs in the preferred order diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 52f9321a2..0143c8fb9 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -342,7 +342,7 @@ def run(self,**kwargs): print(f"Running an integration is not yet supported for {self.codename}") return - print(f"Running a simulation from tstart={self.param['TSTART']} {self.TU_name} to tstop={self.param['TSTOP']} {self.TU_name}") + print(f"Running a {self.codename} {self.integrator} run from tstart={self.param['TSTART']} {self.TU_name} to tstop={self.param['TSTOP']} {self.TU_name}") #term_output = subprocess.run([self.driver_executable, self.integrator, self.param_file], capture_output=True) @@ -436,6 +436,9 @@ def set_simulation_time(self, A dictionary containing the requested parameters """ + if t0 is None and tstart is None and dt is None and istep_out is None and \ + tstep_out is None and istep_dump is None: + return {} update_list = [] @@ -2289,6 +2292,7 @@ def write_param(self, param_file = self.param_file if param is None: param = self.param + print(f"Writing parameter inputs to file {param_file}") # Check to see if the parameter type matches the output type. If not, we need to convert if codename == "Swifter" or codename == "Swiftest": @@ -2302,8 +2306,7 @@ def write_param(self, elif codename == "Swift": io.write_swift_param(param, param_file) else: - print( - 'Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') + print( 'Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') return def convert(self, param_file, newcodename="Swiftest", plname="pl.swiftest.in", tpname="tp.swiftest.in", From 44f3b4059ca3e194b9f684a7eda9f66f44613e76 Mon Sep 17 00:00:00 2001 From: David Minton Date: Mon, 14 Nov 2022 16:49:19 -0500 Subject: [PATCH 54/75] added subprocess call to run sim --- python/swiftest/swiftest/simulation_class.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 0143c8fb9..27ef73774 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -344,12 +344,7 @@ def run(self,**kwargs): print(f"Running a {self.codename} {self.integrator} run from tstart={self.param['TSTART']} {self.TU_name} to tstop={self.param['TSTOP']} {self.TU_name}") - #term_output = subprocess.run([self.driver_executable, self.integrator, self.param_file], capture_output=True) - - # print(parameters['ncount'], ' Calling FORTRAN routine') - # with subprocess.Popen([parameters['workingdir']+'CTEM'], stdout=subprocess.PIPE, bufsize=1,universal_newlines=True) as p: - # for line in p.stdout: - # print(line, end='') + subprocess.run([self.driver_executable, self.integrator, self.param_file], capture_output=True) def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var: Dict | None = None): """ From 00ded00a14d0e48c62d772e5554f5b5ce35a9ae5 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Mon, 14 Nov 2022 16:58:20 -0500 Subject: [PATCH 55/75] Rearranged the outputs in the get_integrator method to avoid duplication --- python/swiftest/swiftest/simulation_class.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 27ef73774..558e636a4 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -756,10 +756,9 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | valid_var = {"codename": "! VERSION", "gmtiny" : "GMTINY"} - valid_instance_vars = {"param_file": self.param_file, - "sim_dir" : self.sim_dir, + valid_instance_vars = {"codename": self.codename, "integrator": self.integrator, - "codename": self.codename, + "param_file": self.param_file, "driver_executable": self.driver_executable} try: @@ -781,14 +780,14 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | arg_list = list(valid_instance_vars.keys()) arg_list.append(*[a for a in valid_var.keys() if a not in valid_instance_vars]) - integrator = self._get_instance_var(arg_list, valid_instance_vars, verbose, **kwargs) + inst_var = self._get_instance_var(arg_list, valid_instance_vars, verbose, **kwargs) valid_arg, integrator_dict = self._get_valid_arg_list(arg_list, valid_var) if verbose: for arg in valid_arg: key = valid_var[arg] - if key in integrator_dict: + if key in integrator_dict and key not in inst_var: if arg == "gmtiny": if self.integrator == "symba": print(f"{arg:<{self._getter_column_width}} {integrator_dict[key]} {self.DU_name}^3 / {self.TU_name}^2 ") From 0477c60f35f8ef4effbc6620291e81af7e492b03 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Mon, 14 Nov 2022 17:37:47 -0500 Subject: [PATCH 56/75] Fixed executable path location --- python/swiftest/swiftest/simulation_class.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 558e636a4..9ee1b77e0 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -338,13 +338,19 @@ def run(self,**kwargs): self.set_parameter(**kwargs) self.save(**kwargs) + if self.codename != "Swiftest": print(f"Running an integration is not yet supported for {self.codename}") return + if self.driver_executable is None: + print("Path to swiftest_driver has not been set!") + return + print(f"Running a {self.codename} {self.integrator} run from tstart={self.param['TSTART']} {self.TU_name} to tstop={self.param['TSTOP']} {self.TU_name}") - subprocess.run([self.driver_executable, self.integrator, self.param_file], capture_output=True) + term = subprocess.run([self.driver_executable, self.integrator, self.param_file], capture_output=True) + return def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var: Dict | None = None): """ @@ -691,11 +697,14 @@ def set_integrator(self, self.param['! VERSION'] = f"{self.codename} parameter input" update_list.append("codename") if self.codename == "Swiftest": - self.binary_path = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(_pyfile)),os.pardir,os.pardir,os.pardir,"build")) + self.binary_path = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(_pyfile)),os.pardir,os.pardir,os.pardir,"bin")) self.driver_executable = os.path.join(self.binary_path,"swiftest_driver") + if not os.path.exists(self.driver_executable): + print(f"Cannot find the Swiftest driver in {self.binary_path}") + self.driver_executable = None else: self.binary_path = "NOT SET" - self.driver_executable = "NOT SET" + self.driver_executable = None update_list.append("driver_executable") if integrator is not None: From dcc13e0586167b9c0452ee24c09bb948e775c54b Mon Sep 17 00:00:00 2001 From: David A Minton Date: Mon, 14 Nov 2022 19:50:27 -0500 Subject: [PATCH 57/75] Fixed subprocess output. Now I'll need to figure out how to get library paths correct --- python/swiftest/swiftest/simulation_class.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 9ee1b77e0..20c16b1f4 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -349,7 +349,9 @@ def run(self,**kwargs): print(f"Running a {self.codename} {self.integrator} run from tstart={self.param['TSTART']} {self.TU_name} to tstop={self.param['TSTOP']} {self.TU_name}") - term = subprocess.run([self.driver_executable, self.integrator, self.param_file], capture_output=True) + with subprocess.Popen([self.driver_executable, self.integrator, self.param_file], stdout=subprocess.PIPE, bufsize=1,universal_newlines=True) as p: + for line in p.stdout: + print(line, end='') return def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var: Dict | None = None): From a0aa03dc071640cdec2c1efbb70d7393c3724a17 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Tue, 15 Nov 2022 08:27:17 -0500 Subject: [PATCH 58/75] Removed duplicate dictionary entry --- python/swiftest/swiftest/io.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 8c93e2e7d..64c4dc4a1 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -295,7 +295,6 @@ def read_swift_param(param_file_name, startfile="swift.in", verbose=True): 'DTOUT': 0.0, 'DTDUMP': 0.0, 'L1': "F", - 'L1': "F", 'L2': "F", 'L3': "F", 'L4': "F", From c34be4917c51ac7f6e18640baea40ac5fe69c8d4 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Tue, 15 Nov 2022 08:27:42 -0500 Subject: [PATCH 59/75] Rearranged the order of variables to output in get_feature --- python/swiftest/swiftest/simulation_class.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 20c16b1f4..01ad0a866 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -1017,17 +1017,17 @@ def get_feature(self, arg_list: str | List[str] | None = None, verbose: bool | N """ valid_var = {"close_encounter_check": "CHK_CLOSE", - "general_relativity": "GR", "fragmentation": "FRAGMENTATION", + "minimum_fragment_gmass": "MIN_GMFRAG", "rotation": "ROTATION", + "general_relativity": "GR", "compute_conservation_values": "ENERGY", + "rhill_present": "RHILL_PRESENT", "extra_force": "EXTRA_FORCE", "big_discard": "BIG_DISCARD", - "rhill_present": "RHILL_PRESENT", - "restart": "RESTART", "interaction_loops": "INTERACTION_LOOPS", "encounter_check_loops": "ENCOUNTER_CHECK", - "minimum_fragment_gmass" : "MIN_GMFRAG" + "restart": "RESTART" } valid_arg, feature_dict = self._get_valid_arg_list(arg_list, valid_var) From d72ffca52907f4626740bc1f8b65dce6960b349c Mon Sep 17 00:00:00 2001 From: David Minton Date: Tue, 15 Nov 2022 12:52:59 -0500 Subject: [PATCH 60/75] Restructured arguments to be able to follow a priority order in overriding arguments from defaults (lowest) to inputs from file (higher) to arguments passed to Swiftest() (highest). --- examples/Basic_Simulation/param.in | 4 +- python/swiftest/swiftest/io.py | 3 +- python/swiftest/swiftest/simulation_class.py | 374 +++++++++++-------- 3 files changed, 224 insertions(+), 157 deletions(-) diff --git a/examples/Basic_Simulation/param.in b/examples/Basic_Simulation/param.in index 03fbfc38f..014bf1fb8 100644 --- a/examples/Basic_Simulation/param.in +++ b/examples/Basic_Simulation/param.in @@ -1,4 +1,4 @@ -! VERSION Swiftest parameter input +! VERSION Swiftest input file T0 0.0 TSTART 0.0 TSTOP 10.0 @@ -24,7 +24,6 @@ DU2M 149597870700.0 GMTINY 9.869231602224408e-07 MIN_GMFRAG 9.869231602224408e-10 RESTART NO -TIDES NO CHK_CLOSE YES GR YES FRAGMENTATION YES @@ -35,3 +34,4 @@ BIG_DISCARD NO RHILL_PRESENT NO INTERACTION_LOOPS TRIANGULAR ENCOUNTER_CHECK TRIANGULAR +TIDES NO diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 64c4dc4a1..64024c2d9 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -152,8 +152,7 @@ def read_swiftest_param(param_file_name, param, verbose=True): param : dict A dictionary containing the entries in the user parameter file """ - param['! VERSION'] = f"Swiftest parameter input from file {param_file_name}" - + param['! VERSION'] = f"Swiftest parameter input file" # Read param.in file if verbose: print(f'Reading Swiftest file {param_file_name}') diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 01ad0a866..c8f6ebf34 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -35,93 +35,70 @@ class Simulation: This is a class that defines the basic Swift/Swifter/Swiftest simulation object """ - def __init__(self, - codename: Literal["Swiftest", "Swifter", "Swift"] = "Swiftest", - integrator: Literal["symba","rmvs","whm","helio"] = "symba", - param_file: os.PathLike | str = "param.in", - read_param: bool = True, - t0: float = 0.0, - tstart: float = 0.0, - tstop: float | None = None, - dt: float | None = None, - istep_out: int | None = None, - tstep_out: float | None = None, - istep_dump: int | None = None, - init_cond_file_type: Literal["NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"] = "NETCDF_DOUBLE", - init_cond_file_name: str | os.PathLike | Dict[str, str] | Dict[str, os.PathLike] | None = None, - init_cond_format: Literal["EL", "XV"] = "EL", - output_file_type: Literal[ - "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"] = "NETCDF_DOUBLE", - output_file_name: os.PathLike | str | None = None, - output_format: Literal["XV", "XVEL"] = "XVEL", - read_old_output_file: bool = False, - MU: str = "MSUN", - DU: str = "AU", - TU: str = "Y", - MU2KG: float | None = None, - DU2M: float | None = None, - TU2S: float | None = None, - MU_name: str | None = None, - DU_name: str | None = None, - TU_name: str | None = None, - rmin: float = constants.RSun / constants.AU2M, - rmax: float = 10000.0, - qmin_coord: Literal["HELIO","BARY"] = "HELIO", - gmtiny: float | None = None, - mtiny: float | None = None, - close_encounter_check: bool = True, - general_relativity: bool = True, - fragmentation: bool = False, - minimum_fragment_mass: float | None = None, - minimum_fragment_gmass: float | None = None, - rotation: bool = True, - compute_conservation_values: bool = False, - extra_force: bool = False, - big_discard: bool = False, - rhill_present: bool = False, - restart: bool = False, - interaction_loops: Literal["TRIANGULAR", "FLAT", "ADAPTIVE"] = "TRIANGULAR", - encounter_check_loops: Literal["TRIANGULAR", "SORTSWEEP", "ADAPTIVE"] = "TRIANGULAR", - ephemeris_date: str = "MBCL", - verbose: bool = True - ): + def __init__(self,read_param: bool = True, **kwargs: Any): """ Parameters ---------- + read_param : bool, default True + If true, read in a pre-existing parameter input file given by the argument `param_file` if it exists. + Otherwise, create a new parameter file using the arguments passed to Simulation or defaults + + Parameters for a given Simulation object can be set a number of different ways, including via a parameter input + file, arguments to Simulation, the general `set_parameter` method, or the specific setters for groups of + similar parameters (e.g. set_init_cond_files, set_simulation_time, etc.). Each parameter has a default value + that can be overridden by an argument to Simulation(). Some argument parameters have equivalent values that + are passed to the `swiftest_driver` Fortran program via a parameter input file. When declaring a new + Simulation object, parameters are chosen in the following way, from highest to lowest priority" + 1. Arguments to Simulation() + 2. The parameter input file given by `param_file` under the following conditions: + - `read_param` is set to True (default behavior). + - The file given by `param_file` exists. The default file is `param.in` located in the current working + directory, which can be changed by passing `param_file` as an argument. + - The argument has an equivalent parameter or set of parameters in the parameter input file. + 3. Default values (see below) + + **kwargs : See list of valid parameters and their defaults below + codename : {"Swiftest", "Swifter", "Swift"}, default "Swiftest" Name of the n-body code that will be used. + Parameter input file equivalent: None integrator : {"symba","rmvs","whm","helio"}, default "symba" Name of the n-body integrator that will be used when executing a run. + Parameter input file equivalent: None param_file : str, path-like, or file-lke, default "param.in" Name of the parameter input file that will be passed to the integrator. - read_param : bool, default False - If true, read in a pre-existing parameter input file given by the argument `param_file`. Otherwise, create - a new parameter file using the arguments passed to Simulation. - > *Note:* If set to true, the parameters defined in the input file will override any passed into the - > arguments of Simulation. + Parameter input file equivalent: None t0 : float, default 0.0 The reference time for the start of the simulation. Defaults is 0.0. + Parameter input file equivalent: `T0` tstart : float, default 0.0 The start time for a restarted simulation. For a new simulation, tstart will be set to t0 automatically. + Parameter input file equivalent: `TSTART` tstop : float, optional The stopping time for a simulation. `tstop` must be greater than `tstart`. + Parameter input file equivalent: `TSTOP` dt : float, optional The step size of the simulation. `dt` must be less than or equal to `tstop-dstart`. + Parameter input file equivalent: `DT` istep_out : int, optional The number of time steps between outputs to file. *Note*: only `istep_out` or `toutput` can be set. + Parameter input file equivalent: `ISTEP_OUT` tstep_out : float, optional The approximate time between when outputs are written to file. Passing this computes `istep_out = floor(tstep_out/dt)`. *Note*: only `istep_out` or `toutput` can be set. + Parameter input file equivalent: None istep_dump : int, optional The anumber of time steps between outputs to dump file. If not set, this will be set to the value of `istep_out` (or the equivalent value determined by `tstep_out`). + Parameter input file equivalent: `ISTEP_DUMP` init_cond_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"}, default "NETCDF_DOUBLE" The file type containing initial conditions for the simulation: * NETCDF_DOUBLE: A single initial conditions input file in NetCDF file format of type NETCDF_DOUBLE. * NETCDF_FLOAT: A single initial conditions input file in NetCDF file format of type NETCDF_FLOAT. * ASCII : Three initial conditions files in ASCII format. The individual files define the central body, massive body, and test particle initial conditions. + Parameter input file equivalent: `IN_TYPE` init_cond_file_name : str, path-like, or dict, optional Name of the input initial condition file or files. Whether to pass a single file name or a dictionary depends on the argument passed to `init_cond_file_type`: If `init_cond_file_type={"NETCDF_DOUBLE","NETCDF_FLOAT"}`, @@ -134,36 +111,44 @@ def __init__(self, If no file name is provided then the following default file names will be used. * NETCDF_DOUBLE, NETCDF_FLOAT: `init_cond_file_name = "init_cond.nc"` * ASCII: `init_cond_file_name = {"CB" : "cb.in", "PL" : "pl.in", "TP" : "tp.in"}` + Parameter input file equivalent: `NC_IN`, `CB_IN`, `PL_IN`, `TP_IN` init_cond_format : {"EL", "XV"}, default "EL" Indicates whether the input initial conditions are given as orbital elements or cartesian position and velocity vectors. > *Note:* If `codename` is "Swift" or "Swifter", EL initial conditions are converted to XV. + Parameter input file equivalent: `IN_FORM` output_file_type : {"NETCDF_DOUBLE", "NETCDF_FLOAT","REAL4","REAL8","XDR4","XDR8"}, default "NETCDF_DOUBLE" The file type for the outputs of the simulation. Compatible file types depend on the `codename` argument. * Swiftest: Only "NETCDF_DOUBLE" or "NETCDF_FLOAT" supported. * Swifter: Only "REAL4","REAL8","XDR4" or "XDR8" supported. * Swift: Only "REAL4" supported. + Parameter input file equivalent: `OUT_TYPE` output_file_name : str or path-like, optional Name of output file to generate. If not supplied, then one of the default file names are used, depending on the value passed to `output_file_type`. If one of the NetCDF types are used, the default is "bin.nc". Otherwise, the default is "bin.dat". + Parameter input file equivalent: `BIN_OUT` output_format : {"XV","XVEL"}, default "XVEL" Specifies the format for the data saved to the output file. If "XV" then cartesian position and velocity vectors for all bodies are stored. If "XVEL" then the orbital elements are also stored. + Parameter input file equivalent: `OUT_FORM` read_old_output_file : bool, default False - If true, read in a pre-existing binary input file given by the argument `output_file_name`. + If true, read in a pre-existing binary input file given by the argument `output_file_name` if it exists. + Parameter input file equivalent: None MU : str, default "MSUN" - The mass unit system to use. Case-insensitive valid options are: - * "Msun" : Solar mass - * "Mearth" : Earth mass - * "kg" : kilograms - * "g" : grams + The mass unit system to use. Case-insensitive valid options are: + * "Msun" : Solar mass + * "Mearth" : Earth mass + * "kg" : kilograms + * "g" : grams + Parameter input file equivalent: None DU : str, optional The distance unit system to use. Case-insensitive valid options are: * "AU" : Astronomical Unit * "Rearth" : Earth radius * "m" : meter * "cm" : centimeter + Parameter input file equivalent: None TU : str, optional The time unit system to use. Case-insensitive valid options are: * "YR" : Year @@ -171,69 +156,92 @@ def __init__(self, * "d" : Julian day * "JD" : Julian day * "s" : second + Parameter input file equivalent: None MU2KG: float, optional The conversion factor to multiply by the mass unit that would convert it to kilogram. Setting this overrides MU + Parameter input file equivalent: `MU2KG` DU2M : float, optional The conversion factor to multiply by the distance unit that would convert it to meter. Setting this overrides DU + Parameter input file equivalent: `DU2M` TU2S : float, optional The conversion factor to multiply by the time unit that would convert it to seconds. Setting this overrides TU + Parameter input file equivalent: `TU2S` MU_name : str, optional The name of the mass unit. When setting one of the standard units via `MU` a name will be automatically set for the unit, so this argument will override the automatic name. + Parameter input file equivalent: None DU_name : str, optional The name of the distance unit. When setting one of the standard units via `DU` a name will be automatically set for the unit, so this argument will override the automatic name. + Parameter input file equivalent: None TU_name : str, optional The name of the time unit. When setting one of the standard units via `TU` a name will be automatically set for the unit, so this argument will override the automatic name. + Parameter input file equivalent: None rmin : float, default value is the radius of the Sun in the unit system defined by the unit input arguments. - Minimum distance of the simulation (CHK_QMIN, CHK_RMIN, CHK_QMIN_RANGE[0]) + Minimum distance of the simulation + Parameter input file equivalent: `CHK_QMIN`, `CHK_RMIN`, `CHK_QMIN_RANGE[0]` rmax : float, default value is 10000 AU in the unit system defined by the unit input arguments. Maximum distance of the simulation (CHK_RMAX, CHK_QMIN_RANGE[1]) + Parameter input file equivalent: `CHK_RMAX`, `CHK_QMIN_RANGE[1]` qmin_coord : str, {"HELIO", "BARY"}, default "HELIO" - coordinate frame to use for CHK_QMIN + coordinate frame to use for checking the minimum periapsis distance + Parameter input file equivalent: `QMIN_COORD` mtiny : float, optional The minimum mass of fully interacting bodies. Bodies below this mass interact with the larger bodies, but not each other (SyMBA only). *Note.* Only mtiny or gmtiny is accepted, not both. + Parameter input file equivalent: None gmtiny : float, optional The minimum G*mass of fully interacting bodies. Bodies below this mass interact with the larger bodies, but not each other (SyMBA only). *Note.* Only mtiny or gmtiny is accepted, not both. + Parameter input file equivalent: `GMTINY` close_encounter_check : bool, default True Check for close encounters between bodies. If set to True, then the radii of massive bodies must be included in initial conditions. + Parameter input file equivalent: `CHK_CLOSE` general_relativity : bool, default True Include the post-Newtonian correction in acceleration calculations. + Parameter input file equivalent: `GR` fragmentation : bool, default True If set to True, this turns on the Fraggle fragment generation code and `rotation` must also be True. This argument only applies to Swiftest-SyMBA simulations. It will be ignored otherwise. + Parameter input file equivalent: `FRAGMENTATION` minimum_fragment_gmass : float, optional If fragmentation is turned on, this sets the mimimum G*mass of a collisional fragment that can be generated. *Note.* Only set one of minimum_fragment_gmass or minimum_fragment_mass + Parameter input file equivalent: None minimum_fragment_mass : float, optional If fragmentation is turned on, this sets the mimimum mass of a collisional fragment that can be generated. *Note.* Only set one of minimum_fragment_gmass or minimum_fragment_mass - rotation : bool, default True + Parameter input file equivalent: `MIN_GMFRAG` + rotation : bool, default False If set to True, this turns on rotation tracking and radius, rotation vector, and moments of inertia values must be included in the initial conditions. This argument only applies to Swiftest-SyMBA simulations. It will be ignored otherwise. + Parameter input file equivalent: `ROTATION` compute_conservation_values : bool, default False Turns on the computation of energy, angular momentum, and mass conservation and reports the values every output step of a running simulation. + Parameter input file equivalent: `ENERGY` extra_force: bool, default False Turns on user-defined force function. + Parameter input file equivalent: `EXTRA_FORCE` big_discard: bool, default False Includes big bodies when performing a discard (Swifter only) + Parameter input file equivalent: `BIG_DISCARD` rhill_present: bool, default False - Include the Hill's radius with the input files. + Include the Hill's radius with the input files . + Parameter input file equivalent: `RHILL_PRESENT` restart : bool, default False If true, will restart an old run. The file given by `output_file_name` must exist before the run can execute. If false, will start a new run. If the file given by `output_file_name` exists, it will be replaced when the run is executed. + Parameter input file equivalent: `OUT_STAT` interaction_loops : {"TRIANGULAR","FLAT","ADAPTIVE"}, default "TRIANGULAR" - *Swiftest Experimental feature* + > *Swiftest Experimental feature* Specifies which algorithm to use for the computation of body-body gravitational forces. * "TRIANGULAR" : Upper-triangular double-loops . * "FLAT" : Body-body interation pairs are flattened into a 1-D array. @@ -242,8 +250,9 @@ def __init__(self, be assured, as the choice of algorithm depends on possible external factors that can influence the wall time calculation. The exact floating-point results of the interaction will be different between the two algorithm types. + Parameter input file equivalent: `INTERACTION_LOOPS` encounter_check_loops : {"TRIANGULAR","SORTSWEEP","ADAPTIVE"}, default "TRIANGULAR" - *Swiftest Experimental feature* + > *Swiftest Experimental feature* Specifies which algorithm to use for checking whether bodies are in a close encounter state or not. * "TRIANGULAR" : Upper-triangular double-loops. * "SORTSWEEP" : A Sort-Sweep algorithm is used to reduce the population of potential close encounter bodies. @@ -254,65 +263,60 @@ def __init__(self, be assured, as the choice of algorithm depends on possible external factors that can influence the wall time calculation. The exact floating-point results of the interaction will be different between the two algorithm types. + Parameter input file equivalent: `ENCOUNTER_CHECK` verbose : bool, default True If set to True, then more information is printed by Simulation methods as they are executed. Setting to False suppresses most messages other than errors. + Parameter input file equivalent: None """ - self.param = {} - self.ds = xr.Dataset() - self.verbose = verbose - self.restart = restart # Width of the column in the printed name of the parameter in parameter getters self._getter_column_width = '32' - # If the parameter file is in a different location than the current working directory, we will need - # to use it to properly open bin files - self.set_parameter(param_file=param_file) + + self.param = {} + self.ds = xr.Dataset() + + # Parameters are set in reverse priority order. First the defaults, then values from a pre-existing input file, + # then using the arguments passed via **kwargs. + + #-------------------------- + # Lowest Priority: Defaults + #-------------------------- + + # Quietly set all parameters to their defaults. + self.verbose = kwargs.pop("verbose",True) + self.set_parameter(verbose=False) + + # Set the location of the parameter input file + param_file = kwargs.pop("param_file",self.param_file) + read_param = kwargs.pop("read_param",True) + self.set_parameter(verbose=False,param_file=param_file) + + #----------------------------------------------------------------- + # Higher Priority: Values from a file (if requested and it exists) + #----------------------------------------------------------------- + + # If the user asks to read in an old parameter file, override any default parameters with values from the file + # If the file doesn't exist, flag it for now so we know to create it if read_param: if os.path.exists(self.param_file): - self.read_param(self.param_file, codename=codename.title(), verbose=self.verbose) + self.read_param(self.param_file, codename=self.codename, verbose=self.verbose) + param_file_found = True else: - print(f"{self.param_file} not found.") - - self.set_parameter(codename=codename, - integrator=integrator, - t0=t0, - tstart=tstart, - tstop=tstop, - dt=dt, - tstep_out=tstep_out, - istep_out=istep_out, - istep_dump=istep_dump, - rmin=rmin, rmax=rmax, - qmin_coord=qmin_coord, - gmtiny=gmtiny, - mtiny=mtiny, - MU=MU, DU=DU, TU=TU, - MU2KG=MU2KG, DU2M=DU2M, TU2S=TU2S, - MU_name=MU_name, DU_name=DU_name, TU_name=TU_name, - recompute_unit_values=False, - init_cond_file_type=init_cond_file_type, - init_cond_file_name=init_cond_file_name, - init_cond_format=init_cond_format, - output_file_type=output_file_type, - output_file_name=output_file_name, - output_format=output_format, - close_encounter_check=close_encounter_check, - general_relativity=general_relativity, - fragmentation=fragmentation, - minimum_fragment_gmass=minimum_fragment_gmass, - minimum_fragment_mass=minimum_fragment_mass, - rotation=rotation, - compute_conservation_values=compute_conservation_values, - extra_force=extra_force, - big_discard=big_discard, - rhill_present=rhill_present, - restart=restart, - interaction_loops=interaction_loops, - encounter_check_loops=encounter_check_loops, - ephemeris_date=ephemeris_date, - verbose=False) + param_file_found = False + # ----------------------------------------------------------------- + # Highest Priority: Values from arguments passed to Simulation() + # ----------------------------------------------------------------- + self.set_parameter(verbose=False, **kwargs) + + # Let the user know that there was a problem reading an old parameter file and we're going to create a new one + if read_param and not param_file_found: + print(f"{self.param_file} not found. Creating a new file using default values for parameters not passed to Simulation().") + self.write_param() + + # Read in an old simulation file if requested + read_old_output_file = kwargs.pop("read_old_output_file",False) if read_old_output_file: binpath = os.path.join(self.sim_dir, self.param['BIN_OUT']) if os.path.exists(binpath): @@ -321,6 +325,7 @@ def __init__(self, print(f"BIN_OUT file {binpath} not found.") return + def run(self,**kwargs): """ Runs a Swiftest integration. Uses the parameters set by the `param` dictionary unless overridden by keyword @@ -328,7 +333,7 @@ def run(self,**kwargs): Parameters ---------- - **kwargs : Any valid keyword arguments accepted by `set_parameter` or `save' + **kwargs : Any valid keyword arguments accepted by `set_parameter` Returns ------- @@ -336,8 +341,11 @@ def run(self,**kwargs): """ - self.set_parameter(**kwargs) - self.save(**kwargs) + if len(kwargs) > 0: + self.set_parameter(**kwargs) + + # Write out the current parameter set before executing run + self.write_param() if self.codename != "Swiftest": print(f"Running an integration is not yet supported for {self.codename}") @@ -345,6 +353,7 @@ def run(self,**kwargs): if self.driver_executable is None: print("Path to swiftest_driver has not been set!") + print(f"Make sure swiftest_driver is compiled and the executable is in {self.binary_path}") return print(f"Running a {self.codename} {self.integrator} run from tstart={self.param['TSTART']} {self.TU_name} to tstop={self.param['TSTOP']} {self.TU_name}") @@ -439,7 +448,7 @@ def set_simulation_time(self, A dictionary containing the requested parameters """ - if t0 is None and tstart is None and dt is None and istep_out is None and \ + if t0 is None and tstart is None and tstop is None and dt is None and istep_out is None and \ tstep_out is None and istep_dump is None: return {} @@ -582,12 +591,13 @@ def get_simulation_time(self, arg_list: str | List[str] | None = None, verbose: return time_dict - def set_parameter(self, **kwargs): + def set_parameter(self, verbose: bool = True, **kwargs): """ - Setter for all possible parameters. Calls each of the specialized setters using keyword arguments + Setter for all possible parameters. This will call each of the specialized setters using keyword arguments. + If no arguments are passed, then default values will be used. Parameters ---------- - **kwargs : [TODO: write this documentation] + **kwargs : Any argument listed listed in the Simulation class definition. Returns ------- @@ -595,15 +605,76 @@ def set_parameter(self, **kwargs): """ + default_arguments = { + "codename" : "Swiftest", + "integrator": "symba", + "param_file": "param.in", + "t0": 0.0, + "tstart": 0.0, + "tstop": None, + "dt": None, + "istep_out": None, + "tstep_out": None, + "istep_dump": None, + "init_cond_file_type": "NETCDF_DOUBLE", + "init_cond_file_name": None, + "init_cond_format": "EL", + "output_file_type": "NETCDF_DOUBLE", + "output_file_name": None, + "output_format": "XVEL", + "MU": "MSUN", + "DU": "AU", + "TU": "Y", + "MU2KG": None, + "DU2M": None, + "TU2S": None, + "MU_name": None, + "DU_name": None, + "TU_name": None, + "rmin": constants.RSun / constants.AU2M, + "rmax": 10000.0, + "qmin_coord": "HELIO", + "gmtiny": None, + "mtiny": None, + "close_encounter_check": True, + "general_relativity": True, + "fragmentation": False, + "minimum_fragment_mass": None, + "minimum_fragment_gmass": None, + "rotation": False, + "compute_conservation_values": False, + "extra_force": False, + "big_discard": False, + "rhill_present": False, + "interaction_loops": "TRIANGULAR", + "encounter_check_loops": "TRIANGULAR", + "ephemeris_date": "MBCL", + "restart": False, + } + + # If no arguments (other than, possibly, verbose) are requested, use defaults + if len(kwargs) == 0: + kwargs = default_arguments + + # Add the verbose flag to the kwargs for passing down to the individual setters + kwargs["verbose"] = verbose + + param_file = kwargs.pop("param_file",None) + + if param_file is not None: + self.param_file = os.path.realpath(param_file) + self.sim_dir = os.path.dirname(self.param_file) + + # Setters returning parameter dictionary values param_dict = {} param_dict.update(self.set_unit_system(**kwargs)) param_dict.update(self.set_integrator(**kwargs)) - param_dict.update(self.set_distance_range(**kwargs)) - param_dict.update(self.set_feature(**kwargs)) + param_dict.update(self.set_simulation_time(**kwargs)) param_dict.update(self.set_init_cond_files(**kwargs)) param_dict.update(self.set_output_files(**kwargs)) - param_dict.update(self.set_simulation_time(**kwargs)) + param_dict.update(self.set_distance_range(**kwargs)) + param_dict.update(self.set_feature(**kwargs)) # Non-returning setters self.set_ephemeris_date(**kwargs) @@ -615,7 +686,7 @@ def get_parameter(self, **kwargs): Setter for all possible parameters. Calls each of the specialized setters using keyword arguments Parameters ---------- - **kwargs : [TODO: write this documentation] + **kwargs : Any of the arguments defined in Simulation. If none provided, it returns all arguments. Returns ------- @@ -640,7 +711,6 @@ def get_parameter(self, **kwargs): def set_integrator(self, codename: Literal["swiftest", "swifter", "swift"] | None = None, integrator: Literal["symba","rmvs","whm","helio"] | None = None, - param_file: PathLike | str | None = None, mtiny: float | None = None, gmtiny: float | None = None, verbose: bool | None = None, @@ -653,8 +723,6 @@ def set_integrator(self, codename : {"swiftest", "swifter", "swift"}, optional integrator : {"symba","rmvs","whm","helio"}, optional Name of the n-body integrator that will be used when executing a run. - param_file: str or path-like, optional - Name of the input parameter file to use to read in parameter values. mtiny : float, optional The minimum mass of fully interacting bodies. Bodies below this mass interact with the larger bodies, but not each other (SyMBA only). *Note.* Only mtiny or gmtiny is accepted, not both. @@ -677,14 +745,6 @@ def set_integrator(self, update_list = [] - # Set defaults - if "codename" not in dir(self): - self.codename = "Swiftest" - if "integerator" not in dir(self): - self.integrator = "symba" - if "driver_executable" not in dir(self): - self.driver_executable = None - if codename is not None: valid_codename = ["Swiftest", "Swifter", "Swift"] if codename.title() not in valid_codename: @@ -696,7 +756,7 @@ def set_integrator(self, else: self.codename = codename.title() - self.param['! VERSION'] = f"{self.codename} parameter input" + self.param['! VERSION'] = f"{self.codename} input file" update_list.append("codename") if self.codename == "Swiftest": self.binary_path = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(_pyfile)),os.pardir,os.pardir,os.pardir,"bin")) @@ -705,7 +765,7 @@ def set_integrator(self, print(f"Cannot find the Swiftest driver in {self.binary_path}") self.driver_executable = None else: - self.binary_path = "NOT SET" + self.binary_path = "NOT IMPLEMENTED FOR THIS CODE" self.driver_executable = None update_list.append("driver_executable") @@ -721,10 +781,6 @@ def set_integrator(self, self.integrator = integrator.lower() update_list.append("integrator") - if param_file is not None: - self.param_file = os.path.realpath(param_file) - self.sim_dir = os.path.dirname(self.param_file) - if mtiny is not None or gmtiny is not None: if self.integrator != "symba": print("mtiny and gmtiny are only used by SyMBA.") @@ -764,8 +820,7 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | The subset of the dictionary containing the code name if codename is selected """ - valid_var = {"codename": "! VERSION", - "gmtiny" : "GMTINY"} + valid_var = {"gmtiny" : "GMTINY"} valid_instance_vars = {"codename": self.codename, "integrator": self.integrator, @@ -791,14 +846,15 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | arg_list = list(valid_instance_vars.keys()) arg_list.append(*[a for a in valid_var.keys() if a not in valid_instance_vars]) - inst_var = self._get_instance_var(arg_list, valid_instance_vars, verbose, **kwargs) - valid_arg, integrator_dict = self._get_valid_arg_list(arg_list, valid_var) if verbose: + for arg in arg_list: + if arg in valid_instance_vars: + print(f"{arg:<{self._getter_column_width}} {valid_instance_vars[arg]}") for arg in valid_arg: key = valid_var[arg] - if key in integrator_dict and key not in inst_var: + if key in integrator_dict: if arg == "gmtiny": if self.integrator == "symba": print(f"{arg:<{self._getter_column_width}} {integrator_dict[key]} {self.DU_name}^3 / {self.TU_name}^2 ") @@ -927,21 +983,27 @@ def set_feature(self, print("Minimum fragment mass is not set. Set it using minimum_fragment_gmass or minimum_fragment_mass") else: update_list.append("minimum_fragment_gmass") - update_list.append("minimum_fragment_mass") + if minimum_fragment_gmass is not None and minimum_fragment_mass is not None: print("Warning! Only set either minimum_fragment_mass or minimum_fragment_gmass, but not both!") if minimum_fragment_gmass is not None: self.param["MIN_GMFRAG"] = minimum_fragment_gmass - update_list.append("minimum_fragment_gmass") + if "minmum_fragment_gmass" not in update_list: + update_list.append("minimum_fragment_gmass") elif minimum_fragment_mass is not None: self.param["MIN_GMFRAG"] = minimum_fragment_mass * self.GU - update_list.append("minimum_fragment_gmass") + if "minimum_fragment_gmass" not in update_list: + update_list.append("minimum_fragment_gmass") if rotation is not None: self.param['ROTATION'] = rotation update_list.append("rotation") + if self.param['FRAGMENTATION'] and not self.param['ROTATION']: + self.param['ROTATION'] = True + update_list.append("rotation") + if compute_conservation_values is not None: self.param["ENERGY"] = compute_conservation_values update_list.append("compute_conservation_values") @@ -1265,6 +1327,7 @@ def set_output_files(self, "NETCDF_DOUBLE", "NETCDF_FLOAT", "REAL4", "REAL8", "XDR4", "XDR8"] | None = None, output_file_name: os.PathLike | str | None = None, output_format: Literal["XV", "XVEL"] | None = None, + restart: bool | None = None, verbose: bool | None = None, **kwargs: Any ): @@ -1285,6 +1348,8 @@ def set_output_files(self, output_format : {"XV","XVEL"}, optional Specifies the format for the data saved to the output file. If "XV" then cartesian position and velocity vectors for all bodies are stored. If "XVEL" then the orbital elements are also stored. + restart: bool, optional + Indicates whether this is a restart of an old run or a new run. verbose: bool, optional If passed, it will override the Simulation object's verbose flag **kwargs @@ -1304,6 +1369,9 @@ def set_output_files(self, update_list.append("output_file_name") if output_format is not None: update_list.append("output_format") + if restart is not None: + self.restart = restart + update_list.append("restart") if len(update_list) == 0: return {} @@ -1384,6 +1452,7 @@ def get_output_files(self, arg_list: str | List[str] | None = None, verbose: boo valid_var = {"output_file_type": "OUT_TYPE", "output_file_name": "BIN_OUT", "output_format": "OUT_FORM", + "restart": "OUT_STAT" } valid_arg, output_file_dict = self._get_valid_arg_list(arg_list, valid_var) @@ -1605,8 +1674,6 @@ def get_unit_system(self, arg_list: str | List[str] | None = None, verbose: bool """ - - valid_var = { "MU": "MU2KG", "DU": "DU2M", @@ -2298,6 +2365,7 @@ def write_param(self, if param is None: param = self.param print(f"Writing parameter inputs to file {param_file}") + param['! VERSION'] = f"{codename} input file" # Check to see if the parameter type matches the output type. If not, we need to convert if codename == "Swifter" or codename == "Swiftest": From dae7ed91572679a7d0ba0c6472877bdb2edb3764 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Tue, 15 Nov 2022 12:56:57 -0500 Subject: [PATCH 61/75] Added FRAGMENTATION to the param file writer --- python/swiftest/swiftest/io.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 64c4dc4a1..ead04cc95 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -428,6 +428,7 @@ def write_labeled_param(param, param_file_name): 'TU2S', 'DU2M', 'GMTINY', + 'FRAGMENTATION' 'MIN_GMFRAG', 'RESTART'] ptmp = param.copy() From 49fdf6b8197b85ae58d45e7511a15b315b85dfea Mon Sep 17 00:00:00 2001 From: David A Minton Date: Tue, 15 Nov 2022 14:01:55 -0500 Subject: [PATCH 62/75] Changed PathLike to os.PathLike --- python/swiftest/swiftest/simulation_class.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index c8f6ebf34..4c3bf6bca 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -361,6 +361,8 @@ def run(self,**kwargs): with subprocess.Popen([self.driver_executable, self.integrator, self.param_file], stdout=subprocess.PIPE, bufsize=1,universal_newlines=True) as p: for line in p.stdout: print(line, end='') + + return def _get_valid_arg_list(self, arg_list: str | List[str] | None = None, valid_var: Dict | None = None): @@ -2335,7 +2337,7 @@ def read_param(self, param_file, codename="Swiftest", verbose=True): def write_param(self, codename: Literal["Swiftest", "Swifter", "Swift"] | None = None, - param_file: str | PathLike | None = None, + param_file: str | os.PathLike | None = None, param: Dict | None = None, **kwargs: Any): """ @@ -2506,7 +2508,7 @@ def follow(self, codestyle="Swifter"): def save(self, codename: Literal["Swiftest", "Swifter", "Swift"] | None = None, - param_file: str | PathLike | None = None, + param_file: str | os.PathLike | None = None, param: Dict | None = None, framenum: int = -1, **kwargs: Any): From 24d6afe946070882d2e55d4b182ee95c16dc4d84 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Tue, 15 Nov 2022 14:52:32 -0500 Subject: [PATCH 63/75] Fixed issue where if Simulation() is called with no arguments, parameters from file could be overridden by defaults --- python/swiftest/swiftest/simulation_class.py | 37 +++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 4c3bf6bca..4dbc05610 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -22,6 +22,7 @@ import numpy.typing as npt import shutil import subprocess +import shlex from typing import ( Literal, Dict, @@ -302,6 +303,9 @@ def __init__(self,read_param: bool = True, **kwargs: Any): if os.path.exists(self.param_file): self.read_param(self.param_file, codename=self.codename, verbose=self.verbose) param_file_found = True + # We will add the parameter file to the kwarg list. This will keep the set_parameter method from + # overriding everything with defaults when there are no arguments passed to Simulation() + kwargs['param_file'] = self.param_file else: param_file_found = False @@ -358,10 +362,25 @@ def run(self,**kwargs): print(f"Running a {self.codename} {self.integrator} run from tstart={self.param['TSTART']} {self.TU_name} to tstop={self.param['TSTOP']} {self.TU_name}") - with subprocess.Popen([self.driver_executable, self.integrator, self.param_file], stdout=subprocess.PIPE, bufsize=1,universal_newlines=True) as p: + # Get current environment variables + env = os.environ.copy() + + try: + cmd = f"{self.driver_executable} {self.integrator} {self.param_file}" + p = subprocess.Popen(shlex.split(cmd), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, + universal_newlines=True) for line in p.stdout: print(line, end='') - + res = p.communicate() + if p.returncode != 0: + for line in res[1]: + print(line, end='') + raise Exception ("Failure in swiftest_driver") + except: + print(f"Error executing main swiftest_driver program") return @@ -2308,17 +2327,17 @@ def get_nvals(ds): def read_param(self, param_file, codename="Swiftest", verbose=True): """ - Reads in a param.in file and determines whether it is a Swift/Swifter/Swiftest parameter file. + Reads in an input parameter file and stores the values in the param dictionary. Parameters ---------- - param_file : string - File name of the input parameter file - codename : string - Type of parameter file, either "Swift", "Swifter", or "Swiftest" + param_file : string + File name of the input parameter file + codename : string + Type of parameter file, either "Swift", "Swifter", or "Swiftest" Returns ------- - self.ds : xarray dataset + """ if codename == "Swiftest": param_old = self.param.copy() @@ -2347,7 +2366,7 @@ def write_param(self, ---------- codename : {"Swiftest", "Swifter", "Swift"}, optional Alternative name of the n-body code that the parameter file will be formatted for. Defaults to current instance - variable self.codename + variable codename param_file : str or path-like, optional Alternative file name of the input parameter file. Defaults to current instance variable self.param_file param: Dict, optional From dad3e36be238b1903837b5b16da110e3d267bcf9 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Tue, 15 Nov 2022 17:54:24 -0500 Subject: [PATCH 64/75] Fixed some NetCDF file problems --- src/modules/swiftest_globals.f90 | 2 +- src/netcdf/netcdf.f90 | 34 ++++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/modules/swiftest_globals.f90 b/src/modules/swiftest_globals.f90 index 47b852f5a..f6644a302 100644 --- a/src/modules/swiftest_globals.f90 +++ b/src/modules/swiftest_globals.f90 @@ -149,7 +149,7 @@ module swiftest_globals character(*), parameter :: NETCDF_OUTFILE = 'bin.nc' !! Default output file name character(*), parameter :: TIME_DIMNAME = "time" !! NetCDF name of the time dimension character(*), parameter :: ID_DIMNAME = "id" !! NetCDF name of the particle id dimension - character(*), parameter :: STR_DIMNAME = "str" !! NetCDF name of the particle id dimension + character(*), parameter :: STR_DIMNAME = "string32" !! NetCDF name of the character string dimension character(*), parameter :: PTYPE_VARNAME = "particle_type" !! NetCDF name of the particle type variable character(*), parameter :: NAME_VARNAME = "name" !! NetCDF name of the particle name variable character(*), parameter :: NPL_VARNAME = "npl" !! NetCDF name of the number of active massive bodies variable diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index c4cd12042..7075d84bc 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -198,8 +198,8 @@ module subroutine netcdf_initialize_output(self, param) ! Define the NetCDF dimensions with particle name as the record dimension call check( nf90_def_dim(self%ncid, ID_DIMNAME, NF90_UNLIMITED, self%id_dimid), "netcdf_initialize_output nf90_def_dim id_dimid" ) ! 'x' dimension - call check( nf90_def_dim(self%ncid, TIME_DIMNAME, NF90_UNLIMITED, self%time_dimid), "netcdf_initialize_output nf90_def_dim time_dimid" ) ! 'y' dimension call check( nf90_def_dim(self%ncid, STR_DIMNAME, NAMELEN, self%str_dimid), "netcdf_initialize_output nf90_def_dim str_dimid" ) ! Dimension for string variables (aka character arrays) + call check( nf90_def_dim(self%ncid, TIME_DIMNAME, NF90_UNLIMITED, self%time_dimid), "netcdf_initialize_output nf90_def_dim time_dimid" ) ! 'y' dimension select case (param%out_type) case(NETCDF_FLOAT_TYPE) @@ -361,7 +361,14 @@ module subroutine netcdf_open(self, param, readonly) call check( nf90_inq_dimid(self%ncid, TIME_DIMNAME, self%time_dimid), "netcdf_open nf90_inq_dimid time_dimid" ) call check( nf90_inq_dimid(self%ncid, ID_DIMNAME, self%id_dimid), "netcdf_open nf90_inq_dimid id_dimid" ) - call check( nf90_inquire_dimension(self%ncid, max(self%time_dimid,self%id_dimid)+1, name=str_dim_name), "netcdf_open nf90_inquire_dimension str_dim_name" ) + if (max(self%time_dimid,self%id_dimid) == 2) then + self%str_dimid = 3 + else if (min(self%time_dimid,self%id_dimid) == 0) then + self%str_dimid = 1 + else + self%str_dimid = 2 + end if + call check( nf90_inquire_dimension(self%ncid, self%str_dimid, name=str_dim_name), "netcdf_open nf90_inquire_dimension str_dim_name" ) call check( nf90_inq_dimid(self%ncid, str_dim_name, self%str_dimid), "netcdf_open nf90_inq_dimid str_dimid" ) ! Required Variables @@ -370,7 +377,6 @@ module subroutine netcdf_open(self, param, readonly) call check( nf90_inq_varid(self%ncid, ID_DIMNAME, self%id_varid), "netcdf_open nf90_inq_varid id_varid" ) call check( nf90_inq_varid(self%ncid, NAME_VARNAME, self%name_varid), "netcdf_open nf90_inq_varid name_varid" ) call check( nf90_inq_varid(self%ncid, PTYPE_VARNAME, self%ptype_varid), "netcdf_open nf90_inq_varid ptype_varid" ) - call check( nf90_inq_varid(self%ncid, STATUS_VARNAME, self%status_varid), "netcdf_open nf90_inq_varid status_varid" ) call check( nf90_inq_varid(self%ncid, GMASS_VARNAME, self%Gmass_varid), "netcdf_open nf90_inq_varid Gmass_varid" ) if ((param%out_form == XV) .or. (param%out_form == XVEL)) then @@ -448,6 +454,7 @@ module subroutine netcdf_open(self, param, readonly) ! Variables The User Doesn't Need to Know About + status = nf90_inq_varid(self%ncid, STATUS_VARNAME, self%status_varid) status = nf90_inq_varid(self%ncid, J2RP2_VARNAME, self%j2rp2_varid) status = nf90_inq_varid(self%ncid, J4RP4_VARNAME, self%j4rp4_varid) @@ -562,7 +569,7 @@ module function netcdf_read_frame_system(self, iu, param) result(ierr) class is (symba_pl) select type (param) class is (symba_parameters) - nplm_check = count(rtemp(:) > param%GMTINY .and. plmask(:)) + nplm_check = count(pack(rtemp,plmask) > param%GMTINY ) if (nplm_check /= pl%nplm) then write(*,*) "Error reading in NetCDF file: The recorded value of nplm does not match the number of active fully interacting massive bodies" call util_exit(failure) @@ -774,17 +781,23 @@ module subroutine netcdf_read_hdr_system(self, iu, param) tslot = int(param%ioutput, kind=I4B) + 1 call check( nf90_inquire_dimension(iu%ncid, iu%id_dimid, len=idmax), "netcdf_read_frame_system nf90_inquire_dimension id_dimid" ) call check( nf90_get_var(iu%ncid, iu%time_varid, param%t, start=[tslot]), "netcdf_read_hdr_system nf90_getvar time_varid" ) - call check( nf90_get_var(iu%ncid, iu%Gmass_varid, gmtemp, start=[1,1]), "netcdf_read_frame_system nf90_getvar Gmass_varid" ) + allocate(gmtemp(idmax)) allocate(tpmask(idmax)) allocate(plmask(idmax)) allocate(plmmask(idmax)) + + call check( nf90_get_var(iu%ncid, iu%Gmass_varid, gmtemp, start=[1,1]), "netcdf_read_frame_system nf90_getvar Gmass_varid" ) + plmask(:) = gmtemp(:) == gmtemp(:) tpmask(:) = .not. plmask(:) plmask(1) = .false. ! This is the central body select type (param) class is (symba_parameters) - plmmask(:) = gmtemp(:) > param%GMTINY .and. plmask(:) + plmmask(:) = plmask(:) + where(plmask(:)) + plmmask(:) = gmtemp(:) > param%GMTINY + endwhere end select status = nf90_inq_varid(iu%ncid, NPL_VARNAME, iu%npl_varid) @@ -920,8 +933,13 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma call tp%info(i)%set_value(particle_type=ctemp(tpind(i))) end do - call check( nf90_get_var(iu%ncid, iu%status_varid, ctemp, count=[NAMELEN, idmax]), "netcdf_read_particle_info_system nf90_getvar status_varid" ) - call cb%info%set_value(status=ctemp(1)) + status = nf90_inq_varid(iu%ncid, STATUS_VARNAME, iu%status_varid) + if (status == nf90_noerr) then + call check( nf90_get_var(iu%ncid, iu%status_varid, ctemp, count=[NAMELEN, idmax]), "netcdf_read_particle_info_system nf90_getvar status_varid") + call cb%info%set_value(status=ctemp(1)) + else + call cb%info%set_value(status="ACTIVE") + end if do i = 1, npl call pl%info(i)%set_value(status=ctemp(plind(i))) end do From fa59dad8b886c2f15f83a8279a961a491517e60c Mon Sep 17 00:00:00 2001 From: David A Minton Date: Tue, 15 Nov 2022 18:11:40 -0500 Subject: [PATCH 65/75] Fixed problems when using EL input --- src/netcdf/netcdf.f90 | 35 ++++++++++++++++++++++++++++------- src/orbel/orbel.f90 | 1 - 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 7075d84bc..2cca37386 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -619,28 +619,39 @@ module function netcdf_read_frame_system(self, iu, param) result(ierr) end if if ((param%in_form == EL) .or. (param%in_form == XVEL)) then - call check( nf90_get_var(iu%ncid, iu%a_varid, rtemp, start=[1, tslot]), "netcdf_read_frame_system nf90_getvar a_varid" ) + if (.not.allocated(pl%a)) allocate(pl%a(npl)) + if (.not.allocated(tp%a)) allocate(tp%a(ntp)) if (npl > 0) pl%a(:) = pack(rtemp, plmask) if (ntp > 0) tp%a(:) = pack(rtemp, tpmask) call check( nf90_get_var(iu%ncid, iu%e_varid, rtemp, start=[1, tslot]), "netcdf_read_frame_system nf90_getvar e_varid" ) + if (.not.allocated(pl%e)) allocate(pl%e(npl)) + if (.not.allocated(tp%e)) allocate(tp%e(ntp)) if (npl > 0) pl%e(:) = pack(rtemp, plmask) if (ntp > 0) tp%e(:) = pack(rtemp, tpmask) call check( nf90_get_var(iu%ncid, iu%inc_varid, rtemp, start=[1, tslot]), "netcdf_read_frame_system nf90_getvar inc_varid" ) + if (.not.allocated(pl%inc)) allocate(pl%inc(npl)) + if (.not.allocated(tp%inc)) allocate(tp%inc(ntp)) if (npl > 0) pl%inc(:) = pack(rtemp, plmask) if (ntp > 0) tp%inc(:) = pack(rtemp, tpmask) call check( nf90_get_var(iu%ncid, iu%capom_varid, rtemp, start=[1, tslot]), "netcdf_read_frame_system nf90_getvar capom_varid" ) + if (.not.allocated(pl%capom)) allocate(pl%capom(npl)) + if (.not.allocated(tp%capom)) allocate(tp%capom(ntp)) if (npl > 0) pl%capom(:) = pack(rtemp, plmask) if (ntp > 0) tp%capom(:) = pack(rtemp, tpmask) call check( nf90_get_var(iu%ncid, iu%omega_varid, rtemp, start=[1, tslot]), "netcdf_read_frame_system nf90_getvar omega_varid" ) + if (.not.allocated(pl%omega)) allocate(pl%omega(npl)) + if (.not.allocated(tp%omega)) allocate(tp%omega(ntp)) if (npl > 0) pl%omega(:) = pack(rtemp, plmask) if (ntp > 0) tp%omega(:) = pack(rtemp, tpmask) call check( nf90_get_var(iu%ncid, iu%capm_varid, rtemp, start=[1, tslot]), "netcdf_read_frame_system nf90_getvar capm_varid" ) + if (.not.allocated(pl%capm)) allocate(pl%capm(npl)) + if (.not.allocated(tp%capm)) allocate(tp%capm(ntp)) if (npl > 0) pl%capm(:) = pack(rtemp, plmask) if (ntp > 0) tp%capm(:) = pack(rtemp, tpmask) @@ -740,6 +751,10 @@ module function netcdf_read_frame_system(self, iu, param) result(ierr) call self%read_particle_info(iu, param, plmask, tpmask) + if (param%in_form == "EL") then + call pl%el2xv(cb) + call tp%el2xv(cb) + end if ! if this is a GR-enabled run, check to see if we got the pseudovelocities in. Otherwise, we'll need to generate them. if (param%lgr .and. .not.(iu%lpseudo_vel_exists)) then call pl%set_mu(cb) @@ -984,21 +999,24 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma if (status == nf90_noerr) then call check( nf90_get_var(iu%ncid, iu%origin_xhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar origin_xhx_varid" ) else - call check( nf90_get_var(iu%ncid, iu%xhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar xhx_varid" ) + ! [TODO]: This doesn't work when the input mode is EL. This needs to get filled in later after xv2el has been called + ! call check( nf90_get_var(iu%ncid, iu%xhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar xhx_varid" ) end if status = nf90_inq_varid(iu%ncid, ORIGIN_XHY_VARNAME, iu%origin_xhy_varid) if (status == nf90_noerr) then call check( nf90_get_var(iu%ncid, iu%origin_xhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar origin_xhy_varid" ) else - call check( nf90_get_var(iu%ncid, iu%xhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar xhy_varid" ) + ! [TODO]: This doesn't work when the input mode is EL. This needs to get filled in later after xv2el has been called + ! call check( nf90_get_var(iu%ncid, iu%xhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar xhy_varid" ) end if status = nf90_inq_varid(iu%ncid, ORIGIN_XHZ_VARNAME, iu%origin_xhz_varid) if (status == nf90_noerr) then call check( nf90_get_var(iu%ncid, iu%origin_xhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar origin_xhz_varid" ) else - call check( nf90_get_var(iu%ncid, iu%xhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar xhz_varid" ) + ! [TODO]: This doesn't work when the input mode is EL. This needs to get filled in later after xv2el has been called + ! call check( nf90_get_var(iu%ncid, iu%xhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar xhz_varid" ) end if do i = 1, npl @@ -1012,21 +1030,24 @@ module subroutine netcdf_read_particle_info_system(self, iu, param, plmask, tpma if (status == nf90_noerr) then call check( nf90_get_var(iu%ncid, iu%origin_vhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar origin_vhx_varid" ) else - call check( nf90_get_var(iu%ncid, iu%vhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar vhx_varid" ) + ! [TODO]: This doesn't work when the input mode is EL. This needs to get filled in later after xv2el has been called + ! call check( nf90_get_var(iu%ncid, iu%vhx_varid, rtemp_arr(1,:)), "netcdf_read_particle_info_system nf90_getvar vhx_varid" ) end if status = nf90_inq_varid(iu%ncid, ORIGIN_VHY_VARNAME, iu%origin_vhy_varid) if (status == nf90_noerr) then call check( nf90_get_var(iu%ncid, iu%origin_vhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar origin_vhy_varid" ) else - call check( nf90_get_var(iu%ncid, iu%vhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar vhy_varid" ) + ! [TODO]: This doesn't work when the input mode is EL. This needs to get filled in later after xv2el has been called + ! call check( nf90_get_var(iu%ncid, iu%vhy_varid, rtemp_arr(2,:)), "netcdf_read_particle_info_system nf90_getvar vhy_varid" ) end if status = nf90_inq_varid(iu%ncid, ORIGIN_VHZ_VARNAME, iu%origin_vhz_varid) if (status == nf90_noerr) then call check( nf90_get_var(iu%ncid, iu%origin_vhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar origin_vhz_varid" ) else - call check( nf90_get_var(iu%ncid, iu%vhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar vhz_varid" ) + ! [TODO]: This doesn't work when the input mode is EL. This needs to get filled in later after xv2el has been called + ! call check( nf90_get_var(iu%ncid, iu%vhz_varid, rtemp_arr(3,:)), "netcdf_read_particle_info_system nf90_getvar vhz_varid" ) end if do i = 1, npl diff --git a/src/orbel/orbel.f90 b/src/orbel/orbel.f90 index 9dc68c397..5e7c4a989 100644 --- a/src/orbel/orbel.f90 +++ b/src/orbel/orbel.f90 @@ -30,7 +30,6 @@ module subroutine orbel_el2xv_vec(self, cb) call orbel_el2xv(self%mu(i), self%a(i), self%e(i), self%inc(i), self%capom(i), & self%omega(i), self%capm(i), self%xh(:, i), self%vh(:, i)) end do - deallocate(self%a, self%e, self%inc, self%capom, self%omega, self%capm) return end subroutine orbel_el2xv_vec From 928f3f1a37d3e03ca9d3c046b7b73dae23871e65 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Tue, 15 Nov 2022 18:15:08 -0500 Subject: [PATCH 66/75] Updated initial conditions script and a script that runs from input file --- examples/Basic_Simulation/initial_conditions.py | 2 -- examples/Basic_Simulation/run_from_file.py | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 examples/Basic_Simulation/run_from_file.py diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index de5a8fcad..95fdb608e 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -74,5 +74,3 @@ # Save everything to a set of initial conditions files sim.run() - -print("All done!") diff --git a/examples/Basic_Simulation/run_from_file.py b/examples/Basic_Simulation/run_from_file.py new file mode 100644 index 000000000..cede6e2ea --- /dev/null +++ b/examples/Basic_Simulation/run_from_file.py @@ -0,0 +1,3 @@ +import swiftest +sim = swiftest.Simulation() +sim.run() \ No newline at end of file From fdfd6d64060bb5dc4e3bac58d7fffc20487eb200 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 16 Nov 2022 15:04:32 -0500 Subject: [PATCH 67/75] Wrapped the call to swiftest_driver in a shell script that is generated at runtime. Not elegant, but works (so far) --- python/swiftest/swiftest/simulation_class.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 4dbc05610..f067f842c 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -364,9 +364,17 @@ def run(self,**kwargs): # Get current environment variables env = os.environ.copy() + driver_script = os.path.join(self.binary_path,"swiftest_driver.sh") + shell = os.path.basename(env['SHELL']) + with open(driver_script,'w') as f: + f.write(f"#{env['SHELL']} -l {os.linesep}") + f.write(f"source ~/.{shell}rc {os.linesep}") + f.write(f"cd {self.sim_dir} {os.linesep}") + f.write(f"pwd {os.linesep}") + f.write(f"{self.driver_executable} {self.integrator} {self.param_file}") try: - cmd = f"{self.driver_executable} {self.integrator} {self.param_file}" + cmd = f"{env['SHELL']} -l {driver_script}" p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -374,6 +382,8 @@ def run(self,**kwargs): universal_newlines=True) for line in p.stdout: print(line, end='') + for line in p.stderr: + print(line, end='') res = p.communicate() if p.returncode != 0: for line in res[1]: @@ -2032,6 +2042,7 @@ def add_solar_system_body(self, J2=J2, J4=J4, t=t) dsnew = self._combine_and_fix_dsnew(dsnew) + self.save() return dsnew @@ -2284,6 +2295,7 @@ def input_to_array(val,t,n=None): J2=J2, J4=J4,t=t) dsnew = self._combine_and_fix_dsnew(dsnew) + self.save() return dsnew From 0b3db9f76d47886dccff3e6080af3c5b537e3414 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 16 Nov 2022 15:06:04 -0500 Subject: [PATCH 68/75] Streamlined output by not reporting the contents of param.in on startup, as we can do that on the python side now. Also added a better error message for when a NetCDF file can't be found --- src/io/io.f90 | 2 +- src/netcdf/netcdf.f90 | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/io/io.f90 b/src/io/io.f90 index aa797ddbe..6dfa6499e 100644 --- a/src/io/io.f90 +++ b/src/io/io.f90 @@ -929,7 +929,7 @@ module subroutine io_param_reader(self, unit, iotype, v_list, iostat, iomsg) iostat = 0 ! Print the contents of the parameter file to standard output - call param%writer(unit = OUTPUT_UNIT, iotype = "none", v_list = [0], iostat = iostat, iomsg = iomsg) + ! call param%writer(unit = OUTPUT_UNIT, iotype = "none", v_list = [0], iostat = iostat, iomsg = iomsg) end associate diff --git a/src/netcdf/netcdf.f90 b/src/netcdf/netcdf.f90 index 2cca37386..4c037a291 100644 --- a/src/netcdf/netcdf.f90 +++ b/src/netcdf/netcdf.f90 @@ -351,13 +351,15 @@ module subroutine netcdf_open(self, param, readonly) ! Internals integer(I4B) :: mode, status character(len=NF90_MAX_NAME) :: str_dim_name + character(len=STRMAX) :: errmsg mode = NF90_WRITE if (present(readonly)) then if (readonly) mode = NF90_NOWRITE end if - call check( nf90_open(param%outfile, mode, self%ncid), "netcdf_open nf90_open" ) + write(errmsg,*) "netcdf_open nf90_open ",trim(adjustl(param%outfile)) + call check( nf90_open(param%outfile, mode, self%ncid), errmsg) call check( nf90_inq_dimid(self%ncid, TIME_DIMNAME, self%time_dimid), "netcdf_open nf90_inq_dimid time_dimid" ) call check( nf90_inq_dimid(self%ncid, ID_DIMNAME, self%id_dimid), "netcdf_open nf90_inq_dimid id_dimid" ) From ec048b682f1f7497bc90d2abd2ef9206de057ccc Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 16 Nov 2022 15:28:13 -0500 Subject: [PATCH 69/75] Added TSTART and RESTART as valid input variables (they don't do anything yet) --- src/io/io.f90 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/io/io.f90 b/src/io/io.f90 index 6dfa6499e..86cf55728 100644 --- a/src/io/io.f90 +++ b/src/io/io.f90 @@ -566,6 +566,7 @@ module subroutine io_param_reader(self, unit, iotype, v_list, iostat, iomsg) character(len=*), intent(inout) :: iomsg !! Message to pass if iostat /= 0 ! Internals logical :: t0_set = .false. !! Is the initial time set in the input file? + logical :: tstart_set = .false. !! Is the final time set in the input file? logical :: tstop_set = .false. !! Is the final time set in the input file? logical :: dt_set = .false. !! Is the step size set in the input file? integer(I4B) :: ilength, ifirst, ilast, i !! Variables used to parse input file @@ -593,6 +594,9 @@ module subroutine io_param_reader(self, unit, iotype, v_list, iostat, iomsg) case ("T0") read(param_value, *, err = 667, iomsg = iomsg) param%t0 t0_set = .true. + case ("TSTART") + read(param_value, *, err = 667, iomsg = iomsg) param%t0 + tstart_set = .true. case ("TSTOP") read(param_value, *, err = 667, iomsg = iomsg) param%tstop tstop_set = .true. @@ -743,6 +747,12 @@ module subroutine io_param_reader(self, unit, iotype, v_list, iostat, iomsg) read(param_value, *, err = 667, iomsg = iomsg) param%maxid_collision case ("PARTICLE_OUT") param%particle_out = param_value + case ("RESTART") + if (param_value == "NO" .or. param_value == 'F') then + param%lrestart = .false. + else if (param_value == "YES" .or. param_value == 'T') then + param%lrestart = .true. + end if case ("NPLMAX", "NTPMAX", "GMTINY", "MIN_GMFRAG", "FRAGMENTATION", "SEED", "YARKOVSKY", "YORP") ! Ignore SyMBA-specific, not-yet-implemented, or obsolete input parameters case default write(*,*) "Ignoring unknown parameter -> ",param_name From 87f4ca7a1964ac54db23581f9f0d07a96f57178f Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 16 Nov 2022 16:39:29 -0500 Subject: [PATCH 70/75] Checks if 'name' variable is unique, and if so, uses that as the dimension coordinate instead of id --- python/swiftest/swiftest/io.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 2ec86f108..64d3f0c6e 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -852,6 +852,10 @@ def swiftest2xr(param, verbose=True): ds = fix_types(ds,ftype=np.float64) elif param['OUT_TYPE'] == "NETCDF_FLOAT": ds = fix_types(ds,ftype=np.float32) + # Check if the name variable contains unique values. If so, make name the dimension instead of id + if len(np.unique(ds['name'])) == len(ds['name']): + ds = ds.swap_dims({"id" : "name"}) + sim.ds.reset_coords("id") else: print(f"Error encountered. OUT_TYPE {param['OUT_TYPE']} not recognized.") return None From 5fa3a4c6c6373f35287cce8136a2c0412a5e1669 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 16 Nov 2022 16:40:06 -0500 Subject: [PATCH 71/75] Fixed issue where too many of the time variables were being reported out when one changed. --- python/swiftest/swiftest/simulation_class.py | 159 ++++++++++--------- 1 file changed, 81 insertions(+), 78 deletions(-) diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index f067f842c..5df0c3edb 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -23,6 +23,7 @@ import shutil import subprocess import shlex +import warnings from typing import ( Literal, Dict, @@ -316,7 +317,7 @@ def __init__(self,read_param: bool = True, **kwargs: Any): # Let the user know that there was a problem reading an old parameter file and we're going to create a new one if read_param and not param_file_found: - print(f"{self.param_file} not found. Creating a new file using default values for parameters not passed to Simulation().") + warnings.warn(f"{self.param_file} not found. Creating a new file using default values for parameters not passed to Simulation().") self.write_param() # Read in an old simulation file if requested @@ -326,7 +327,7 @@ def __init__(self,read_param: bool = True, **kwargs: Any): if os.path.exists(binpath): self.bin2xr() else: - print(f"BIN_OUT file {binpath} not found.") + warnings.warn(f"BIN_OUT file {binpath} not found.") return @@ -352,12 +353,12 @@ def run(self,**kwargs): self.write_param() if self.codename != "Swiftest": - print(f"Running an integration is not yet supported for {self.codename}") + warnings.warn(f"Running an integration is not yet supported for {self.codename}") return if self.driver_executable is None: - print("Path to swiftest_driver has not been set!") - print(f"Make sure swiftest_driver is compiled and the executable is in {self.binary_path}") + warnings.warn("Path to swiftest_driver has not been set!") + warnings.warn(f"Make sure swiftest_driver is compiled and the executable is in {self.binary_path}") return print(f"Running a {self.codename} {self.integrator} run from tstart={self.param['TSTART']} {self.TU_name} to tstop={self.param['TSTOP']} {self.TU_name}") @@ -370,27 +371,28 @@ def run(self,**kwargs): f.write(f"#{env['SHELL']} -l {os.linesep}") f.write(f"source ~/.{shell}rc {os.linesep}") f.write(f"cd {self.sim_dir} {os.linesep}") - f.write(f"pwd {os.linesep}") f.write(f"{self.driver_executable} {self.integrator} {self.param_file}") try: cmd = f"{env['SHELL']} -l {driver_script}" - p = subprocess.Popen(shlex.split(cmd), + with subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, - universal_newlines=True) - for line in p.stdout: - print(line, end='') - for line in p.stderr: - print(line, end='') - res = p.communicate() - if p.returncode != 0: - for line in res[1]: - print(line, end='') - raise Exception ("Failure in swiftest_driver") + universal_newlines=True) as p: + for line in p.stdout: + print(line, end='') + res = p.communicate() + if p.returncode != 0: + for line in res[1]: + print(line, end='') + raise Exception ("Failure in swiftest_driver") except: - print(f"Error executing main swiftest_driver program") + warnings.warn(f"Error executing main swiftest_driver program") + return + + # Read in new data + self.bin2xr() return @@ -509,7 +511,7 @@ def set_simulation_time(self, if tstop is not None: if tstop <= tstart: - print("Error! tstop must be greater than tstart.") + warnings.warn("tstop must be greater than tstart.") return {} if tstop is not None: @@ -522,8 +524,8 @@ def set_simulation_time(self, if dt is not None and tstop is not None: if dt > (tstop - tstart): - print("Error! dt must be smaller than tstop-tstart") - print(f"Setting dt = {tstop - tstart} instead of {dt}") + warnings.warn("dt must be smaller than tstop-tstart") + warnings.warn(f"Setting dt = {tstop - tstart} instead of {dt}") dt = tstop - tstart if dt is not None: @@ -531,26 +533,27 @@ def set_simulation_time(self, if istep_out is None and tstep_out is None: istep_out = self.param.pop("ISTEP_OUT", None) - - if istep_out is not None and tstep_out is not None: - print("Error! istep_out and tstep_out cannot both be set") + elif istep_out is not None and tstep_out is not None: + warnings.warn("istep_out and tstep_out cannot both be set") return {} + else: + update_list.append("istep_out") if tstep_out is not None and dt is not None: istep_out = int(np.floor(tstep_out / dt)) if istep_out is not None: self.param['ISTEP_OUT'] = istep_out - update_list.append("istep_out") if istep_dump is None: istep_dump = self.param.pop("ISTEP_DUMP", None) if istep_dump is None: istep_dump = istep_out + else: + update_list.append("istep_dump") if istep_dump is not None: self.param['ISTEP_DUMP'] = istep_dump - update_list.append("istep_dump") time_dict = self.get_simulation_time(update_list, verbose=verbose) @@ -779,7 +782,7 @@ def set_integrator(self, if codename is not None: valid_codename = ["Swiftest", "Swifter", "Swift"] if codename.title() not in valid_codename: - print(f"{codename} is not a valid codename. Valid options are ",",".join(valid_codename)) + warnings.warn(f"{codename} is not a valid codename. Valid options are ",",".join(valid_codename)) try: self.codename except: @@ -793,7 +796,7 @@ def set_integrator(self, self.binary_path = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(_pyfile)),os.pardir,os.pardir,os.pardir,"bin")) self.driver_executable = os.path.join(self.binary_path,"swiftest_driver") if not os.path.exists(self.driver_executable): - print(f"Cannot find the Swiftest driver in {self.binary_path}") + warnings.warn(f"Cannot find the Swiftest driver in {self.binary_path}") self.driver_executable = None else: self.binary_path = "NOT IMPLEMENTED FOR THIS CODE" @@ -803,7 +806,7 @@ def set_integrator(self, if integrator is not None: valid_integrator = ["symba","rmvs","whm","helio"] if integrator.lower() not in valid_integrator: - print(f"{integrator} is not a valid integrator. Valid options are ",",".join(valid_integrator)) + warnings.warn(f"{integrator} is not a valid integrator. Valid options are ",",".join(valid_integrator)) try: self.integrator except: @@ -814,9 +817,9 @@ def set_integrator(self, if mtiny is not None or gmtiny is not None: if self.integrator != "symba": - print("mtiny and gmtiny are only used by SyMBA.") + warnings.warn("mtiny and gmtiny are only used by SyMBA.") if mtiny is not None and gmtiny is not None: - print("Only set mtiny or gmtiny, not both!") + warnings.warn("Only set mtiny or gmtiny, not both!") elif gmtiny is not None: self.param['GMTINY'] = gmtiny update_list.append("gmtiny") @@ -861,13 +864,13 @@ def get_integrator(self,arg_list: str | List[str] | None = None, verbose: bool | try: self.integrator except: - print(f"integrator is not set") + warnings.warn(f"integrator is not set") return {} try: self.codename except: - print(f"codename is not set") + warnings.warn(f"codename is not set") return {} if verbose is None: @@ -1004,19 +1007,19 @@ def set_feature(self, if fragmentation is not None: if self.codename != "Swiftest" and self.integrator != "symba" and fragmentation: - print("Fragmentation is only available on Swiftest SyMBA.") + warnings.warn("Fragmentation is only available on Swiftest SyMBA.") self.param['FRAGMENTATION'] = False else: self.param['FRAGMENTATION'] = fragmentation update_list.append("fragmentation") if fragmentation: if "MIN_GMFRAG" not in self.param and minimum_fragment_mass is None and minimum_fragment_gmass is None: - print("Minimum fragment mass is not set. Set it using minimum_fragment_gmass or minimum_fragment_mass") + warnings.warn("Minimum fragment mass is not set. Set it using minimum_fragment_gmass or minimum_fragment_mass") else: update_list.append("minimum_fragment_gmass") if minimum_fragment_gmass is not None and minimum_fragment_mass is not None: - print("Warning! Only set either minimum_fragment_mass or minimum_fragment_gmass, but not both!") + warnings.warn("Only set either minimum_fragment_mass or minimum_fragment_gmass, but not both!") if minimum_fragment_gmass is not None: self.param["MIN_GMFRAG"] = minimum_fragment_gmass @@ -1061,8 +1064,8 @@ def set_feature(self, if interaction_loops is not None: valid_vals = ["TRIANGULAR", "FLAT", "ADAPTIVE"] if interaction_loops not in valid_vals: - print(f"{interaction_loops} is not a valid option for interaction loops.") - print(f"Must be one of {valid_vals}") + warnings.warn(f"{interaction_loops} is not a valid option for interaction loops.") + warnings.warn(f"Must be one of {valid_vals}") if "INTERACTION_LOOPS" not in self.param: self.param["INTERACTION_LOOPS"] = valid_vals[0] else: @@ -1072,8 +1075,8 @@ def set_feature(self, if encounter_check_loops is not None: valid_vals = ["TRIANGULAR", "SORTSWEEP", "ADAPTIVE"] if encounter_check_loops not in valid_vals: - print(f"{encounter_check_loops} is not a valid option for interaction loops.") - print(f"Must be one of {valid_vals}") + warnings.warn(f"{encounter_check_loops} is not a valid option for interaction loops.") + warnings.warn(f"Must be one of {valid_vals}") if "ENCOUNTER_CHECK" not in self.param: self.param["ENCOUNTER_CHECK"] = valid_vals[0] else: @@ -1204,13 +1207,13 @@ def set_init_cond_files(self, return {} def ascii_file_input_error_msg(codename): - print(f"Error in set_init_cond_files: init_cond_file_name must be a dictionary of the form: ") - print('{') + warnings.warn(f"in set_init_cond_files: init_cond_file_name must be a dictionary of the form: ") + warnings.warn('{') if codename == "Swiftest": - print('"CB" : *path to central body initial conditions file*,') - print('"PL" : *path to massive body initial conditions file*,') - print('"TP" : *path to test particle initial conditions file*') - print('}') + warnings.warn('"CB" : *path to central body initial conditions file*,') + warnings.warn('"PL" : *path to massive body initial conditions file*,') + warnings.warn('"TP" : *path to test particle initial conditions file*') + warnings.warn('}') return {} if init_cond_format is None: @@ -1230,21 +1233,21 @@ def ascii_file_input_error_msg(codename): else: init_cond_keys = ["PL", "TP"] if init_cond_file_type != "ASCII": - print(f"{init_cond_file_type} is not supported by {self.codename}. Using ASCII instead") + warnings.warn(f"{init_cond_file_type} is not supported by {self.codename}. Using ASCII instead") init_cond_file_type = "ASCII" if init_cond_format != "XV": - print(f"{init_cond_format} is not supported by {self.codename}. Using XV instead") + warnings.warn(f"{init_cond_format} is not supported by {self.codename}. Using XV instead") init_cond_format = "XV" valid_formats = {"EL", "XV"} if init_cond_format not in valid_formats: - print(f"{init_cond_format} is not a valid input format") + warnings.warn(f"{init_cond_format} is not a valid input format") else: self.param['IN_FORM'] = init_cond_format valid_types = {"NETCDF_DOUBLE", "NETCDF_FLOAT", "ASCII"} if init_cond_file_type not in valid_types: - print(f"{init_cond_file_type} is not a valid input type") + warnings.warn(f"{init_cond_file_type} is not a valid input type") else: self.param['IN_TYPE'] = init_cond_file_type @@ -1271,7 +1274,7 @@ def ascii_file_input_error_msg(codename): elif type(init_cond_file_name) is dict: # Oops, accidentally passed a dictionary instead of the expected single string or path-like for NetCDF # input type. - print(f"Only a single input file is used for NetCDF files") + warnings.warn(f"Only a single input file is used for NetCDF files") else: self.param["NC_IN"] = init_cond_file_name @@ -1412,7 +1415,7 @@ def set_output_files(self, if output_file_type is None: output_file_type = "NETCDF_DOUBLE" elif output_file_type not in ["NETCDF_DOUBLE", "NETCDF_FLOAT"]: - print(f"{output_file_type} is not compatible with Swiftest. Setting to NETCDF_DOUBLE") + warnings.warn(f"{output_file_type} is not compatible with Swiftest. Setting to NETCDF_DOUBLE") output_file_type = "NETCDF_DOUBLE" elif self.codename == "Swifter": if output_file_type is None: @@ -1420,7 +1423,7 @@ def set_output_files(self, if output_file_type is None: output_file_type = "REAL8" elif output_file_type not in ["REAL4", "REAL8", "XDR4", "XDR8"]: - print(f"{output_file_type} is not compatible with Swifter. Setting to REAL8") + warnings.warn(f"{output_file_type} is not compatible with Swifter. Setting to REAL8") output_file_type = "REAL8" elif self.codename == "Swift": if output_file_type is None: @@ -1428,7 +1431,7 @@ def set_output_files(self, if output_file_type is None: output_file_type = "REAL4" if output_file_type not in ["REAL4"]: - print(f"{output_file_type} is not compatible with Swift. Setting to REAL4") + warnings.warn(f"{output_file_type} is not compatible with Swift. Setting to REAL4") output_file_type = "REAL4" self.param['OUT_TYPE'] = output_file_type @@ -1441,7 +1444,7 @@ def set_output_files(self, self.param['BIN_OUT'] = output_file_name if output_format != "XV" and self.codename != "Swiftest": - print(f"{output_format} is not compatible with {self.codename}. Setting to XV") + warnings.warn(f"{output_format} is not compatible with {self.codename}. Setting to XV") output_format = "XV" self.param["OUT_FORM"] = output_format @@ -1616,7 +1619,7 @@ def set_unit_system(self, self.param['MU2KG'] = 1000.0 self.MU_name = "g" else: - print(f"{MU} not a recognized unit system. Using MSun as a default.") + warnings.warn(f"{MU} not a recognized unit system. Using MSun as a default.") self.param['MU2KG'] = constants.MSun self.MU_name = "MSun" @@ -1639,7 +1642,7 @@ def set_unit_system(self, self.param['DU2M'] = 100.0 self.DU_name = "cm" else: - print(f"{DU} not a recognized unit system. Using AU as a default.") + warnings.warn(f"{DU} not a recognized unit system. Using AU as a default.") self.param['DU2M'] = constants.AU2M self.DU_name = "AU" @@ -1659,7 +1662,7 @@ def set_unit_system(self, self.param['TU2S'] = 1.0 self.TU_name = "s" else: - print(f"{TU} not a recognized unit system. Using YR as a default.") + warnings.warn(f"{TU} not a recognized unit system. Using YR as a default.") self.param['TU2S'] = constants.YR2S self.TU_name = "y" @@ -1842,7 +1845,7 @@ def set_distance_range(self, if qmin_coord is not None: valid_qmin_coord = ["HELIO","BARY"] if qmin_coord.upper() not in valid_qmin_coord: - print(f"qmin_coord = {qmin_coord} is not a valid option. Must be one of",','.join(valid_qmin_coord)) + warnings.warn(f"qmin_coord = {qmin_coord} is not a valid option. Must be one of",','.join(valid_qmin_coord)) self.param['CHK_QMIN_COORD'] = valid_qmin_coord[0] else: self.param['CHK_QMIN_COORD'] = qmin_coord.upper() @@ -1965,7 +1968,7 @@ def add_solar_system_body(self, if type(ephemeris_id) is int: ephemeris_id = [ephemeris_id] if len(ephemeris_id) != len(name): - print(f"Error! The length of ephemeris_id ({len(ephemeris_id)}) does not match the length of name ({len(name)})") + warnings.warn(f"The length of ephemeris_id ({len(ephemeris_id)}) does not match the length of name ({len(name)})") return None else: ephemeris_id = [None] * len(name) @@ -1978,11 +1981,11 @@ def add_solar_system_body(self, try: datetime.datetime.fromisoformat(date) except: - print(f"{date} is not a valid date format. Must be 'YYYY-MM-DD'. Setting to {self.ephemeris_date}") + warnings.warn(f"{date} is not a valid date format. Must be 'YYYY-MM-DD'. Setting to {self.ephemeris_date}") date = self.ephemeris_date if source.upper() != "HORIZONS": - print("Currently only the JPL Horizons ephemeris service is supported") + warnings.warn("Currently only the JPL Horizons ephemeris service is supported") body_list = [] for i,n in enumerate(name): @@ -2087,8 +2090,8 @@ def set_ephemeris_date(self, datetime.datetime.fromisoformat(ephemeris_date) except: valid_date_args = ['"MBCL"', '"TODAY"', '"YYYY-MM-DD"'] - print(f"{ephemeris_date} is not a valid format. Valid options include:", ', '.join(valid_date_args)) - print("Using MBCL for date.") + warnings.warn(f"{ephemeris_date} is not a valid format. Valid options include:", ', '.join(valid_date_args)) + warnings.warn("Using MBCL for date.") ephemeris_date = minton_bcl self.ephemeris_date = ephemeris_date @@ -2123,7 +2126,7 @@ def get_ephemeris_date(self, arg_list: str | List[str] | None = None, verbose: b try: self.ephemeris_date except: - print(f"ephemeris_date is not set") + warnings.warn(f"ephemeris_date is not set") return valid_arg = {"ephemeris_date": self.ephemeris_date} @@ -2362,7 +2365,7 @@ def read_param(self, param_file, codename="Swiftest", verbose=True): self.param = io.read_swift_param(param_file, verbose=verbose) self.codename = "Swift" else: - print(f'{codename} is not a recognized code name. Valid options are "Swiftest", "Swifter", or "Swift".') + warnings.warn(f'{codename} is not a recognized code name. Valid options are "Swiftest", "Swifter", or "Swift".') self.codename = "Unknown" return @@ -2412,7 +2415,7 @@ def write_param(self, elif codename == "Swift": io.write_swift_param(param, param_file) else: - print( 'Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') + warnings.warn('Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') return def convert(self, param_file, newcodename="Swiftest", plname="pl.swiftest.in", tpname="tp.swiftest.in", @@ -2442,10 +2445,10 @@ def convert(self, param_file, newcodename="Swiftest", plname="pl.swiftest.in", t """ oldparam = self.param if self.codename == newcodename: - print(f"This parameter configuration is already in {newcodename} format") + warnings.warn(f"This parameter configuration is already in {newcodename} format") return oldparam if newcodename != "Swift" and newcodename != "Swifter" and newcodename != "Swiftest": - print(f'{newcodename} is an invalid code type. Valid options are "Swiftest", "Swifter", or "Swift".') + warnings.warn(f'{newcodename} is an invalid code type. Valid options are "Swiftest", "Swifter", or "Swift".') return oldparam goodconversion = True if self.codename == "Swifter": @@ -2466,7 +2469,7 @@ def convert(self, param_file, newcodename="Swiftest", plname="pl.swiftest.in", t if goodconversion: self.write_param(param_file) else: - print(f"Conversion from {self.codename} to {newcodename} is not supported.") + warnings.warn(f"Conversion from {self.codename} to {newcodename} is not supported.") return oldparam def bin2xr(self): @@ -2485,7 +2488,7 @@ def bin2xr(self): # This is done to handle cases where the method is called from a different working directory than the simulation # results param_tmp = self.param.copy() - param_tmp['BIN_OUT'] = os.path.join(self.dir_path, self.param['BIN_OUT']) + param_tmp['BIN_OUT'] = os.path.join(self.sim_dir, self.param['BIN_OUT']) if self.codename == "Swiftest": self.ds = io.swiftest2xr(param_tmp, verbose=self.verbose) if self.verbose: print('Swiftest simulation data stored as xarray DataSet .ds') @@ -2493,10 +2496,9 @@ def bin2xr(self): self.ds = io.swifter2xr(param_tmp, verbose=self.verbose) if self.verbose: print('Swifter simulation data stored as xarray DataSet .ds') elif self.codename == "Swift": - print("Reading Swift simulation data is not implemented yet") + warnings.warn("Reading Swift simulation data is not implemented yet") else: - print( - 'Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') + warnings.warn('Cannot process unknown code type. Call the read_param method with a valid code name. Valid options are "Swiftest", "Swifter", or "Swift".') return def follow(self, codestyle="Swifter"): @@ -2527,7 +2529,7 @@ def follow(self, codestyle="Swifter"): i_list = [i for i in line.split(" ") if i.strip()] nskp = int(i_list[0]) except IOError: - print('No follow.in file found') + warnings.warn('No follow.in file found') ifol = None nskp = None fol = tool.follow_swift(self.ds, ifol=ifol, nskp=nskp) @@ -2582,7 +2584,7 @@ def save(self, io.swifter_xr2infile(self.ds, swifter_param, framenum) self.write_param(param_file, param=swifter_param) else: - print(f'Saving to {codename} not supported') + warnings.warn(f'Saving to {codename} not supported') return @@ -2608,7 +2610,8 @@ def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_fil Returns ------- - frame : NetCDF dataset + frame : NetCDF dataset + A dataset containing the extracted initial condition data. """ if codename != "Swiftest": @@ -2626,7 +2629,7 @@ def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_fil elif self.param['OUT_TYPE'] == 'NETCDF_FLOAT': new_param['IN_TYPE'] = 'NETCDF_FLOAT' else: - print(f"{self.param['OUT_TYPE']} is an invalid OUT_TYPE file") + warnings.warn(f"{self.param['OUT_TYPE']} is an invalid OUT_TYPE file") return if self.param['BIN_OUT'] != new_param['BIN_OUT'] and restart: From 3ff22ee01a13c9a4e869051bdbd28bc979d554de Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 16 Nov 2022 16:41:18 -0500 Subject: [PATCH 72/75] Fixed typo --- python/swiftest/swiftest/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 64d3f0c6e..3092edad3 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -855,7 +855,7 @@ def swiftest2xr(param, verbose=True): # Check if the name variable contains unique values. If so, make name the dimension instead of id if len(np.unique(ds['name'])) == len(ds['name']): ds = ds.swap_dims({"id" : "name"}) - sim.ds.reset_coords("id") + ds.reset_coords("id") else: print(f"Error encountered. OUT_TYPE {param['OUT_TYPE']} not recognized.") return None From 3006a9f53cb8fb0f88c4525a2005534500b87632 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 16 Nov 2022 16:42:28 -0500 Subject: [PATCH 73/75] Forgot that xarray doesn't self update datasets --- python/swiftest/swiftest/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/swiftest/swiftest/io.py b/python/swiftest/swiftest/io.py index 3092edad3..2b8c47cb3 100644 --- a/python/swiftest/swiftest/io.py +++ b/python/swiftest/swiftest/io.py @@ -855,7 +855,7 @@ def swiftest2xr(param, verbose=True): # Check if the name variable contains unique values. If so, make name the dimension instead of id if len(np.unique(ds['name'])) == len(ds['name']): ds = ds.swap_dims({"id" : "name"}) - ds.reset_coords("id") + ds = ds.reset_coords("id") else: print(f"Error encountered. OUT_TYPE {param['OUT_TYPE']} not recognized.") return None From ed5d09da696f96d843f7b7f1559f025f54c3bda1 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 16 Nov 2022 16:46:56 -0500 Subject: [PATCH 74/75] Updated Basic Simulation files --- .../Basic_Simulation/initial_conditions.py | 2 +- examples/Basic_Simulation/param.in | 2 +- examples/Basic_Simulation/run_from_file.py | 2 +- .../Basic_Simulation/run_simulation.ipynb | 159 ++++++++++++++++++ examples/Basic_Simulation/test_io.ipynb | 143 ---------------- 5 files changed, 162 insertions(+), 146 deletions(-) create mode 100644 examples/Basic_Simulation/run_simulation.ipynb delete mode 100644 examples/Basic_Simulation/test_io.ipynb diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index 95fdb608e..c14cdd931 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -72,5 +72,5 @@ sim.add_body(name_tp, a_tp, e_tp, inc_tp, capom_tp, omega_tp, capm_tp) -# Save everything to a set of initial conditions files +# Run the simulation sim.run() diff --git a/examples/Basic_Simulation/param.in b/examples/Basic_Simulation/param.in index 014bf1fb8..0ee870562 100644 --- a/examples/Basic_Simulation/param.in +++ b/examples/Basic_Simulation/param.in @@ -22,7 +22,6 @@ MU2KG 1.988409870698051e+30 TU2S 31557600.0 DU2M 149597870700.0 GMTINY 9.869231602224408e-07 -MIN_GMFRAG 9.869231602224408e-10 RESTART NO CHK_CLOSE YES GR YES @@ -35,3 +34,4 @@ RHILL_PRESENT NO INTERACTION_LOOPS TRIANGULAR ENCOUNTER_CHECK TRIANGULAR TIDES NO +MIN_GMFRAG 9.869231602224408e-10 diff --git a/examples/Basic_Simulation/run_from_file.py b/examples/Basic_Simulation/run_from_file.py index cede6e2ea..9c477410a 100644 --- a/examples/Basic_Simulation/run_from_file.py +++ b/examples/Basic_Simulation/run_from_file.py @@ -1,3 +1,3 @@ import swiftest sim = swiftest.Simulation() -sim.run() \ No newline at end of file +sim.run(tstop=20.0) \ No newline at end of file diff --git a/examples/Basic_Simulation/run_simulation.ipynb b/examples/Basic_Simulation/run_simulation.ipynb new file mode 100644 index 000000000..fa47bcd56 --- /dev/null +++ b/examples/Basic_Simulation/run_simulation.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "86c845ce-1801-46ca-8a8a-1cabb266e6a6", + "metadata": {}, + "outputs": [], + "source": [ + "import swiftest\n", + "import xarray as xr\n", + "import numpy as np\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d716c371-8eb4-4fc1-82af-8b5c444c831e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reading Swiftest file /home/daminton/git_debug/swiftest/examples/Basic_Simulation/param.in\n" + ] + } + ], + "source": [ + "sim = swiftest.Simulation()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ec7452d6-4c9b-4df3-acc0-b11c32264b91", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tstop 10.0 y\n", + "Writing parameter inputs to file /home/daminton/git_debug/swiftest/examples/Basic_Simulation/param.in\n", + "Running a Swiftest symba run from tstart=0.0 y to tstop=10.0 y\n", + "\u001b]2;cd /home/daminton/git_debug/swiftest/examples/Basic_Simulation\u0007\u001b]1;\u0007\u001b]2;/home/daminton/git_debug/swiftest/bin/swiftest_driver symba \u0007\u001b]1;\u0007 Parameter input file is /home/daminton/git_debug/swiftest/examples/Basic_Simulation/param.in\n", + " \n", + " Warning! NPLM variable not set in input file. Calculating.\n", + " *************** Main Loop *************** \n", + "Time = 1.00000E+00; fraction done = 0.100; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 6.30684E-01; Interval wall time: 5.29890E-01;Interval wall time/step: 2.70141E-03\n", + "Time = 2.00000E+00; fraction done = 0.200; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 1.66455E+00; Interval wall time: 5.27720E-01;Interval wall time/step: 2.99766E-03\n", + "Time = 3.00000E+00; fraction done = 0.300; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 2.64051E+00; Interval wall time: 5.20805E-01;Interval wall time/step: 2.88832E-03\n", + "Time = 4.00000E+00; fraction done = 0.400; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 3.60585E+00; Interval wall time: 5.24579E-01;Interval wall time/step: 2.82311E-03\n", + "Time = 5.00000E+00; fraction done = 0.500; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 4.58823E+00; Interval wall time: 5.37595E-01;Interval wall time/step: 2.96439E-03\n", + "Time = 6.00000E+00; fraction done = 0.600; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 5.55600E+00; Interval wall time: 5.27663E-01;Interval wall time/step: 2.83397E-03\n", + "Time = 7.00000E+00; fraction done = 0.700; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 6.64194E+00; Interval wall time: 5.83431E-01;Interval wall time/step: 3.11589E-03\n", + "Time = 8.00000E+00; fraction done = 0.800; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 7.63044E+00; Interval wall time: 5.52408E-01;Interval wall time/step: 2.95843E-03\n", + "Time = 9.00000E+00; fraction done = 0.900; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 8.62339E+00; Interval wall time: 5.46944E-01;Interval wall time/step: 3.01578E-03\n", + "Time = 1.00000E+01; fraction done = 1.000; Number of active plm, pl, tp = 13, 14, 10\n", + "Integration steps: Total wall time: 9.67297E+00; Interval wall time: 6.00750E-01;Interval wall time/step: 3.20243E-03\n", + "\n", + "Normal termination of Swiftest (version 1.0)\n", + "------------------------------------------------\n", + "\n", + "Creating Dataset from NetCDF file\n", + "Successfully converted 11 output frames.\n", + "Swiftest simulation data stored as xarray DataSet .ds\n" + ] + } + ], + "source": [ + "sim.run(tstop=10.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "5b0a57a6-dbd5-4d34-8e6e-91fc8ad8789f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sim.ds.where(sim.ds['particle_type'] == 'Massive Body',drop=True)['a'].plot(hue=\"name\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f46b22e-dbd0-4562-a4cf-ea55247a2126", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (My debug_env Kernel)", + "language": "python", + "name": "debug_env" + }, + "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.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/Basic_Simulation/test_io.ipynb b/examples/Basic_Simulation/test_io.ipynb deleted file mode 100644 index ce92a8229..000000000 --- a/examples/Basic_Simulation/test_io.ipynb +++ /dev/null @@ -1,143 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "86c845ce-1801-46ca-8a8a-1cabb266e6a6", - "metadata": {}, - "outputs": [], - "source": [ - "import swiftest\n", - "import xarray as xr\n", - "import numpy as np\n", - "import os" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d716c371-8eb4-4fc1-82af-8b5c444c831e", - "metadata": {}, - "outputs": [], - "source": [ - "sim = swiftest.Simulation()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "83cebbc1-387b-4ef5-b96e-76856b6672e5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "integrator symba\n", - "codename Swiftest\n", - "driver_executable /Users/daminton/git/swiftest/build/swiftest_driver\n", - "t0 0.0 y\n", - "tstart 0.0 y\n", - "tstop NOT SET\n", - "dt NOT SET\n", - "istep_out NOT SET\n", - "istep_dump NOT SET\n", - "init_cond_file_type NETCDF_DOUBLE\n", - "init_cond_format EL\n", - "init_cond_file_name init_cond.nc\n", - "output_file_type NETCDF_DOUBLE\n", - "output_file_name bin.nc\n", - "output_format XVEL\n", - "rmin 0.004650467260962157 AU\n", - "rmax 10000.0 AU\n", - "qmin_coord HELIO\n", - "MU: MSun 1.988409870698051e+30 kg / MSun\n", - "DU: AU 149597870700.0 m / AU\n", - "TU: y 31557600.0 s / y\n", - "close_encounter_check True\n", - "general_relativity True\n", - "fragmentation True\n", - "rotation True\n", - "compute_conservation_values False\n", - "extra_force False\n", - "big_discard False\n", - "rhill_present False\n", - "restart False\n", - "interaction_loops TRIANGULAR\n", - "encounter_check_loops TRIANGULAR\n", - "ephemeris_date 2027-04-30\n" - ] - }, - { - "data": { - "text/plain": [ - "{'! VERSION': 'Swiftest parameter input',\n", - " 'T0': 0.0,\n", - " 'TSTART': 0.0,\n", - " 'IN_TYPE': 'NETCDF_DOUBLE',\n", - " 'IN_FORM': 'EL',\n", - " 'NC_IN': 'init_cond.nc',\n", - " 'OUT_TYPE': 'NETCDF_DOUBLE',\n", - " 'BIN_OUT': 'bin.nc',\n", - " 'OUT_FORM': 'XVEL',\n", - " 'CHK_RMIN': 0.004650467260962157,\n", - " 'CHK_RMAX': 10000.0,\n", - " 'CHK_QMIN_COORD': 'HELIO',\n", - " 'CHK_QMIN': 0.004650467260962157,\n", - " 'CHK_QMIN_RANGE': '0.004650467260962157 10000.0',\n", - " 'MU2KG': 1.988409870698051e+30,\n", - " 'DU2M': 149597870700.0,\n", - " 'TU2S': 31557600.0,\n", - " 'CHK_CLOSE': True,\n", - " 'GR': True,\n", - " 'FRAGMENTATION': True,\n", - " 'ROTATION': True,\n", - " 'ENERGY': False,\n", - " 'EXTRA_FORCE': False,\n", - " 'BIG_DISCARD': False,\n", - " 'RHILL_PRESENT': False,\n", - " 'RESTART': False,\n", - " 'INTERACTION_LOOPS': 'TRIANGULAR',\n", - " 'ENCOUNTER_CHECK': 'TRIANGULAR'}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sim.get_parameter()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ec7452d6-4c9b-4df3-acc0-b11c32264b91", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.9.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 0aedbe63339405d15115b2aa77c301cf3101a7d9 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 16 Nov 2022 16:53:58 -0500 Subject: [PATCH 75/75] Refactored the variable ds to be data throughout the Simulation class definition. This makes it more obvious what it is and more intuitive to use (IMHO) --- .../Basic_Simulation/initial_conditions.py | 9 +--- .../Basic_Simulation/run_simulation.ipynb | 2 +- python/swiftest/swiftest/simulation_class.py | 48 +++++++++---------- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/examples/Basic_Simulation/initial_conditions.py b/examples/Basic_Simulation/initial_conditions.py index c14cdd931..78900ce1f 100644 --- a/examples/Basic_Simulation/initial_conditions.py +++ b/examples/Basic_Simulation/initial_conditions.py @@ -15,14 +15,7 @@ Returns ------- -param.in : ASCII text file - Swiftest parameter input file. -pl.in : ASCII text file - Swiftest massive body input file. -tp.in : ASCII text file - Swiftest test particle input file. -cb.in : ASCII text file - Swiftest central body input file. +Updates sim.data with the simulation data """ import swiftest diff --git a/examples/Basic_Simulation/run_simulation.ipynb b/examples/Basic_Simulation/run_simulation.ipynb index fa47bcd56..8f2ab51b3 100644 --- a/examples/Basic_Simulation/run_simulation.ipynb +++ b/examples/Basic_Simulation/run_simulation.ipynb @@ -123,7 +123,7 @@ } ], "source": [ - "sim.ds.where(sim.ds['particle_type'] == 'Massive Body',drop=True)['a'].plot(hue=\"name\")" + "sim.data.where(sim.data['particle_type'] == 'Massive Body',drop=True)['a'].plot(hue=\"name\")" ] }, { diff --git a/python/swiftest/swiftest/simulation_class.py b/python/swiftest/swiftest/simulation_class.py index 5df0c3edb..7c17c3d32 100644 --- a/python/swiftest/swiftest/simulation_class.py +++ b/python/swiftest/swiftest/simulation_class.py @@ -81,7 +81,7 @@ def __init__(self,read_param: bool = True, **kwargs: Any): The stopping time for a simulation. `tstop` must be greater than `tstart`. Parameter input file equivalent: `TSTOP` dt : float, optional - The step size of the simulation. `dt` must be less than or equal to `tstop-dstart`. + The step size of the simulation. `dt` must be less than or equal to `tstop-tstart`. Parameter input file equivalent: `DT` istep_out : int, optional The number of time steps between outputs to file. *Note*: only `istep_out` or `toutput` can be set. @@ -276,7 +276,7 @@ def __init__(self,read_param: bool = True, **kwargs: Any): self._getter_column_width = '32' self.param = {} - self.ds = xr.Dataset() + self.data = xr.Dataset() # Parameters are set in reverse priority order. First the defaults, then values from a pre-existing input file, # then using the arguments passed via **kwargs. @@ -1959,7 +1959,7 @@ def add_solar_system_body(self, >*Note.* Currently only the JPL Horizons ephemeris is implemented, so this is ignored. Returns ------- - ds : Xarray dataset with body or bodies added. + data : Xarray dataset with body or bodies added. """ if type(name) is str: @@ -2195,7 +2195,7 @@ def add_body(self, Adds a body (test particle or massive body) to the internal DataSet given a set up 6 vectors (orbital elements or cartesian state vectors, depending on the value of self.param). Input all angles in degress. - This method will update self.ds with the new body or bodies added to the existing Dataset. + This method will update self.data with the new body or bodies added to the existing Dataset. Parameters ---------- @@ -2228,7 +2228,7 @@ def add_body(self, Returns ------- - ds : Xarray Dataset + data : Xarray Dataset Dasaset containing the body or bodies that were added """ @@ -2276,16 +2276,16 @@ def input_to_array(val,t,n=None): J2 = input_to_array(J2,"f",nbodies) J4 = input_to_array(J4,"f",nbodies) - if len(self.ds) == 0: + if len(self.data) == 0: maxid = -1 else: - maxid = self.ds.id.max().values[()] + maxid = self.data.id.max().values[()] if idvals is None: idvals = np.arange(start=maxid+1,stop=maxid+1+nbodies,dtype=int) - if len(self.ds) > 0: - dup_id = np.in1d(idvals,self.ds.id) + if len(self.data) > 0: + dup_id = np.in1d(idvals, self.data.id) if any(dup_id): raise ValueError(f"Duplicate ids detected: ", *idvals[dup_id]) @@ -2317,7 +2317,7 @@ def _combine_and_fix_dsnew(self,dsnew): """ - self.ds = xr.combine_by_coords([self.ds, dsnew]) + self.data = xr.combine_by_coords([self.data, dsnew]) def get_nvals(ds): if "Gmass" in ds: @@ -2329,14 +2329,14 @@ def get_nvals(ds): return ds dsnew = get_nvals(dsnew) - self.ds = get_nvals(self.ds) + self.data = get_nvals(self.data) if self.param['OUT_TYPE'] == "NETCDF_DOUBLE": dsnew = io.fix_types(dsnew, ftype=np.float64) - self.ds = io.fix_types(self.ds, ftype=np.float64) + self.data = io.fix_types(self.data, ftype=np.float64) elif self.param['OUT_TYPE'] == "NETCDF_FLOAT": dsnew = io.fix_types(dsnew, ftype=np.float32) - self.ds = io.fix_types(self.ds, ftype=np.float32) + self.data = io.fix_types(self.data, ftype=np.float32) return dsnew @@ -2481,7 +2481,7 @@ def bin2xr(self): Returns ------- - self.ds : xarray dataset + self.data : xarray dataset """ # Make a temporary copy of the parameter dictionary so we can supply the absolute path of the binary file @@ -2490,11 +2490,11 @@ def bin2xr(self): param_tmp = self.param.copy() param_tmp['BIN_OUT'] = os.path.join(self.sim_dir, self.param['BIN_OUT']) if self.codename == "Swiftest": - self.ds = io.swiftest2xr(param_tmp, verbose=self.verbose) - if self.verbose: print('Swiftest simulation data stored as xarray DataSet .ds') + self.data = io.swiftest2xr(param_tmp, verbose=self.verbose) + if self.verbose: print('Swiftest simulation data stored as xarray DataSet .data') elif self.codename == "Swifter": - self.ds = io.swifter2xr(param_tmp, verbose=self.verbose) - if self.verbose: print('Swifter simulation data stored as xarray DataSet .ds') + self.data = io.swifter2xr(param_tmp, verbose=self.verbose) + if self.verbose: print('Swifter simulation data stored as xarray DataSet .data') elif self.codename == "Swift": warnings.warn("Reading Swift simulation data is not implemented yet") else: @@ -2514,7 +2514,7 @@ def follow(self, codestyle="Swifter"): ------- fol : xarray dataset """ - if self.ds is None: + if self.data is None: self.bin2xr() if codestyle == "Swift": try: @@ -2532,7 +2532,7 @@ def follow(self, codestyle="Swifter"): warnings.warn('No follow.in file found') ifol = None nskp = None - fol = tool.follow_swift(self.ds, ifol=ifol, nskp=nskp) + fol = tool.follow_swift(self.data, ifol=ifol, nskp=nskp) else: fol = None @@ -2574,14 +2574,14 @@ def save(self, param = self.param if codename == "Swiftest": - io.swiftest_xr2infile(ds=self.ds, param=param, in_type=self.param['IN_TYPE'], framenum=framenum) + io.swiftest_xr2infile(ds=self.data, param=param, in_type=self.param['IN_TYPE'], framenum=framenum) self.write_param(param_file=param_file) elif codename == "Swifter": if codename == "Swiftest": swifter_param = io.swiftest2swifter_param(param) else: swifter_param = param - io.swifter_xr2infile(self.ds, swifter_param, framenum) + io.swifter_xr2infile(self.data, swifter_param, framenum) self.write_param(param_file, param=swifter_param) else: warnings.warn(f'Saving to {codename} not supported') @@ -2623,7 +2623,7 @@ def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_fil if codename == "Swiftest": if restart: - new_param['T0'] = self.ds.time.values[framenum] + new_param['T0'] = self.data.time.values[framenum] if self.param['OUT_TYPE'] == 'NETCDF_DOUBLE': new_param['IN_TYPE'] = 'NETCDF_DOUBLE' elif self.param['OUT_TYPE'] == 'NETCDF_FLOAT': @@ -2646,7 +2646,7 @@ def initial_conditions_from_bin(self, framenum=-1, new_param=None, new_param_fil new_param.pop('TP_IN', None) new_param.pop('CB_IN', None) print(f"Extracting data from dataset at time frame number {framenum} and saving it to {new_param['NC_IN']}") - frame = io.swiftest_xr2infile(self.ds, self.param, infile_name=new_param['NC_IN'], framenum=framenum) + frame = io.swiftest_xr2infile(self.data, self.param, infile_name=new_param['NC_IN'], framenum=framenum) print(f"Saving parameter configuration file to {new_param_file}") self.write_param(new_param_file, param=new_param)