# coding: utf-8 import os from distutils.file_util import copy_file import glob import numpy as np from functools import partial from scipy.stats import linregress from baseprocessor import Rule, HDF5Container from aggregator import Aggregator from snapshotprocessor import SnapshotProcessor from utils.runselector import RunSelector from utils.params import default_params from utils.units import U class StudyProcessor(Aggregator, HDF5Container): """ This object is linked to several ramses simulation of a same study """ def __init__( self, path, runs, nums, path_out=None, params=default_params(), selector=None, tag=None, unit_time=U.year, **kwargs, ): """ Creates the basic structures needed for the outputs """ # log id self.log_id = "study({})".format(tag) super(StudyProcessor, self).__init__(path, path_out, params, tag) # Open outfile tag_name = self.params.out.tag if not self.params.out.tag == "": tag_name = "_" + tag_name if self.params.out.ext_subfolder: self.filename = f"{self.path_out}/h5/study{tag_name}.h5" else: self.filename = f"{self.path_out}/study{tag_name}.h5" os.makedirs(os.path.dirname(self.filename), exist_ok=True) # Select runs if selector is None: selector = RunSelector( path, runs, nums, self.params.input.nml_filename, unit_time=unit_time, **kwargs, ) # Save infos self.path = path self.runs = selector.runs self.nums = selector.nums run0 = self.runs[0] self.info = selector.info[run0][self.nums[run0][0]] self.namelist = selector.namelist # Get postprocesor objets for each run and infos on them self.snaps = {} for run in self.runs: path_run = path + "/" + run path_out_run = self.path_out + "/" + run self.snaps[run] = {} for num in self.nums[run]: self.snaps[run][num] = SnapshotProcessor( path_run, num, selector=selector, path_out=path_out_run, params=self.params, unit_time=unit_time, ) # Save namelist and logs if self.params.out.copy_info: for run in self.runs: nml_src = f"{self.path}/{run}/{self.params.input.nml_filename}" nml_dest = f"{self.path_out}/{run}/{self.params.input.nml_filename}" if os.path.exists(nml_src): copy_file(nml_src, nml_dest, update=1) logs = self.get_logs(run) os.makedirs(f"{self.path_out}/{run}/logs", exist_ok=True) for log in logs: dest = f"{self.path_out}/{run}/logs/{os.path.basename(log)}" if os.path.exists(log): copy_file(log, dest, update=1) # Define rules self.def_rules() def _needs_computation(self, overwrite, name_full): """ Returns True if a new computation of the rule is needed """ if overwrite or not (name_full in self.save): return True elif "nums" not in self.save.get_node(name_full)._v_attrs: return True else: saved_nums = self.save.get_node(name_full)._v_attrs.nums missing_runs = len([run for run in self.nums if run not in saved_nums]) > 0 missing_nums = missing_runs or all( [ len([num for num in self.nums[run] if num not in saved_nums[run]]) > 0 for run in self.nums if run in saved_nums ] ) return missing_nums def _save_data(self, name_full, data, description, unit): super(StudyProcessor, self)._save_data(name_full, data, description, unit) if name_full in self.save: self.save.get_node(name_full)._v_attrs.nums = self.nums else: self.logger.warning(f"{name_full} was not written") def time_series(self, getter, arg=None): series = {} for run in self.runs: series[run] = [] for i, num in enumerate(self.nums[run]): series[run].append(getter(run, num, arg=arg)) series[run] = np.array(series[run], dtype=float) return series def compare(self, getter, use_num=True, select=None): if select is None: runs = self.runs else: runs, _ = self.selector.select(**select) prop = {} for i, run in enumerate(runs): if use_num: num = self.nums[run][0] prop[run] = getter(run, num) else: prop[run] = getter(run) return np.array(list(prop.values())) def time_avg( self, name, start=None, end=None, span=None, unit_time=U.Myr, group="/series", select=None, ): """Do the time average and quantiles of a time series Parameters ---------- name : str name of the array to average start : float, optional The average is taken between start and end or start + span, by default None end : float, optional The average is taken between start and end or end - span, by default None span : _type_, optional length of the averaging period (overrrided if both start and end are set), by default None unit_time : _type_, optional Time unit to use, by default U.Myr group : str, optional group of the data to average, by default "/series" select : dict, optional arguments to selector, by default None Returns ------- dict time average and quantiles """ serie0 = self.save.get_node(group + "/" + name + "/" + self.runs[0]).read() if len(serie0.shape) > 1: shape = [len(self.runs)] + list(serie0.shape[1:]) else: shape = len(self.runs) mean = np.zeros(shape) median = np.zeros(shape) std = np.zeros(shape) v_min = np.zeros(shape) v_max = np.zeros(shape) q975 = np.zeros(shape) q025 = np.zeros(shape) q16 = np.zeros(shape) q84 = np.zeros(shape) if select is None: runs = self.runs else: runs, _ = self.selector.select(**select) for i, run in enumerate(runs): serie = self.get_value(group + "/" + name + "/" + run) time = self.get_value(group + "/time/" + run, unit=unit_time) if len(serie.shape) <= 1: mask = np.isfinite(serie) if not ((start, end, span) == (None, None, None)): start_r, end_r = start, end # Be sure that start_r and end_r are defined if end_r is None: if span is None or start_r is None: end_r = time[-1] else: end_r = start_r + span if start_r is None: if span is None: start_r = time[0] else: start_r = end_r - span mask = ( mask & (time >= start_r) & (time <= end_r) & np.isfinite(serie) ) serie = serie[mask] mean[i] = np.mean(serie, axis=0) std[i] = np.std(serie, axis=0) ( v_min[i], q025[i], q16[i], median[i], q84[i], q975[i], v_max[i], ) = np.percentile(serie, [0, 2.5, 16, 50, 84, 97.5, 100], axis=0) return { "runs": np.array(runs), "mean": mean, "std": std, "median": median, "min": v_min, "max": v_max, "q025": q025, "q975": q975, "q16": q16, "q84": q84, } def get_attr(self, attr_name, run, num, node_name="/", arg=None): snap = self.snaps[run][num] if arg is not None: node_name = node_name + "_" + str(arg) return snap.get_attribute(node_name, attr_name) def get_snap_value(self, name, run, num, arg=None): snap = self.snaps[run][num] if arg is not None: name = name + "_" + str(arg) return snap.get_value(name) def get_global(self, node_name, run, num, arg=None, unload_cells=False): if arg is not None: node_name = node_name + "_" + str(arg) snap = self.snaps[run][num] if unload_cells: snap.unload_cells() value = snap.get_value(node_name) return value def get_nml(self, nml_key=None, run=None): if run is not None: if nml_key is not None: return self.namelist[run][nml_key] else: return self.namelist[run] else: if nml_key is not None: return {run: self.namelist[run][nml_key] for run in self.runs} else: return self.namelist def get_pdf_slope(self, name, run, num): snap = self.snaps[run][num] snap.process(["fit_pdf_" + name], ["z"], overwrite=self.overwrite_dep) slope = snap.get_attribute("/hist/pdf_" + name + "_z", "slope") return slope def _extract_sinks_from_log(self, series, log_filename, run): cmd_grep = f"grep 'Number of sink' {log_filename} -A 2" content = os.popen(cmd_grep).readlines() block_err = [] # Block that will ill parsed for i in range(0, len(content), 4): try: nb_sink = np.int(content[i].split("=")[1]) mass_sink = np.float(content[i + 1].split("=")[1]) time = np.float(content[i + 2].split("=")[1]) timekey = content[i + 2].split("=")[0] series["nb_sink"][run].append(nb_sink) series["mass_sink"][run].append(mass_sink) if "[yr]" not in timekey: time *= self.info["unit_time"].express(U.year) series["time"][run].append(time) except (ValueError, IndexError): block_err.append(i) if len(block_err) > 0: self.logger.warning( f"Errors encountered in parsing {log_filename} (grepped blocks {block_err})" ) return series def _extract_stellar_from_log(self, stellar_objects, log_filename, run): cmd_grep = f"grep 'stellar objects:' {log_filename} -n" content = os.popen(cmd_grep).readlines() nb_stellar = list(map(lambda s: int(s.split()[1]), content)) line_numbers = list(map(lambda s: int(s.split(":")[0]), content)) current_line = 0 block_err = [] # Block that will ill parsed logfile = open(log_filename) for i in range(0, len(line_numbers)): try: while current_line < line_numbers[i] + 3: logfile.readline() current_line += 1 for j in range(nb_stellar[i]): line_stellar = logfile.readline().split() current_line += 1 while ( line_stellar[0] == "random" ): # random number outputs are ... random line_stellar = logfile.readline().split() current_line += 1 mass = float(line_stellar[3]) time = float(line_stellar[4]) lifetime = float(line_stellar[5]) id = int(line_stellar[6]) stellar_objects["mass"][run].append(mass) stellar_objects["time"][run].append(time) stellar_objects["lifetime"][run].append(lifetime) stellar_objects["id"][run].append(id) except (ValueError, IndexError): block_err.append(i) if len(block_err) > 0: self.logger.warning( f"Errors encountered in parsing {log_filename} (grepped blocks {block_err})" ) logfile.close() return stellar_objects def _extract_sfr_from_log(self, series, log_filename, run): cmd_grep = "grep '\[SFR' {} ".format(log_filename) content = os.popen(cmd_grep).readlines() for i in range(0, len(content)): time = np.float(content[i].split("]")[0].split("=")[1].split()[0]) sfr = np.float(content[i].split("]")[1].split("=")[1].split()[0]) series["time"][run].append(time) series["sfr"][run].append(sfr) return series def _extract_fine_step_from_log(self, series, log_filename, run): cmd_grep = "grep 'Fine step' {} ".format(log_filename) content = os.popen(cmd_grep).readlines() block_err = [] # Blocks that are ill parsed for i in range(0, len(content)): try: data = content[i].replace("=", " ").split() fine_step = np.int(data[2]) time = np.float(data[4]) dt = np.float(data[6]) a = np.float(data[8]) mempc1 = np.float(data[10][:-1]) mempc2 = np.float(data[11][:-1]) series["time"][run].append(time) series["fine_step"][run].append(fine_step) series["dt"][run].append(dt) series["a"][run].append(a) series["mem_cells"][run].append(mempc1) series["mem_parts"][run].append(mempc2) except (ValueError, IndexError): block_err.append(i) if len(block_err) > 0: self.logger.warning( f"Error encountered in parsing {log_filename} (grepped blocks {block_err})" ) return series def _extract_coarse_step_from_log(self, series, log_filename, run): rism = self.params.input.ramses_ism nlines = 2 + int(rism) # Number of useful lines cmd_grep = "grep 'Main step\\|coarse step' {} -A {}".format( log_filename, nlines - 1 ) content = os.popen(cmd_grep).readlines() block_err = [] # Blocks that are ill parsed for j in range(0, len(content), 2 * (nlines + 1)): i = j + nlines + 1 # Index for the "Main step" grep if i + nlines - 1 < len(content): try: series["time"][run].append( np.float(content[i + nlines - 1].split("=")[2].split()[0]) ) series["step"][run].append( np.int(content[i].split("=")[1].split()[0]) ) series["mcons"][run].append( np.float(content[i].split("=")[2].split()[0]) ) series["econs"][run].append( np.float(content[i].split("=")[3].split()[0]) ) series["epot"][run].append( np.float(content[i].split("=")[4].split()[0]) ) series["ekin"][run].append( np.float(content[i].split("=")[5].split()[0]) ) if rism: eint = np.float(content[i].split("=")[6].split()[0]) emag = np.float(content[i + 1].split("=")[1].split()[0]) else: eint = 0.0 emag = 0.0 series["eint"][run].append(eint) series["emag"][run].append(emag) series["elapsed"][run].append( np.float(content[j].split(":")[1].split()[0]) ) series["memory"][run].append(content[j + 1].split(":")[1]) except (ValueError, IndexError): block_err.append(i) if len(block_err) > 0: self.logger.warning( f"Error encountered in parsing {log_filename} (grepped blocks {block_err})" ) return series def _extract_rms_from_log(self, series, log_filename, run): cmd_grep = "grep 'turbulent rms' {} -C 1".format(log_filename) content = os.popen(cmd_grep).readlines() for i in range(0, len(content), 4): series["time"][run].append(np.float(content[i].split("=")[2].split()[0])) series["dt"][run].append(np.float(content[i].split("=")[3].split()[0])) series["turb_rms"][run].append(np.float(content[i + 1].split(":")[1])) try: turb_energy = np.float(content[i + 2].split(":")[1]) threshold = self.params.rules.turb_energy_threshold assert threshold < 0 or abs(turb_energy) < threshold series["turb_energy"][run].append(abs(turb_energy)) except (AssertionError, ValueError, IndexError): series["turb_energy"][run].append(np.nan) return series def _extract_SN_Mom_from_log(self, series, log_filename, run): cmd_grep = f"grep -e 'SN momentum' -e 'Fine step' {log_filename}" content = os.popen(cmd_grep).readlines() block_err = [] time = 0 for i in range(0, len(content)): try: if content[i][1:5] == "Fine": data = content[i].replace("=", " ").split() time = np.float(data[4]) elif content[i][1:3] == "SN": series["time"][run].append(time) series["SN_momentum"][run].append(np.float(content[i].split()[-1])) else: raise ValueError("Wrong start of line") except (AssertionError, ValueError, IndexError): block_err.append(i) if len(block_err) > 0: self.logger.warning( f"Error encountered in parsing {log_filename} (grepped blocks {block_err})" ) return series def get_logs(self, run): glob_str = f"{self.path}/{run}/{self.params.input.log_prefix}*" logs = glob.glob(glob_str) if len(logs) == 0: glob_str = f"{self.path}/{run}/logs/{self.params.input.log_prefix}*" logs = glob.glob(glob_str) return logs def _from_log(self, keys, extractor, unique=False): # Initialize series series = {} for key in keys: series[key] = {} for run in self.runs: # Initialize list for key in keys: series[key][run] = [] # Get list of log files log_files = self.get_logs(run) # Parse files for log_filename in log_files: size = len(series["time"][run]) series = extractor(series, log_filename, run) if unique: # Always prefer data from last log, assuming they come in the right order time = series["time"][run] time_new = time[size] ind_overlap = np.searchsorted(time[:size], time_new, side="right") for key in series: del series[key][run][ind_overlap:size] # Numpify the lists for key in series: series[key][run] = np.array(series[key][run]) # Sort the arrays ind_sort = series["time"][run].argsort() for key in series: series[key][run] = series[key][run][ind_sort] return series def get_coldens0(self, run): mp = 1.4 * 1.66 * 10 ** (-24) * U.g try: z0 = self.get_nml("galbox_params/height0", run) * U.pc n0 = self.get_nml("galbox_params/dens0", run) * U.cm ** (-3) except KeyError: z0 = self.get_nml("cloud_params/height0", run) * U.pc n0 = self.get_nml("cloud_params/dens0", run) * U.cm ** (-3) return (np.sqrt(2 * np.pi) * mp * z0 * n0).express(U.coldens) def total_mass(self): """ Sum of gas plus sink mass """ time_gas = self.get_value("/series/coarse_step_from_log/time") mass_gas = self.get_value("/series/coarse_step_from_log/mcons") mass_sink = self.get_value("/series/sinks_from_log/mass_sink") time_sink = self.get_value("/series/sinks_from_log/time") total_mass = {} for run in self.runs: if time_sink[run][-1] > time_gas[run][-1]: time_sink[run] = time_sink[run][:-1] mass_sink[run] = mass_sink[run][:-1] # A bit specific ... needs to be generalized (TODO) info = self.snaps[run][self.nums[run][0]].info surface = (info["unit_length"].express(U.pc)) ** 2 m0 = self.get_coldens0(run) * surface # Initial mass in Msun offset = time_gas[run].size - time_sink[run].size mass_gas[run] = m0 + m0 * mass_gas[run] # convert in Msun total_mass[run] = mass_gas[run].copy() total_mass[run][offset:] = ( mass_gas[run][offset:] + mass_sink[run] ) # re add sink_mass return time_gas, total_mass, mass_gas def _ssfr_from_mass_sink(self, avg_window=None): """ avg_window in year """ time_unit = self.get_attribute("/series/sinks_from_log/time", "unit") mass_unit = self.get_attribute("/series/sinks_from_log/mass_sink", "unit") ssfr = {} for run in self.runs: # Surface of the box in pc^2 info = self.snaps[run][self.nums[run][0]].info surface = (info["unit_length"].express(U.pc)) ** 2 # WARNING : We do not multiply by boxlen since already done in 'unit_length' (pymses) time = self.get_value(f"/series/sinks_from_log/time/{run}") time = time * time_unit.express(U.year) mass_sink = self.get_value(f"/series/sinks_from_log/mass_sink/{run}") mass_sink = mass_sink * mass_unit.express(U.Msun) if avg_window is None: shift = 1 else: # We assume that the timestep do not vary a lot ... shift = np.searchsorted(time, time[-1] - avg_window, side="right") shift = len(time) - shift sfr = (mass_sink[shift:] - mass_sink[:-shift]) / ( time[shift:] - time[:-shift] ) sfr_beg = (mass_sink[:shift] - mass_sink[0]) / (time[:shift] - time[0]) ssfr[run] = np.zeros(len(mass_sink)) ssfr[run][shift:] = sfr / surface ssfr[run][:shift] = sfr_beg / surface return ssfr, {"avg_window": avg_window} def _surfacic_sink_mass(self): mass_unit = self.get_attribute("/series/sinks_from_log/mass_sink", "unit") ssm = {} for run in self.runs: # Surface of the box in pc^2 info = self.snaps[run][self.nums[run][0]].info surface = (info["unit_length"].express(U.pc)) ** 2 mass_sink = self.get_value(f"/series/sinks_from_log/mass_sink/{run}") mass_sink = mass_sink * mass_unit.express(U.Msun) ssm[run] = mass_sink / surface return ssm def _turb_power(self): turb_power = {} for run in self.runs: dt = self.get_value(f"/series/rms_from_log/dt/{run}") # Energy injected at each timestep energy = self.get_value(f"/series/rms_from_log/turb_energy/{run}") # Power of the turbulence at this step in Watts turb_power[run] = energy / dt return turb_power def _sbeta_onavg(self): """ [ismfeed] Compute the slope of the Sigma pdf as a function of the value of beta """ col_pdf = self.get_value("/comp/avg_time_coldens_pdf_z") beta = self.get_value("/comp/nml_cloud_params/beta_cool") slope = np.zeros(len(col_pdf["runs"])) origin = np.zeros(len(col_pdf["runs"])) stderr = np.zeros(len(col_pdf["runs"])) for i, run in enumerate(col_pdf["runs"]): values, centers = col_pdf["mean"][i] mask_fit = ( (centers > self.params.pdf.xmin_fit) & (centers < self.params.pdf.xmax_fit) & (values > np.max(values) * self.params.pdf.fit_cut) ) (slope[i], origin[i], correlation, _, stderr[i]) = linregress( centers[mask_fit], np.log10(values[mask_fit]) ) return {"beta": beta, "slope": slope, "origin": origin, "stderr": stderr} def _gen_rule_time_global( self, glob_name, name=None, glob_group="/globals", unit=U.none, ): if name is None: name = "time_" + glob_name self.rules[name] = Rule( partial( self.time_series, partial( self.get_global, glob_group + "/" + glob_name, unload_cells=True ), ), group="/series", unit=unit, dependencies={"time": None, glob_name: "__parent__"}, ) def _gen_rule_avg(self, rule_src_name, subarray_name=None, name=None): rule_src = self.rules[rule_src_name] if subarray_name is None: src_name = rule_src_name group_src = rule_src.group unit = rule_src.unit descr = rule_src.description else: src_name = subarray_name group_src = rule_src.group + "/" + rule_src_name unit = rule_src.unit[subarray_name] descr = rule_src.description[subarray_name] description = { "runs": "List of runs", "mean": "Temporal average of {}".format(descr), "std": "Standard deviation of {}".format(descr), "median": "Median of {}".format(descr), "max": "Maximum of {}".format(descr), "min": "Minimum of {}".format(descr), "q025": "2.5 percentile of {}".format(descr), "q975": "97.5 percentile of {}".format(descr), "q16": "16 percentile of {}".format(descr), "q84": "84 percentile of {}".format(descr), } units = unit if name is None: name = "avg_" + src_name def fn(arg=None, **kwargs): if arg is None: return self.time_avg(src_name, group=group_src, **kwargs) else: return self.time_avg( src_name + "_" + str(arg), group=group_src, **kwargs ) self.rules[name] = Rule( fn, group="/comp", unit=units, description=description, dependencies=[rule_src_name], ) def def_rules(self): self.rules = { # Read from log "sinks_from_log": Rule( partial( self._from_log, ["time", "mass_sink", "nb_sink"], self._extract_sinks_from_log, ), group="/series", unit={"time": U.year, "mass_sink": U.Msun, "nb_sink": U.none}, description={ "time": "Time", "mass_sink": "Total mass of stars", "nb_sink": "Number of stars", }, ), "issfr": Rule( self._ssfr_from_mass_sink, group="/series/sinks_from_log", unit=U.ssfr, description="Instantaneous surfacic star formation rate", dependencies=["sinks_from_log"], ), "ssm": Rule( self._surfacic_sink_mass, group="/series/sinks_from_log", unit=U.Msun / U.pc**2, description="Surfacic sink mass", dependencies=["sinks_from_log"], ), "sfr_from_log": Rule( partial(self._from_log, ["time", "sfr"], self._extract_sfr_from_log), group="/series", unit={"time": U.year, "sfr": U.ssfr}, description={ "time": "Time", "sfr": "Averaged surfacic star formation rate", }, ), "rms_from_log": Rule( partial( self._from_log, ["time", "dt", "turb_rms", "turb_energy"], self._extract_rms_from_log, ), group="/series", unit={ "time": "unit_time", "dt": "unit_time", "turb_rms": U.none, "turb_energy": { "unit_length": 3, "unit_velocity": 2, "unit_density": 1, }, }, description={ "time": "Time", "dt": "Timestep", "turb_rms": "Computed turbulent RMS", "turb_energy": "Injected turbulent energy", }, ), "coarse_step_from_log": Rule( partial( self._from_log, [ "time", "step", "mcons", "econs", "epot", "ekin", "eint", "emag", "elapsed", "memory", ], self._extract_coarse_step_from_log, ), group="/series", unit={ "time": "unit_time", "step": U.none, "mcons": U.none, "econs": U.none, "epot": U.none, # TODO find unit "ekin": U.none, "eint": U.none, "emag": U.none, "elapsed": U.s, "memory": U.none, }, ), "fine_step_from_log": Rule( partial( self._from_log, ["time", "fine_step", "dt", "a", "mem_cells", "mem_parts"], self._extract_fine_step_from_log, ), group="/series", unit={ "time": "unit_time", "fine_step": U.none, "dt": "unit_time", "a": U.none, "mem_cells": U.none, "mem_parts": U.none, }, ), "stellar_from_log": Rule( partial( self._from_log, ["time", "mass", "lifetime", "id"], self._extract_stellar_from_log, ), group="/dataset", unit={ "time": "unit_time", "mass": "unit_mass", "lifetime": "unit_time", "id": U.none, }, ), "SN_momentum_from_log": Rule( partial( self._from_log, ["time", "SN_momentum"], self._extract_SN_Mom_from_log, ), group="/series", unit={ "time": "unit_time", "SN_momentum": {"unit_mass": 1, "unit_velocity": 1}, }, description={ "time": "Time", "SN_momentum": "Injected momentum", }, ), "turb_power": Rule( self._turb_power, group="/series/rms_from_log", unit={ "unit_length": 3, "unit_velocity": 2, "unit_density": 1, "unit_time": -1, }, description="Injected turbulent power", dependencies=["rms_from_log"], ), # Read from outputs "time": Rule( partial( self.time_series, partial(self.get_global, "/globals/time_num") ), group="/series", unit="unit_time", dependencies=["time_num"], ), "time_rho_prof": Rule( partial( self.time_series, partial(self.get_snap_value, "/profile/rho_prof") ), group="/series", dependencies={"time": None, "rho_prof": "__parent__"}, ), "time_coldens_pdf": Rule( partial( self.time_series, partial(self.get_snap_value, "/hist/pdf_coldens") ), group="/series", dependencies={"time": None, "pdf_coldens": "__parent__"}, ), "time_rho_pdf": Rule( partial( self.time_series, partial(self.get_snap_value, "/hist/rho_pdf") ), group="/series", dependencies={"time": None}, ), "time_pdf_slope_coldens": Rule( partial( self.time_series, partial( self.get_attr, "slope", node_name="/hist/pdf_coldens_z", ), ), group="/series", dependencies={"time": None, "fit_pdf_coldens": "z"}, ), "sbeta_onavg": Rule( partial(self._sbeta_onavg), group="/comp", dependencies={ "avg_time_coldens_pdf": "z", "nml": "cloud_params/beta_cool", }, ), # namelist "nml": Rule( lambda nml_key: self.compare( partial(self.get_nml, nml_key), use_num=False ), group="/comp", ), } self._gen_rule_time_global("mwa_sigma", "time_sigma", unit="unit_velocity") self._gen_rule_time_global("max_fluct_coldens") self._gen_rule_time_global( "mass", unit=self.info["unit_density"] * self.info["unit_length"] ** 3 ) self._gen_rule_time_global("mwa_B_int", unit="unit_mag") for name in [ "issfr", "time_sigma", "time_pdf_slope_coldens", "turb_power", "time_rho_prof", "time_coldens_pdf", "time_rho_pdf", ]: self._gen_rule_avg(name) self._gen_rule_avg("sinks_from_log", "mass_sink") self._gen_rule_avg("sinks_from_log", "nb_sink") self._gen_rule_avg("sfr_from_log", "sfr") self._gen_rule_avg("rms_from_log", "turb_rms") self._gen_rule_avg("rms_from_log", "turb_energy") super(StudyProcessor, self).def_rules()