changed to tuple index in Configuration

This commit is contained in:
Benoît Sierro
2021-09-09 12:37:58 +02:00
parent eb03496271
commit f724d1dcf6
12 changed files with 209 additions and 139 deletions

0
1 Normal file
View File

View File

@@ -3,13 +3,20 @@ import numpy as np
import scgenerator as sc import scgenerator as sc
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from pathlib import Path from pathlib import Path
from pprint import pprint
def _main():
print(os.getcwd())
for v_list, params in sc.Configuration("PM1550+PM2000D+PM1550/Pos30000.toml"):
print(params.fiber_map)
def main(): def main():
drr = os.getcwd() drr = os.getcwd()
os.chdir("/Users/benoitsierro/Nextcloud/PhD/Supercontinuum/PCF Simulations") os.chdir("/Users/benoitsierro/Nextcloud/PhD/Supercontinuum/PCF Simulations")
try: try:
sc.run_simulation("PM1550+PM2000D/Pos30000.toml") _main()
finally: finally:
os.chdir(drr) os.chdir(drr)

View File

@@ -4,5 +4,5 @@ from .physics import fiber, materials, pulse, simulate, units
from .physics.simulate import RK4IP, parallel_RK4IP, run_simulation from .physics.simulate import RK4IP, parallel_RK4IP, run_simulation
from .plotting import mean_values_plot, plot_spectrogram, propagation_plot, single_position_plot from .plotting import mean_values_plot, plot_spectrogram, propagation_plot, single_position_plot
from .spectra import Pulse, Spectrum from .spectra import Pulse, Spectrum
from .utils import Paths, load_toml from .utils import Paths, open_config
from .utils.parameter import Configuration, Parameters, PlotRange from .utils.parameter import Configuration, Parameters, PlotRange

View File

@@ -1124,16 +1124,3 @@ def capillary_loss(
nu_n = 0.5 * (chi_silica + 2) / np.sqrt(chi_silica) nu_n = 0.5 * (chi_silica + 2) / np.sqrt(chi_silica)
alpha[mask] = nu_n * (u_nm(*he_mode) * wl_for_disp[mask] / pipi) ** 2 * core_radius ** -3 alpha[mask] = nu_n * (u_nm(*he_mode) * wl_for_disp[mask] / pipi) ** 2 * core_radius ** -3
return alpha return alpha
if __name__ == "__main__":
w = np.linspace(0, 1, 4096)
c = np.arange(8)
import time
t = time.time()
for _ in range(10000):
dispersion_from_coefficients(w, c)
print((time.time() - t) / 10, "ms")

View File

@@ -1055,7 +1055,10 @@ def rin_curve(spectra: np.ndarray) -> np.ndarray:
def measure_field(t: np.ndarray, field: np.ndarray) -> Tuple[float, float, float]: def measure_field(t: np.ndarray, field: np.ndarray) -> Tuple[float, float, float]:
"""returns fwhm, peak_power, energy""" """returns fwhm, peak_power, energy"""
intensity = abs2(field) if np.iscomplexobj(field):
intensity = abs2(field)
else:
intensity = field
_, fwhm_lim, _, _ = find_lobe_limits(t, intensity) _, fwhm_lim, _, _ = find_lobe_limits(t, intensity)
fwhm = length(fwhm_lim) fwhm = length(fwhm_lim)
peak_power = intensity.max() peak_power = intensity.max()

View File

@@ -459,17 +459,19 @@ class Simulations:
self.configuration = configuration self.configuration = configuration
self.name = self.configuration.name self.name = self.configuration.final_path
self.sim_dir = self.configuration.final_sim_dir self.sim_dir = self.configuration.final_sim_dir
self.configuration.save_parameters() self.configuration.save_parameters()
self.sim_jobs_per_node = 1 self.sim_jobs_per_node = 1
def finished_and_complete(self): def finished_and_complete(self):
for sim in self.configuration.data_dirs: for sim in self.configuration.all_configs_dict.values():
for data_dir in sim: if (
if self.configuration.sim_status(data_dir)[0] != self.configuration.State.COMPLETE: self.configuration.sim_status(sim.output_path)[0]
return False != self.configuration.State.COMPLETE
):
return False
return True return True
def run(self): def run(self):
@@ -517,12 +519,14 @@ class SequencialSimulations(Simulations, priority=0):
def __init__(self, configuration: Configuration, task_id): def __init__(self, configuration: Configuration, task_id):
super().__init__(configuration, task_id=task_id) super().__init__(configuration, task_id=task_id)
self.pbars = utils.PBars( self.pbars = utils.PBars(
self.configuration.total_num_steps, "Simulating " + self.configuration.name, 1 self.configuration.total_num_steps, "Simulating " + self.configuration.final_path, 1
) )
self.configuration.skip_callback = lambda num: self.pbars.update(0, num) self.configuration.skip_callback = lambda num: self.pbars.update(0, num)
def new_sim(self, v_list_str: str, params: Parameters): def new_sim(self, v_list_str: str, params: Parameters):
self.logger.info(f"{self.configuration.name} : launching simulation with {v_list_str}") self.logger.info(
f"{self.configuration.final_path} : launching simulation with {v_list_str}"
)
SequentialRK4IP( SequentialRK4IP(
params, self.pbars, save_data=True, job_identifier=v_list_str, task_id=self.id params, self.pbars, save_data=True, job_identifier=v_list_str, task_id=self.id
).run() ).run()
@@ -558,7 +562,7 @@ class MultiProcSimulations(Simulations, priority=1):
self.p_worker = multiprocessing.Process( self.p_worker = multiprocessing.Process(
target=utils.progress_worker, target=utils.progress_worker,
args=( args=(
self.configuration.name, self.configuration.final_path,
self.sim_jobs_per_node, self.sim_jobs_per_node,
self.configuration.total_num_steps, self.configuration.total_num_steps,
self.progress_queue, self.progress_queue,
@@ -646,7 +650,7 @@ class RaySimulations(Simulations, priority=2):
self.num_submitted = 0 self.num_submitted = 0
self.rolling_id = 0 self.rolling_id = 0
self.p_actor = ray.remote(utils.ProgressBarActor).remote( self.p_actor = ray.remote(utils.ProgressBarActor).remote(
self.configuration.name, self.sim_jobs_total, self.configuration.total_num_steps self.configuration.final_path, self.sim_jobs_total, self.configuration.total_num_steps
) )
self.configuration.skip_callback = lambda num: ray.get(self.p_actor.update.remote(0, num)) self.configuration.skip_callback = lambda num: ray.get(self.p_actor.update.remote(0, num))
@@ -668,7 +672,9 @@ class RaySimulations(Simulations, priority=2):
) )
self.num_submitted += 1 self.num_submitted += 1
self.logger.info(f"{self.configuration.name} : launching simulation with {v_list_str}") self.logger.info(
f"{self.configuration.final_path} : launching simulation with {v_list_str}"
)
def collect_1_job(self): def collect_1_job(self):
ray.get(self.p_actor.update_pbars.remote()) ray.get(self.p_actor.update_pbars.remote())
@@ -707,7 +713,7 @@ def run_simulation(
final_name = env.get(env.OUTPUT_PATH) final_name = env.get(env.OUTPUT_PATH)
if final_name is None: if final_name is None:
final_name = config.name final_name = config.final_path
utils.merge(final_name, path_trees) utils.merge(final_name, path_trees)
try: try:
@@ -722,7 +728,7 @@ def new_simulation(
) -> Simulations: ) -> Simulations:
logger = get_logger(__name__) logger = get_logger(__name__)
task_id = random.randint(1e9, 1e12) task_id = random.randint(1e9, 1e12)
logger.info(f"running {configuration.name}") logger.info(f"running {configuration.final_path}")
return Simulations.new(configuration, task_id, method) return Simulations.new(configuration, task_id, method)

View File

@@ -259,6 +259,7 @@ def propagation_plot(
params: Parameters, params: Parameters,
ax: plt.Axes, ax: plt.Axes,
log: Union[int, float, bool, str] = "1D", log: Union[int, float, bool, str] = "1D",
renormalize: bool = False,
vmin: float = None, vmin: float = None,
vmax: float = None, vmax: float = None,
transpose: bool = False, transpose: bool = False,
@@ -295,6 +296,8 @@ def propagation_plot(
""" """
x_axis, y_axis, values = transform_2D_propagation(values, plt_range, params, log, skip) x_axis, y_axis, values = transform_2D_propagation(values, plt_range, params, log, skip)
if renormalize and log is False:
values = values / values.max()
if log is not False: if log is not False:
vmax = defaults["vmax"] if vmax is None else vmax vmax = defaults["vmax"] if vmax is None else vmax
vmin = defaults["vmin"] if vmin is None else vmin vmin = defaults["vmin"] if vmin is None else vmin

View File

@@ -1,6 +1,4 @@
import itertools
import os import os
from itertools import cycle
from pathlib import Path from pathlib import Path
from typing import Any, Iterable, Optional from typing import Any, Iterable, Optional
@@ -14,7 +12,7 @@ from ..const import PARAM_FN, PARAM_SEPARATOR
from ..physics import fiber, units from ..physics import fiber, units
from ..plotting import plot_setup from ..plotting import plot_setup
from ..spectra import Pulse from ..spectra import Pulse
from ..utils import auto_crop, load_toml, save_toml, translate_parameters from ..utils import auto_crop, open_config, save_toml, translate_parameters
from ..utils.parameter import ( from ..utils.parameter import (
Configuration, Configuration,
Parameters, Parameters,
@@ -259,7 +257,7 @@ def finish_plot(fig, legend_axes, all_labels, params):
def plot_helper(config_path: Path) -> Iterable[tuple[dict, list[str], Parameters]]: def plot_helper(config_path: Path) -> Iterable[tuple[dict, list[str], Parameters]]:
cc = cycler(color=[f"C{i}" for i in range(10)]) * cycler(ls=["-", "--"]) cc = cycler(color=[f"C{i}" for i in range(10)]) * cycler(ls=["-", "--"])
pseq = Configuration(load_toml(config_path)) pseq = Configuration(open_config(config_path))
for style, (variables, params) in zip(cc, pseq): for style, (variables, params) in zip(cc, pseq):
lbl = [pretty_format_value(name, value) for name, value in variables[1:-1]] lbl = [pretty_format_value(name, value) for name, value in variables[1:-1]]
yield style, lbl, params yield style, lbl, params
@@ -268,7 +266,7 @@ def plot_helper(config_path: Path) -> Iterable[tuple[dict, list[str], Parameters
def convert_params(params_file: os.PathLike): def convert_params(params_file: os.PathLike):
p = Path(params_file) p = Path(params_file)
if p.name == PARAM_FN: if p.name == PARAM_FN:
d = load_toml(params_file) d = open_config(params_file)
d = translate_parameters(d) d = translate_parameters(d)
save_toml(params_file, d) save_toml(params_file, d)
print(f"converted {p}") print(f"converted {p}")

View File

@@ -127,7 +127,7 @@ def main():
) )
config = Configuration(args.config) config = Configuration(args.config)
final_name = config.name final_name = config.final_path
sim_num = config.num_sim sim_num = config.num_sim
if args.command == "merge": if args.command == "merge":

View File

@@ -150,7 +150,9 @@ class Pulse(Sequence):
raise FileNotFoundError(f"Folder {self.path} does not exist") raise FileNotFoundError(f"Folder {self.path} does not exist")
self.params = Parameters.load(self.path / "params.toml") self.params = Parameters.load(self.path / "params.toml")
self.params.compute(["t", "l", "w_c", "w0", "z_targets"]) self.params.compute(["name", "t", "l", "w_c", "w0", "z_targets"])
if self.params.fiber_map is None:
self.params.fiber_map = {0.0: self.params.name}
try: try:
self.z = np.load(os.path.join(path, "z.npy")) self.z = np.load(os.path.join(path, "z.npy"))

View File

@@ -20,6 +20,8 @@ from string import printable as str_printable
from typing import Any, Callable, Generator, Iterable, MutableMapping, Sequence, TypeVar, Union from typing import Any, Callable, Generator, Iterable, MutableMapping, Sequence, TypeVar, Union
import numpy as np import numpy as np
from numpy.lib.arraysetops import isin
from numpy.lib.function_base import insert
import pkg_resources as pkg import pkg_resources as pkg
import toml import toml
from tqdm import tqdm from tqdm import tqdm
@@ -88,18 +90,21 @@ def load_previous_spectrum(prev_data_dir: str) -> np.ndarray:
return np.load(prev_data_dir / SPEC1_FN.format(num)) return np.load(prev_data_dir / SPEC1_FN.format(num))
def conform_toml_path(path: os.PathLike) -> Path: def conform_toml_path(path: os.PathLike) -> str:
path = Path(path) path: str = str(path)
if not path.name.lower().endswith(".toml"): if not path.lower().endswith(".toml"):
path = path.parent / (path.name + ".toml") path = path + ".toml"
return path return path
def load_toml(path: os.PathLike): def open_config(path: os.PathLike):
"""returns a dictionary parsed from the specified toml file""" """returns a dictionary parsed from the specified toml file
This also handle having a 'INCLUDE' argument that will fill
otherwise unspecified keys with what's in the INCLUDE file(s)"""
path = conform_toml_path(path) path = conform_toml_path(path)
with open(path, mode="r") as file: dico = resolve_loadfile_arg(load_toml(path))
dico = toml.load(file)
dico.setdefault("variable", {}) dico.setdefault("variable", {})
for key in {"simulation", "fiber", "gas", "pulse"} & dico.keys(): for key in {"simulation", "fiber", "gas", "pulse"} & dico.keys():
section = dico.pop(key, {}) section = dico.pop(key, {})
@@ -110,6 +115,35 @@ def load_toml(path: os.PathLike):
return dico return dico
def resolve_loadfile_arg(dico: dict[str, Any]) -> dict[str, Any]:
if (f_list := dico.pop("INCLUDE", None)) is not None:
if isinstance(f_list, str):
f_list = [f_list]
for to_load in f_list:
loaded = load_toml(to_load)
for k, v in loaded.items():
if k not in dico and k not in dico.get("variable", {}):
dico[k] = v
for k, v in dico.items():
if isinstance(v, MutableMapping):
dico[k] = resolve_loadfile_arg(v)
elif isinstance(v, Sequence):
for i, vv in enumerate(v):
if isinstance(vv, MutableMapping):
dico[k][i] = resolve_loadfile_arg(vv)
return dico
def load_toml(descr: str) -> dict[str, Any]:
if ":" in descr:
path, entry = descr.split(":", 1)
with open(path) as file:
return toml.load(file)[entry]
else:
with open(descr) as file:
return toml.load(file)
def save_toml(path: os.PathLike, dico): def save_toml(path: os.PathLike, dico):
"""saves a dictionary into a toml file""" """saves a dictionary into a toml file"""
path = conform_toml_path(path) path = conform_toml_path(path)
@@ -119,7 +153,7 @@ def save_toml(path: os.PathLike, dico):
def load_config_sequence(final_config_path: os.PathLike) -> tuple[list[dict[str, Any]], str]: def load_config_sequence(final_config_path: os.PathLike) -> tuple[list[dict[str, Any]], str]:
loaded_config = load_toml(final_config_path) loaded_config = open_config(final_config_path)
final_name = loaded_config.get("name") final_name = loaded_config.get("name")
fiber_list = loaded_config.pop("Fiber") fiber_list = loaded_config.pop("Fiber")
configs = [] configs = []
@@ -133,7 +167,7 @@ def load_config_sequence(final_config_path: os.PathLike) -> tuple[list[dict[str,
else: else:
configs.append(loaded_config) configs.append(loaded_config)
while "previous_config_file" in configs[0]: while "previous_config_file" in configs[0]:
configs.insert(0, load_toml(configs[0]["previous_config_file"])) configs.insert(0, open_config(configs[0]["previous_config_file"]))
configs[0].setdefault("variable", {}) configs[0].setdefault("variable", {})
for pre, nex in zip(configs[:-1], configs[1:]): for pre, nex in zip(configs[:-1], configs[1:]):
variable = nex.pop("variable", {}) variable = nex.pop("variable", {})
@@ -189,13 +223,9 @@ def load_material_dico(name: str) -> dict[str, Any]:
def update_appended_params(source: Path, destination: Path, z: Sequence): def update_appended_params(source: Path, destination: Path, z: Sequence):
z_num = len(z) z_num = len(z)
params = load_toml(source) params = open_config(source)
if "simulation" in params: params["z_num"] = z_num
params["simulation"]["z_num"] = z_num params["length"] = float(z[-1] - z[0])
params["fiber"]["length"] = float(z[-1] - z[0])
else:
params["z_num"] = z_num
params["length"] = float(z[-1] - z[0])
for p_name in ["recovery_data_dir", "prev_data_dir", "output_path"]: for p_name in ["recovery_data_dir", "prev_data_dir", "output_path"]:
if p_name in params: if p_name in params:
del params[p_name] del params[p_name]
@@ -230,7 +260,9 @@ def build_path_branch(data_dir: Path) -> tuple[Path, ...]:
if not data_dir.is_dir(): if not data_dir.is_dir():
return None return None
path_branch = [data_dir] path_branch = [data_dir]
while (prev_sim_path := load_toml(path_branch[-1] / PARAM_FN).get("prev_data_dir")) is not None: while (
prev_sim_path := open_config(path_branch[-1] / PARAM_FN).get("prev_data_dir")
) is not None:
p = Path(prev_sim_path).resolve() p = Path(prev_sim_path).resolve()
if not p.exists(): if not p.exists():
p = Path(*p.parts[-2:]).resolve() p = Path(*p.parts[-2:]).resolve()
@@ -345,7 +377,7 @@ def merge(destination: os.PathLike, path_trees: list[PathTree] = None):
conf, conf,
destination / f"initial_config_{i}.toml", destination / f"initial_config_{i}.toml",
) )
prev_z_num = load_toml(conf).get("z_num", prev_z_num) prev_z_num = open_config(conf).get("z_num", prev_z_num)
z_num += prev_z_num z_num += prev_z_num
pbars = PBars( pbars = PBars(

View File

@@ -14,7 +14,7 @@ from typing import Any, Callable, Generator, Iterable, Literal, Optional, TypeVa
import numpy as np import numpy as np
from .. import math, utils from .. import math, utils
from ..const import PARAM_SEPARATOR, __version__ from ..const import PARAM_FN, PARAM_SEPARATOR, __version__
from ..errors import EvaluatorError, NoDefaultError from ..errors import EvaluatorError, NoDefaultError
from ..logger import get_logger from ..logger import get_logger
from ..physics import fiber, materials, pulse, units from ..physics import fiber, materials, pulse, units
@@ -308,6 +308,10 @@ class Parameter:
return f"{num_str} {unit}" return f"{num_str} {unit}"
def fiber_map_converter(d: dict[str, str]) -> dict[float, str]:
return {float(k): v for k, v in d.items()}
@dataclass @dataclass
class Parameters: class Parameters:
""" """
@@ -421,11 +425,13 @@ class Parameters:
const_qty: np.ndarray = Parameter(type_checker(np.ndarray)) const_qty: np.ndarray = Parameter(type_checker(np.ndarray))
beta_func: Callable[[float], list[float]] = Parameter(func_validator) beta_func: Callable[[float], list[float]] = Parameter(func_validator)
gamma_func: Callable[[float], float] = Parameter(func_validator) gamma_func: Callable[[float], float] = Parameter(func_validator)
fiber_map: dict[float, str] = Parameter(type_checker(dict), converter=fiber_map_converter)
datetime: datetime_module.datetime = Parameter(type_checker(datetime_module.datetime)) datetime: datetime_module.datetime = Parameter(type_checker(datetime_module.datetime))
version: str = Parameter(string) version: str = Parameter(string)
def prepare_for_dump(self) -> dict[str, Any]: def prepare_for_dump(self) -> dict[str, Any]:
param = asdict(self) param = asdict(self)
param["fiber_map"] = {str(z): n for z, n in param.get("fiber_map", {}).items()}
param = Parameters.strip_params_dict(param) param = Parameters.strip_params_dict(param)
param["datetime"] = datetime_module.datetime.now() param["datetime"] = datetime_module.datetime.now()
param["version"] = __version__ param["version"] = __version__
@@ -448,7 +454,7 @@ class Parameters:
@classmethod @classmethod
def load(cls, path: os.PathLike) -> "Parameters": def load(cls, path: os.PathLike) -> "Parameters":
return cls(**utils.load_toml(path)) return cls(**utils.open_config(path))
@classmethod @classmethod
def load_and_compute(cls, path: os.PathLike) -> "Parameters": def load_and_compute(cls, path: os.PathLike) -> "Parameters":
@@ -756,8 +762,7 @@ class Configuration:
obj with the output path of the simulation saved in its output_path attribute. obj with the output path of the simulation saved in its output_path attribute.
""" """
configs: list[dict[str, Any]] master_configs: list[dict[str, Any]]
data_dirs: list[list[Path]]
sim_dirs: list[Path] sim_dirs: list[Path]
num_sim: int num_sim: int
repeat: int repeat: int
@@ -766,14 +771,20 @@ class Configuration:
worker_num: int worker_num: int
parallel: bool parallel: bool
overwrite: bool overwrite: bool
name: str final_path: str
all_required: list[list[tuple[list[tuple[str, Any]], dict[str, Any]]]] all_configs_dict: dict[tuple[tuple[int, ...], ...], "Configuration.__SimConfig"]
# | | | | | all_configs_list: list[list["Configuration.__SimConfig"]]
# | | | | param name and value
# | | | all variable parameters @dataclass(frozen=True)
# | | list of all variable parameters associated with the full config dict class __SimConfig:
# | list of all configs for 1 fiber vary_list: list[tuple[str, Any]]
# list of all fibers config: dict[str, Any]
output_path: Path
index: tuple[tuple[int, ...], ...]
@property
def sim_num(self) -> int:
return len(self.index)
class State(enum.Enum): class State(enum.Enum):
COMPLETE = enum.auto() COMPLETE = enum.auto()
@@ -793,46 +804,48 @@ class Configuration:
): ):
self.logger = get_logger(__name__) self.logger = get_logger(__name__)
self.configs, self.name = utils.load_config_sequence(final_config_path) self.master_configs, self.final_path = utils.load_config_sequence(final_config_path)
if self.name is None: if self.final_path is None:
self.name = Parameters.name.default self.final_path = Parameters.name.default
self.name = Path(self.final_path).name
self.z_num = 0 self.z_num = 0
self.total_num_steps = 0 self.total_num_steps = 0
self.sim_dirs = [] self.sim_dirs = []
self.overwrite = overwrite self.overwrite = overwrite
self.skip_callback = skip_callback self.skip_callback = skip_callback
self.worker_num = self.configs[0].get("worker_num", max(1, os.cpu_count() // 2)) self.worker_num = self.master_configs[0].get("worker_num", max(1, os.cpu_count() // 2))
self.repeat = self.configs[0].get("repeat", 1) self.repeat = self.master_configs[0].get("repeat", 1)
names = set() names = set()
for i, config in enumerate(self.configs): for i, config in enumerate(self.master_configs):
self.z_num += config["z_num"] self.z_num += config["z_num"]
config.setdefault("name", f"{Parameters.name.default} {i}") config.setdefault("name", f"{Parameters.name.default} {i}")
given_name = config["name"] given_name = config["name"]
i = 0 fn_i = 0
while config["name"] in names: while config["name"] in names:
config["name"] = given_name + f"_{i}" config["name"] = given_name + f"_{fn_i}"
i += 1 fn_i += 1
names.add(config["name"]) names.add(config["name"])
self.sim_dirs.append( self.sim_dirs.append(
utils.ensure_folder( utils.ensure_folder(
Path("__" + config["name"] + "__"), Path("_".join(["_", self.name, Path(config["name"]).name, "_"])),
mkdir=False, mkdir=False,
prevent_overwrite=not self.overwrite, prevent_overwrite=not self.overwrite,
) )
) )
self.__validate_variable(config) self.__validate_variable(config)
self.__compute_sim_dirs() self.__compute_sim_dirs()
[Evaluator.evaluate_default(req[0][1], check_only=True) for req in self.all_required] [Evaluator.evaluate_default(c[0].config, True) for c in self.all_configs_list]
self.num_sim = len(self.data_dirs[-1]) self.num_sim = len(self.all_configs_list[-1])
self.total_num_steps = sum( self.total_num_steps = sum(
config["z_num"] * len(self.data_dirs[i]) for i, config in enumerate(self.configs) config["z_num"] * len(self.all_configs_list[i])
for i, config in enumerate(self.master_configs)
) )
self.final_sim_dir = utils.ensure_folder( self.final_sim_dir = utils.ensure_folder(
Path(self.configs[-1]["name"]), mkdir=False, prevent_overwrite=not self.overwrite Path(self.master_configs[-1]["name"]), mkdir=False, prevent_overwrite=not self.overwrite
) )
self.parallel = self.configs[0].get("parallel", Parameters.parallel.default) self.parallel = self.master_configs[0].get("parallel", Parameters.parallel.default)
def __validate_variable(self, config: dict[str, Any]): def __validate_variable(self, config: dict[str, Any]):
for k, v in config.get("variable", {}).items(): for k, v in config.get("variable", {}).items():
@@ -844,14 +857,15 @@ class Configuration:
raise ValueError(f"variable parameter {k!r} must not be empty") raise ValueError(f"variable parameter {k!r} must not be empty")
def __compute_sim_dirs(self): def __compute_sim_dirs(self):
self.all_required = [] self.all_configs_dict = {}
self.data_dirs = [] self.all_configs_list = []
self.configs[0]["variable"]["num"] = list(range(self.configs[0].get("repeat", 1))) self.master_configs[0]["variable"]["num"] = list(
dp = DataPather([c["variable"] for c in self.configs]) range(self.master_configs[0].get("repeat", 1))
for i, conf in enumerate(self.configs): )
self.all_required.append([]) dp = DataPather([c["variable"] for c in self.master_configs])
self.data_dirs.append([]) for i, conf in enumerate(self.master_configs):
for prev_path, this_path, this_vary in dp.all_vary_list(i): self.all_configs_list.append([])
for sim_index, prev_path, this_path, this_vary in dp.all_vary_list(i):
this_conf = conf.copy() this_conf = conf.copy()
if i > 0: if i > 0:
prev_path = utils.ensure_folder( prev_path = utils.ensure_folder(
@@ -862,50 +876,56 @@ class Configuration:
this_path = utils.ensure_folder( this_path = utils.ensure_folder(
self.sim_dirs[i] / this_path, not self.overwrite, False self.sim_dirs[i] / this_path, not self.overwrite, False
) )
self.data_dirs[i].append(this_path)
this_conf.pop("variable") this_conf.pop("variable")
conf_to_use = {k: v for k, v in this_vary if k != "num"} | this_conf conf_to_use = {k: v for k, v in this_vary if k != "num"} | this_conf
self.all_required[i].append((this_vary, conf_to_use)) self.all_configs_dict[sim_index] = self.__SimConfig(
this_vary, conf_to_use, this_path, sim_index
)
self.all_configs_list[i].append(self.all_configs_dict[sim_index])
def __iter__(self) -> Generator[tuple[list[tuple[str, Any]], Parameters], None, None]: def __iter__(self) -> Generator[tuple[list[tuple[str, Any]], Parameters], None, None]:
for sim_paths, fiber in zip(self.data_dirs, self.all_required): for i, sim_config_list in enumerate(self.all_configs_list):
for variable_list, data_dir, params in self.__iter_1_sim(sim_paths, fiber): for sim_config, params in self.__iter_1_sim(sim_config_list):
params.output_path = str(data_dir) fiber_map = []
yield variable_list, params for j in range(i + 1):
this_conf = self.all_configs_dict[sim_config.index[: j + 1]].config
if j > 0:
prev_conf = self.all_configs_dict[sim_config.index[:j]].config
length = prev_conf["length"] + fiber_map[j - 1][0]
else:
length = 0.0
fiber_map.append((length, this_conf["name"]))
params.output_path = str(sim_config.output_path)
params.fiber_map = dict(fiber_map)
yield sim_config.vary_list, params
def __iter_1_sim( def __iter_1_sim(
self, sim_paths: list[Path], fiber: list[tuple[list[tuple[str, Any]], dict[str, Any]]] self, configs: list["Configuration.__SimConfig"]
) -> Generator[tuple[list[tuple[str, Any]], Path, Parameters], None, None]: ) -> Generator[tuple["Configuration.__SimConfig", Parameters], None, None]:
"""iterates through the parameters of only one fiber. It takes care of recovery partially completed """iterates through the parameters of only one fiber. It takes care of recovering partially
simulations, skipping complete ones and waiting for the previous fiber to finish completed simulations, skipping complete ones and waiting for the previous fiber to finish
Parameters Parameters
---------- ----------
sim_paths : list[Path] configs : list[__SimConfig]
output_paths of the desired simulations list of configuration obj
fiber : list[tuple[list[tuple[str, Any]], dict[str, Any]]]
list of variable list and config dict as yielded by variable_iterator
Yields Yields
------- -------
list[tuple[str, Any]] __SimConfig
list of variable paramters configuration obj
Path
desired output path
Parameters Parameters
computed Parameters obj computed Parameters obj
""" """
sim_dict: dict[Path, tuple[list[tuple[str, Any]], dict[str, Any]]] = dict( sim_dict: dict[Path, Configuration.__SimConfig] = {s.output_path: s for s in configs}
zip(sim_paths, fiber)
)
while len(sim_dict) > 0: while len(sim_dict) > 0:
for data_dir, (variable_list, config_dict) in sim_dict.items(): for data_dir, sim_config in sim_dict.items():
task, config_dict = self.__decide(data_dir, config_dict) task, config_dict = self.__decide(sim_config)
if task == self.Action.RUN: if task == self.Action.RUN:
sim_dict.pop(data_dir) sim_dict.pop(data_dir)
p = Parameters(**config_dict) p = Parameters(**config_dict)
p.compute() p.compute()
yield variable_list, data_dir, p yield sim_config, p
if "recovery_last_stored" in config_dict and self.skip_callback is not None: if "recovery_last_stored" in config_dict and self.skip_callback is not None:
self.skip_callback(config_dict["recovery_last_stored"]) self.skip_callback(config_dict["recovery_last_stored"])
break break
@@ -920,16 +940,13 @@ class Configuration:
time.sleep(1) time.sleep(1)
def __decide( def __decide(
self, data_dir: Path, config_dict: dict[str, Any] self, sim_config: "Configuration.__SimConfig"
) -> tuple["Configuration.Action", dict[str, Any]]: ) -> tuple["Configuration.Action", dict[str, Any]]:
"""decide what to to with a particular simulation """decide what to to with a particular simulation
Parameters Parameters
---------- ----------
data_dir : Path sim_config : __SimConfig
path to the output of the simulation
config_dict : dict[str, Any]
configuration of the simulation
Returns Returns
------- -------
@@ -939,20 +956,20 @@ class Configuration:
config dictionary. The only key possibly modified is 'prev_data_dir', which config dictionary. The only key possibly modified is 'prev_data_dir', which
gets set if the simulation is partially completed gets set if the simulation is partially completed
""" """
out_status, num = self.sim_status(data_dir, config_dict) out_status, num = self.sim_status(sim_config.output_path, sim_config.config)
if out_status == self.State.COMPLETE: if out_status == self.State.COMPLETE:
return self.Action.SKIP, config_dict return self.Action.SKIP, sim_config.config
elif out_status == self.State.PARTIAL: elif out_status == self.State.PARTIAL:
config_dict["recovery_data_dir"] = str(data_dir) sim_config.config["recovery_data_dir"] = str(sim_config.output_path)
config_dict["recovery_last_stored"] = num sim_config.config["recovery_last_stored"] = num
return self.Action.RUN, config_dict return self.Action.RUN, sim_config.config
if "prev_data_dir" in config_dict: if "prev_data_dir" in sim_config.config:
prev_data_path = Path(config_dict["prev_data_dir"]) prev_data_path = Path(sim_config.config["prev_data_dir"])
prev_status, _ = self.sim_status(prev_data_path) prev_status, _ = self.sim_status(prev_data_path)
if prev_status in {self.State.PARTIAL, self.State.ABSENT}: if prev_status in {self.State.PARTIAL, self.State.ABSENT}:
return self.Action.WAIT, config_dict return self.Action.WAIT, sim_config.config
return self.Action.RUN, config_dict return self.Action.RUN, sim_config.config
def sim_status( def sim_status(
self, data_dir: Path, config_dict: dict[str, Any] = None self, data_dir: Path, config_dict: dict[str, Any] = None
@@ -975,9 +992,9 @@ class Configuration:
num = utils.find_last_spectrum_num(data_dir) num = utils.find_last_spectrum_num(data_dir)
if config_dict is None: if config_dict is None:
try: try:
config_dict = utils.load_toml(data_dir / "params.toml") config_dict = utils.open_config(data_dir / PARAM_FN)
except FileNotFoundError: except FileNotFoundError:
self.logger.warning(f"did not find 'params.toml' in {data_dir}") self.logger.warning(f"did not find {PARAM_FN!r} in {data_dir}")
return self.State.ABSENT, 0 return self.State.ABSENT, 0
if num == config_dict["z_num"] - 1: if num == config_dict["z_num"] - 1:
return self.State.COMPLETE, num return self.State.COMPLETE, num
@@ -989,18 +1006,23 @@ class Configuration:
raise ValueError(f"Too many spectra in {data_dir}") raise ValueError(f"Too many spectra in {data_dir}")
def save_parameters(self): def save_parameters(self):
for config, sim_dir in zip(self.configs, self.sim_dirs): for config, sim_dir in zip(self.master_configs, self.sim_dirs):
os.makedirs(sim_dir, exist_ok=True) os.makedirs(sim_dir, exist_ok=True)
utils.save_toml(sim_dir / f"initial_config.toml", config) utils.save_toml(sim_dir / f"initial_config.toml", config)
@property
def first(self) -> Parameters:
for _, param in self:
return param
class DataPather: class DataPather:
def __init__(self, dl: list[dict[str, Any]]): def __init__(self, dl: list[dict[str, Any]]):
self.dict_list = dl self.dict_list = dl
self.n = len(self.dict_list)
self.final_list = list(self.dico_iterator(self.n))
def dico_iterator(self, index: int) -> Generator[list[list[tuple[str, Any]]], None, None]: def dico_iterator(
self, index: int
) -> Generator[tuple[tuple[tuple[int, ...]], list[list[tuple[str, Any]]]], None, None]:
"""iterates through every possible combination of a list of dict of lists """iterates through every possible combination of a list of dict of lists
Parameters Parameters
@@ -1034,12 +1056,14 @@ class DataPather:
for r in itertools.product(*ranges): for r in itertools.product(*ranges):
flat = [(d_tem_list[i][0], d_tem_list[i][1][j]) for i, j in enumerate(r)] flat = [(d_tem_list[i][0], d_tem_list[i][1][j]) for i, j in enumerate(r)]
pos = tuple(r)
out = [flat[left:right] for left, right in zip(dict_pos[:-1], dict_pos[1:])] out = [flat[left:right] for left, right in zip(dict_pos[:-1], dict_pos[1:])]
yield out pos = tuple(pos[left:right] for left, right in zip(dict_pos[:-1], dict_pos[1:]))
yield pos, out
def all_vary_list(self, index): def all_vary_list(self, index):
for l in self.dico_iterator(index): for sim_index, l in self.dico_iterator(index):
unique_vary = [] unique_vary: list[tuple[str, Any]] = []
for ll in l[: index + 1]: for ll in l[: index + 1]:
for pname, pval in ll: for pname, pval in ll:
for i, (pn, _) in enumerate(unique_vary): for i, (pn, _) in enumerate(unique_vary):
@@ -1047,9 +1071,12 @@ class DataPather:
del unique_vary[i] del unique_vary[i]
break break
unique_vary.append((pname, pval)) unique_vary.append((pname, pval))
yield format_variable_list(reduce_all_variable(l[:index])), format_variable_list( yield sim_index, format_variable_list(
reduce_all_variable(l) reduce_all_variable(l[:index])
), unique_vary ), format_variable_list(reduce_all_variable(l)), unique_vary
def __repr__(self):
return f"DataPather([{', '.join(repr(d) for d in self.dict_list)}])"
@dataclass @dataclass
@@ -1065,6 +1092,11 @@ class PlotRange:
def sort_axis(self, axis: np.ndarray) -> tuple[np.ndarray, np.ndarray, tuple[float, float]]: def sort_axis(self, axis: np.ndarray) -> tuple[np.ndarray, np.ndarray, tuple[float, float]]:
return sort_axis(axis, self) return sort_axis(axis, self)
def __iter__(self):
yield self.left
yield self.right
yield self.unit.__name__
def sort_axis( def sort_axis(
axis: np.ndarray, plt_range: PlotRange axis: np.ndarray, plt_range: PlotRange