Big upgrade

This commit is contained in:
Benoît Sierro
2021-05-27 11:05:11 +02:00
parent b94d8ed3fb
commit 9aabcbe1d4
49 changed files with 488 additions and 277 deletions

View File

@@ -16,8 +16,28 @@ spectra, params = load_sim_data("varyTechNoise100kW_sim_data")
# Configuration # Configuration
You can load parameters by simpling passing the path to a toml file to the appropriate simulation function. Each possible key of this dictionary is described below. Every value must be given in standard SI units (m, s, W, J, ...) You can load parameters by simply passing the path to a toml file to the appropriate simulation function. Each possible key of this dictionary is described below. Every value must be given in standard SI units (m, s, W, J, ...)
The configuration file can have a ```name``` parameter at the root and must otherwise contain the following sections with the specified parameters The configuration file can have a ```name``` parameter at the root and must otherwise contain the following sections with the specified parameters. Every section ("fiber", "gas", "pulse", "simulation") can have a "variable" subsection where parameters are specified in a list INSTEAD of being specified as a single value in the main section. This has the effect of running one simulation per value in the list. If many parameters are variable, all possible combinations are ran.
Examples :
```
[fiber]
n2 = 2.2e-20
```
a single simulation is ran with this value
```
[fiber.variable]
n2 = [2.1e-20, 2.4e-20, 2.6e-20]
```
3 simulations are ran, one for each value
```
[fiber]
n2 = 2.2e-20
[fiber.variable]
n2 = [2.1e-20, 2.4e-20, 2.6e-20]
```
NOT ALLOWED
note : internally, another structure with a flattened dictionary is used note : internally, another structure with a flattened dictionary is used
@@ -29,6 +49,11 @@ If you already know the Taylor coefficients corresponding to the expansion of th
beta: list-like beta: list-like
list of Taylor coefficients for the beta_2 function list of Taylor coefficients for the beta_2 function
If you already have a dispersion curve, you can convert it to a npz file with the wavelength (key : 'wavelength') in m and the D parameter (key : 'dispersion') in s/m/m. You the refer to this file as
dispersion_file : str
path to the npz dispersion file
else, you can choose a mathematical fiber model else, you can choose a mathematical fiber model
@@ -81,7 +106,7 @@ capillary_thickness : float
capillary_spacing : float, optional if d is specified capillary_spacing : float, optional if d is specified
spacing between the capillary spacing between the capillary
capillray_resonance_strengths : list, optional capillary_resonance_strengths : list, optional
list of resonance strengths. Default is [] list of resonance strengths. Default is []
capillary_nested : int, optional capillary_nested : int, optional
@@ -93,14 +118,20 @@ capillary_nested : int, optional
gamma: float, optional unless beta is directly provided gamma: float, optional unless beta is directly provided
nonlinear parameter in m^2 / W. Will overwrite any computed gamma parameter. nonlinear parameter in m^2 / W. Will overwrite any computed gamma parameter.
effective_mode_diameter : float, optional
effective mode field diameter in m
n2 : float, optional
non linear refractive index
A_eff : float, optional
effective mode field area
length: float, optional length: float, optional
length of the fiber in m. default : 1 length of the fiber in m. default : 1
fiber_id : int
in case multiple fibers are chained together, indicates the index of this particular fiber, default : 0
input_transmission : float input_transmission : float
number between 0 and 1 indicating how much light enters the fiber, default : 1 number between 0 and 1 indicating how much light enters the fiber, useful when chaining many fibers together, default : 1
## Gas parameters ## Gas parameters
@@ -124,11 +155,17 @@ plasma_density: float
wavelength: float wavelength: float
pump wavelength in m pump wavelength in m
To specify the initial pulse shape, either use one of 2 in (power, energy) together with one of 2 in (width, t0), or use soliton_num together with one of 4 in (power, energy, width, t0) To specify the initial pulse properties, either use one of 3 in (peak_power, energy, mean_power) together with one of 2 in (width, t0), or use soliton_num together with one of 5 in (peak_power, mean_power, energy, width, t0)
power: float peak_power : float
peak power in W peak power in W
mean_power : float
mean power of the pulse train in W. if specified, repetition_rate must also be specified
repetition_rate : float
repetition rate of the pulse train in Hz
energy: float energy: float
total pulse energy in J total pulse energy in J
@@ -138,11 +175,14 @@ width: float
t0: float t0: float
pulse width parameter pulse width parameter
solition_num: float soliton_num: float
soliton number soliton number
### optional ### optional
field_file : str
if you have an initial field to use, convert it to a npz file with time (key : 'time') in s and electric field (key : 'field') in sqrt(W) (can be complex). You the use it with this config key. You can then scale it by settings any 1 of mean_power, energy and peak_power (priority is in this order)
quantum_noise: bool quantum_noise: bool
whether or not one-photon-per-mode quantum noise is activated. default : False whether or not one-photon-per-mode quantum noise is activated. default : False

View File

@@ -2,5 +2,5 @@ from .initialize import compute_init_parameters
from .io import Paths, load_toml from .io import Paths, load_toml
from .math import abs2, argclosest, span from .math import abs2, argclosest, span
from .physics import fiber, materials, pulse, simulate, units from .physics import fiber, materials, pulse, simulate, units
from .physics.simulate import RK4IP, new_simulations, resume_simulations from .physics.simulate import RK4IP, new_simulation, resume_simulations
from .plotting import plot_avg, plot_results_1D, plot_results_2D, plot_spectrogram from .plotting import plot_avg, plot_results_1D, plot_results_2D, plot_spectrogram

View File

@@ -6,7 +6,7 @@ import sys
import ray import ray
from scgenerator import initialize from scgenerator import initialize
from ..physics.simulate import new_simulations, resume_simulations, SequencialSimulations from ..physics.simulate import run_simulation_sequence, resume_simulations, SequencialSimulations
from .. import io from .. import io
@@ -37,11 +37,14 @@ def create_parser():
run_parser.add_argument("configs", help="path(s) to the toml configuration file(s)", nargs="+") run_parser.add_argument("configs", help="path(s) to the toml configuration file(s)", nargs="+")
run_parser.add_argument( run_parser.add_argument(
"appendto", "--append-to",
"-a",
help="optional directory where a compatible simulation has already been ran", help="optional directory where a compatible simulation has already been ran",
nargs="?",
default=None, default=None,
) )
run_parser.add_argument(
"--output-name", "--o", help="path to the final output folder", default=None
)
run_parser.set_defaults(func=run_sim) run_parser.set_defaults(func=run_sim)
resume_parser = subparsers.add_parser("resume", help="resume a simulation") resume_parser = subparsers.add_parser("resume", help="resume a simulation")
@@ -53,11 +56,10 @@ def create_parser():
merge_parser = subparsers.add_parser("merge", help="merge simulation results") merge_parser = subparsers.add_parser("merge", help="merge simulation results")
merge_parser.add_argument( merge_parser.add_argument(
"paths", "path", help="path to the final simulation folder containing 'initial_config.toml'"
nargs="+", )
help="path(s) to simulation folder(s) containing 'initial_config.toml'. If more " merge_parser.add_argument(
"than one path is given, simulations are appended to each other as if they're " "--output-name", "--o", help="path to the final output folder", default=None
"physically the continuation of the previous one.",
) )
merge_parser.set_defaults(func=merge) merge_parser.set_defaults(func=merge)
@@ -73,29 +75,11 @@ def main():
def run_sim(args): def run_sim(args):
method = prep_ray(args) method = prep_ray(args)
configs = args.configs.copy() run_simulation_sequence(*args.configs, method=method, final_name=args.output_name)
first_config = configs.pop(0)
if args.appendto is None:
sim = new_simulations(first_config, method=method)
else:
sim = new_simulations(
first_config, prev_data_folder=args.appendto, method=method, initial=False
)
sim.run()
data_folders = [sim.data_folder]
for config in configs:
print("launching", config)
sim = new_simulations(
config, prev_data_folder=data_folders[-1], method=method, initial=False
)
sim.run()
data_folders.append(sim.data_folder)
io.merge(data_folders)
def merge(args): def merge(args):
io.merge(args.paths) io.append_and_merge(args.path, args.output_name)
def prep_ray(args): def prep_ray(args):

View File

@@ -1,13 +1,31 @@
import numpy as np import numpy as np
def pbar_format(worker_id: int):
if worker_id == 0:
return dict(
position=0,
bar_format="{l_bar}{bar}" "|[{elapsed}<{remaining}, " "{rate_fmt}{postfix}]",
)
else:
return dict(
total=1,
desc=f"Worker {worker_id}",
position=worker_id,
bar_format="{l_bar}{bar}" "|[{elapsed}<{remaining}, " "{rate_fmt}{postfix}]",
)
#####
def in_range_excl(func, r): def in_range_excl(func, r):
def _in_range(n): def _in_range(n):
if not func(n): if not func(n):
return False return False
return n > r[0] and n < r[1] return n > r[0] and n < r[1]
_in_range.__doc__ = func.__doc__ + f" between {r[0]} and {r[1]}" _in_range.__doc__ = func.__doc__ + f" between {r[0]} and {r[1]} (exclusive) "
return _in_range return _in_range
@@ -17,7 +35,7 @@ def in_range_incl(func, r):
return False return False
return n >= r[0] and n <= r[1] return n >= r[0] and n <= r[1]
_in_range.__doc__ = func.__doc__ + f" between {r[0]} and {r[1]}" _in_range.__doc__ = func.__doc__ + f" between {r[0]} and {r[1]} (inclusive)"
return _in_range return _in_range
@@ -122,7 +140,7 @@ valid_param_types = dict(
beta=beta, beta=beta,
dispersion_file=lambda s: isinstance(s, str), dispersion_file=lambda s: isinstance(s, str),
model=string(["pcf", "marcatili", "marcatili_adjusted", "hasan", "custom"]), model=string(["pcf", "marcatili", "marcatili_adjusted", "hasan", "custom"]),
length=num, length=in_range_excl(num, (0, 1e9)),
capillary_num=integer, capillary_num=integer,
capillary_outer_d=in_range_excl(num, (0, 1e-3)), capillary_outer_d=in_range_excl(num, (0, 1e-3)),
capillary_thickness=in_range_excl(num, (0, 1e-3)), capillary_thickness=in_range_excl(num, (0, 1e-3)),
@@ -139,7 +157,9 @@ valid_param_types = dict(
pulse=dict( pulse=dict(
field_0=field_0, field_0=field_0,
field_file=lambda s: isinstance(s, str), field_file=lambda s: isinstance(s, str),
power=num, repetition_rate=num,
peak_power=num,
mean_power=num,
energy=num, energy=num,
soliton_num=num, soliton_num=num,
quantum_noise=boolean, quantum_noise=boolean,
@@ -201,7 +221,9 @@ valid_variable = dict(
], ],
gas=["pressure", "temperature", "gas_name", "plasma_density"], gas=["pressure", "temperature", "gas_name", "plasma_density"],
pulse=[ pulse=[
"power", "peak_power",
"mean_power",
"energy",
"quantum_noise", "quantum_noise",
"shape", "shape",
"wavelength", "wavelength",

View File

@@ -50,5 +50,5 @@ done
############################################################################################## ##############################################################################################
#### call your code below #### call your code below
scgenerator run {config} scgenerator run {configs_list}
exit exit

View File

@@ -18,10 +18,10 @@ default_parameters = dict(
quantum_noise=False, quantum_noise=False,
intensity_noise=0, intensity_noise=0,
shape="gaussian", shape="gaussian",
frep=80e6, frep=40e6,
behaviors=["spm", "ss"], behaviors=["spm", "ss"],
raman_type="agrawal", raman_type="agrawal",
parallel=False, parallel=True,
repeat=1, repeat=1,
tolerated_error=1e-11, tolerated_error=1e-11,
lower_wavelength_interp_limit=300e-9, lower_wavelength_interp_limit=300e-9,

View File

@@ -4,7 +4,6 @@ from typing import Any, Dict, Iterator, List, Set, Tuple
import numpy as np import numpy as np
from numpy import pi from numpy import pi
from numpy.core.numeric import full
from scipy.interpolate.interpolate import interp1d from scipy.interpolate.interpolate import interp1d
from tqdm import tqdm from tqdm import tqdm
from pathlib import Path from pathlib import Path
@@ -15,7 +14,7 @@ from .errors import *
from .logger import get_logger from .logger import get_logger
from .math import length, power_fact from .math import length, power_fact
from .physics import fiber, pulse, units from .physics import fiber, pulse, units
from .utils import count_variations, required_simulations from .utils import count_variations, override_config, required_simulations
class ParamSequence(Mapping): class ParamSequence(Mapping):
@@ -66,7 +65,7 @@ class ContinuationParamSequence(ParamSequence):
for variable_list, _ in required_simulations(init_config) for variable_list, _ in required_simulations(init_config)
] ]
new_config = utils.override_config(init_config, new_config) new_config = utils.override_config(new_config, init_config)
super().__init__(new_config) super().__init__(new_config)
def __iter__(self) -> Iterator[Tuple[List[Tuple[str, Any]], Dict[str, Any]]]: def __iter__(self) -> Iterator[Tuple[List[Tuple[str, Any]], Dict[str, Any]]]:
@@ -75,7 +74,6 @@ class ContinuationParamSequence(ParamSequence):
for variable_list, full_config in required_simulations(self.config): for variable_list, full_config in required_simulations(self.config):
prev_sim_folder = self.find_prev_data_folder(variable_list) prev_sim_folder = self.find_prev_data_folder(variable_list)
full_config["prev_data_dir"] = str(prev_sim_folder.resolve()) full_config["prev_data_dir"] = str(prev_sim_folder.resolve())
yield variable_list, compute_subsequent_paramters(prev_sim_folder, full_config) yield variable_list, compute_subsequent_paramters(prev_sim_folder, full_config)
def find_prev_data_folder(self, new_variable_list: List[Tuple[str, Any]]) -> Path: def find_prev_data_folder(self, new_variable_list: List[Tuple[str, Any]]) -> Path:
@@ -170,6 +168,28 @@ def validate(config: dict) -> dict:
return _ensure_consistency(config) return _ensure_consistency(config)
def validate_config_sequence(*configs: os.PathLike) -> Dict[str, Any]:
"""validates a sequence of configs where all but the first one may have
parameters missing
Parameters
----------
configs : os.PathLike
sequence of paths to toml config files
Returns
-------
Dict[str, Any]
the final config as would be simulated, but of course missing input fields in the middle
"""
previous = None
for config in configs:
dico = io.load_toml(config)
previous = override_config(dico, previous)
validate(previous)
return previous
def wspace(t, t_num=0): def wspace(t, t_num=0):
"""frequency array such that x(t) <-> np.fft(x)(w) """frequency array such that x(t) <-> np.fft(x)(w)
Parameters Parameters
@@ -312,15 +332,13 @@ def _ensure_consistency_fiber(fiber: Dict[str, Any]):
When at least one required parameter with no default is missing When at least one required parameter with no default is missing
""" """
if _contains(fiber, "beta") and not ( if _contains(fiber, "beta"):
_contains(fiber, "n2") and _contains(fiber, "effective_mode_diameter") if not (_contains(fiber, "A_eff") or _contains(fiber, "effective_mode_diameter")):
):
fiber = defaults.get(fiber, "gamma", specified_parameters=["beta"]) fiber = defaults.get(fiber, "gamma", specified_parameters=["beta"])
fiber.setdefault("model", "custom") fiber.setdefault("model", "custom")
elif _contains(fiber, "dispersion_file") and not ( elif _contains(fiber, "dispersion_file"):
_contains(fiber, "n2") and _contains(fiber, "effective_mode_diameter") if not (_contains(fiber, "A_eff") or _contains(fiber, "effective_mode_diameter")):
):
fiber = defaults.get(fiber, "gamma", specified_parameters=["dispersion_file"]) fiber = defaults.get(fiber, "gamma", specified_parameters=["dispersion_file"])
fiber.setdefault("model", "custom") fiber.setdefault("model", "custom")
@@ -400,12 +418,17 @@ def _ensure_consistency_pulse(pulse):
if _contains(pulse, "soliton_num"): if _contains(pulse, "soliton_num"):
pulse = defaults.get_multiple( pulse = defaults.get_multiple(
pulse, ["power", "energy", "width", "t0"], 1, specified_parameters=["soliton_num"] pulse,
["peak_power", "mean_power", "energy", "width", "t0"],
1,
specified_parameters=["soliton_num"],
) )
else: else:
pulse = defaults.get_multiple(pulse, ["t0", "width"], 1) pulse = defaults.get_multiple(pulse, ["t0", "width"], 1)
pulse = defaults.get_multiple(pulse, ["power", "energy"], 1) pulse = defaults.get_multiple(pulse, ["peak_power", "energy", "mean_power"], 1)
if _contains(pulse, "mean_power"):
pulse = defaults.get(pulse, "repetition_rate", specified_parameters=["mean_power"])
return pulse return pulse
@@ -565,6 +588,9 @@ def compute_init_parameters(config: Dict[str, Any]) -> Dict[str, Any]:
params["hr_w"] = fiber.delayed_raman_w(params["t"], params["dt"], params["raman_type"]) params["hr_w"] = fiber.delayed_raman_w(params["t"], params["dt"], params["raman_type"])
# PULSE # PULSE
if "mean_power" in params:
params["energy"] = params["mean_power"] / params["repetition_rate"]
if "field_file" in params: if "field_file" in params:
field_data = np.load(params["field_file"]) field_data = np.load(params["field_file"])
field_interp = interp1d( field_interp = interp1d(
@@ -581,7 +607,7 @@ def compute_init_parameters(config: Dict[str, Any]) -> Dict[str, Any]:
logger.info(f"computed initial N = {params['soliton_num']:.3g}") logger.info(f"computed initial N = {params['soliton_num']:.3g}")
params["L_D"] = params["t0"] ** 2 / abs(params["beta"][0]) params["L_D"] = params["t0"] ** 2 / abs(params["beta"][0])
params["L_NL"] = 1 / (params["gamma"] * params["power"]) if params["gamma"] else np.inf params["L_NL"] = 1 / (params["gamma"] * params["peak_power"]) if params["gamma"] else np.inf
params["L_sol"] = pi / 2 * params["L_D"] params["L_sol"] = pi / 2 * params["L_D"]
# Technical noise # Technical noise
@@ -589,7 +615,7 @@ def compute_init_parameters(config: Dict[str, Any]) -> Dict[str, Any]:
params = _technical_noise(params) params = _technical_noise(params)
params["field_0"] = pulse.initial_field( params["field_0"] = pulse.initial_field(
params["t"], params["shape"], params["t0"], params["power"] params["t"], params["shape"], params["t0"], params["peak_power"]
) )
if params["quantum_noise"]: if params["quantum_noise"]:
@@ -605,17 +631,22 @@ def compute_init_parameters(config: Dict[str, Any]) -> Dict[str, Any]:
def compute_subsequent_paramters(sim_folder: str, config: Dict[str, Any]) -> Dict[str, Any]: def compute_subsequent_paramters(sim_folder: str, config: Dict[str, Any]) -> Dict[str, Any]:
params = compute_init_parameters(config) params = compute_init_parameters(config)
params["spec_0"] = io.load_last_spectrum(sim_folder)[1] spec = io.load_last_spectrum(sim_folder)[1]
params["field_0"] = np.fft.ifft(params["spec_0"]) * params["input_transmission"] params["field_0"] = np.fft.ifft(spec) * params["input_transmission"]
params["spec_0"] = np.fft.fft(params["field_0"])
return params return params
def _comform_custom_field(params): def _comform_custom_field(params):
params["field_0"] = params["field_0"] * pulse.modify_field_ratio( params["field_0"] = params["field_0"] * pulse.modify_field_ratio(
params["field_o"], params.get("power"), params.get("intensity_noise") params["t"],
params["field_0"],
params.get("peak_power"),
params.get("energy"),
params.get("intensity_noise"),
) )
params["width"], params["power"], params["energy"] = pulse.measure_field( params["width"], params["peak_power"], params["energy"] = pulse.measure_field(
params["t"], params["field_0"] params["t"], params["field_0"]
) )
return params return params
@@ -625,14 +656,14 @@ def _update_pulse_parameters(params):
( (
params["width"], params["width"],
params["t0"], params["t0"],
params["power"], params["peak_power"],
params["energy"], params["energy"],
params["soliton_num"], params["soliton_num"],
) = pulse.conform_pulse_params( ) = pulse.conform_pulse_params(
shape=params["shape"], shape=params["shape"],
width=params.get("width", None), width=params.get("width", None),
t0=params.get("t0", None), t0=params.get("t0", None),
power=params.get("power", None), peak_power=params.get("peak_power", None),
energy=params.get("energy", None), energy=params.get("energy", None),
gamma=params["gamma"], gamma=params["gamma"],
beta2=params["beta"][0], beta2=params["beta"][0],
@@ -658,7 +689,7 @@ def _technical_noise(params):
if params["intensity_noise"] > 0: if params["intensity_noise"] > 0:
logger.info(f"intensity noise of {params['intensity_noise']}") logger.info(f"intensity noise of {params['intensity_noise']}")
delta_int, delta_T0 = pulse.technical_noise(params["intensity_noise"]) delta_int, delta_T0 = pulse.technical_noise(params["intensity_noise"])
params["power"] *= delta_int params["peak_power"] *= delta_int
params["t0"] *= delta_T0 params["t0"] *= delta_T0
params["width"] *= delta_T0 params["width"] *= delta_T0
params = _update_pulse_parameters(params) params = _update_pulse_parameters(params)

View File

@@ -5,10 +5,8 @@ from glob import glob
from typing import Any, Dict, Iterable, List, Tuple, Union from typing import Any, Dict, Iterable, List, Tuple, Union
import numpy as np import numpy as np
from numpy.lib import delete
import pkg_resources as pkg import pkg_resources as pkg
import toml import toml
from ray import util
from send2trash import TrashPermissionError, send2trash from send2trash import TrashPermissionError, send2trash
from tqdm import tqdm from tqdm import tqdm
from pathlib import Path from pathlib import Path
@@ -139,6 +137,7 @@ def load_toml(path: os.PathLike):
def save_toml(path, dico): def save_toml(path, dico):
"""saves a dictionary into a toml file""" """saves a dictionary into a toml file"""
path = str(path)
if not path.lower().endswith(".toml"): if not path.lower().endswith(".toml"):
path += ".toml" path += ".toml"
with open(path, mode="w") as file: with open(path, mode="w") as file:
@@ -366,42 +365,11 @@ def load_last_spectrum(path: str) -> Tuple[int, np.ndarray]:
return num, np.load(os.path.join(path, f"spectrum_{num}.npy")) return num, np.load(os.path.join(path, f"spectrum_{num}.npy"))
def merge(paths: Union[str, List[str]]): def merge(paths: Union[str, List[str]], delete=False):
if isinstance(paths, str): if isinstance(paths, (str, Path)):
paths = [paths] paths = [paths]
for path in paths: for path in paths:
merge_same_simulations(path, delete=False) merge_same_simulations(path, delete=delete)
def append_simulations(paths: List[os.PathLike]):
paths: List[Path] = [Path(p).resolve() for p in paths]
master_sim_path = paths[-1]
merged_path = master_sim_path.parent / "merged_sims"
merged_path.mkdir(exist_ok=True)
for i, path in enumerate(paths):
shutil.copy(path / "initial_config.toml", merged_path / f"initial_config{i}.toml")
for sim in master_sim_path.glob("*"):
if not sim.is_dir() or not str(sim).endswith("merged"):
continue
sim_name = sim.name
merge_sim_path = merged_path / sim_name
merge_sim_path.mkdir(exist_ok=True)
shutil.copy(sim / "params.toml", merge_sim_path / f"params.toml")
z = []
z_num = 0
last_z = 0
for path in paths:
curr_z_num = load_toml(str(path / sim_name / "params.toml"))["z_num"]
for i in range(curr_z_num):
shutil.copy(
path / sim_name / f"spectra_{i}.npy",
merge_sim_path / f"spectra_{i + z_num}.npy",
)
z_arr = np.load(path / sim_name / "z.npy")
z.append(z_arr + last_z)
last_z += z_arr[-1]
z_num += curr_z_num
np.save(merge_sim_path / "z.npy", np.concatenate(z))
def append_and_merge(final_sim_path: os.PathLike, new_name=None): def append_and_merge(final_sim_path: os.PathLike, new_name=None):
@@ -409,20 +377,56 @@ def append_and_merge(final_sim_path: os.PathLike, new_name=None):
if new_name is None: if new_name is None:
new_name = final_sim_path.name + " appended" new_name = final_sim_path.name + " appended"
appended_path = final_sim_path.parent / new_name destination_path = final_sim_path.parent / new_name
appended_path.mkdir(exist_ok=True) destination_path.mkdir(exist_ok=True)
for sim_path in final_sim_path.glob("id*num*"): for sim_path in tqdm(list(final_sim_path.glob("id*num*")), position=0, desc="Appending"):
path_tree = [sim_path] path_tree = [sim_path]
sim_name = sim_path.name sim_name = sim_path.name
appended_sim_path = appended_path / sim_name appended_sim_path = destination_path / sim_name
appended_sim_path.mkdir(exist_ok=True) appended_sim_path.mkdir(exist_ok=True)
while (prev_sim_path := load_toml(path_tree[-1] / "params.toml")).get( while (
"prev_sim_dir" prev_sim_path := load_toml(path_tree[-1] / "params.toml").get("prev_data_dir")
) is not None: ) is not None:
path_tree.append(Path(prev_sim_path).resolve()) path_tree.append(Path(prev_sim_path).resolve())
z: List[np.ndarray] = []
z_num = 0
last_z = 0
for path in tqdm(list(reversed(path_tree)), position=1, leave=False):
curr_z_num = load_toml(path / "params.toml")["z_num"]
for i in range(curr_z_num):
shutil.copy(
path / f"spectrum_{i}.npy",
appended_sim_path / f"spectrum_{i + z_num}.npy",
)
z_arr = np.load(path / "z.npy")
z.append(z_arr + last_z)
last_z += z_arr[-1]
z_num += curr_z_num
z_arr = np.concatenate(z)
update_appended_params(sim_path / "params.toml", appended_sim_path / "params.toml", z_arr)
np.save(appended_sim_path / "z.npy", z_arr)
update_appended_params(
final_sim_path / "initial_config.toml", destination_path / "initial_config.toml", z_arr
)
merge(destination_path, delete=True)
def update_appended_params(param_path, new_path, z):
z_num = len(z)
params = load_toml(param_path)
if "simulation" in params:
params["simulation"]["z_num"] = z_num
params["simulation"]["z_targets"] = z_num
else:
params["z_num"] = z_num
params["z_targets"] = z_num
save_toml(new_path, params)
def merge_same_simulations(path: str, delete=True): def merge_same_simulations(path: str, delete=True):
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -443,7 +447,7 @@ def merge_same_simulations(path: str, delete=True):
base_folders.add(base_folder) base_folders.add(base_folder)
sim_num, param_num = utils.count_variations(config) sim_num, param_num = utils.count_variations(config)
pbar = utils.PBars(tqdm(total=sim_num * z_num, desc="merging data", ncols=100)) pbar = utils.PBars(tqdm(total=sim_num * z_num, desc="Merging data", ncols=100))
spectra = [] spectra = []
for z_id in range(z_num): for z_id in range(z_num):
@@ -462,7 +466,7 @@ def merge_same_simulations(path: str, delete=True):
if repeat_id == max_repeat_id: if repeat_id == max_repeat_id:
out_path = os.path.join( out_path = os.path.join(
path, path,
utils.format_variable_list(variable_and_ind[:-1]) + PARAM_SEPARATOR + "merged", utils.format_variable_list(variable_and_ind[1:-1]) + PARAM_SEPARATOR + "merged",
) )
out_path = ensure_folder(out_path, prevent_overwrite=False) out_path = ensure_folder(out_path, prevent_overwrite=False)
spectra = np.array(spectra).reshape(repeat, len(spectra[0])) spectra = np.array(spectra).reshape(repeat, len(spectra[0]))

View File

@@ -71,7 +71,7 @@ def _power_fact_array(x, n):
@jit(nopython=True) @jit(nopython=True)
def abs2(z): def abs2(z: np.ndarray) -> np.ndarray:
return z.real ** 2 + z.imag ** 2 return z.real ** 2 + z.imag ** 2

View File

@@ -44,7 +44,7 @@ P0T0_to_E0_fac = dict(
"""relates the total energy (amplitue^2) to the t0 parameter of the amplitude and the peak intensity (peak_amplitude^2)""" """relates the total energy (amplitue^2) to the t0 parameter of the amplitude and the peak intensity (peak_amplitude^2)"""
def initial_field(t, shape, t0, power): def initial_field(t, shape, t0, peak_power):
"""returns the initial field """returns the initial field
Parameters Parameters
@@ -56,7 +56,7 @@ def initial_field(t, shape, t0, power):
t0 : float t0 : float
time parameters. Can be obtained by dividing the FWHM by time parameters. Can be obtained by dividing the FWHM by
`scgenerator.physics.pulse.fwhm_to_T0_fac[shape]` `scgenerator.physics.pulse.fwhm_to_T0_fac[shape]`
power : float peak_power : float
peak power peak power
Returns Returns
@@ -70,20 +70,26 @@ def initial_field(t, shape, t0, power):
raised when shape is not recognized raised when shape is not recognized
""" """
if shape == "gaussian": if shape == "gaussian":
return gauss_pulse(t, t0, power) return gauss_pulse(t, t0, peak_power)
elif shape == "sech": elif shape == "sech":
return sech_pulse(t, t0, power) return sech_pulse(t, t0, peak_power)
else: else:
raise ValueError(f"shape '{shape}' not understood") raise ValueError(f"shape '{shape}' not understood")
def modify_field_ratio( def modify_field_ratio(
field: np.ndarray, target_power: float = None, intensity_noise: float = None t: np.ndarray,
field: np.ndarray,
target_power: float = None,
target_energy: float = None,
intensity_noise: float = None,
) -> float: ) -> float:
"""multiply a field by this number to get the desired effects """multiply a field by this number to get the desired effects
Parameters Parameters
---------- ----------
t : np.ndarray
time (only used when target_energy is not None)
field : np.ndarray field : np.ndarray
initial field initial field
target_power : float, optional target_power : float, optional
@@ -97,8 +103,11 @@ def modify_field_ratio(
ratio (multiply field by this number) ratio (multiply field by this number)
""" """
ratio = 1 ratio = 1
if target_power is not None: if target_energy is not None:
ratio *= np.sqrt(target_energy / np.trapz(abs2(field), t))
elif target_power is not None:
ratio *= np.sqrt(target_power / abs2(field).max()) ratio *= np.sqrt(target_power / abs2(field).max())
if intensity_noise is not None: if intensity_noise is not None:
d_int, _ = technical_noise(intensity_noise) d_int, _ = technical_noise(intensity_noise)
ratio *= np.sqrt(d_int) ratio *= np.sqrt(d_int)
@@ -109,7 +118,7 @@ def conform_pulse_params(
shape, shape,
width=None, width=None,
t0=None, t0=None,
power=None, peak_power=None,
energy=None, energy=None,
soliton_num=None, soliton_num=None,
gamma=None, gamma=None,
@@ -125,7 +134,7 @@ def conform_pulse_params(
fwhm of the intensity pulse, by default None fwhm of the intensity pulse, by default None
t0 : float, optional t0 : float, optional
time parameter of the amplitude pulse, by default None time parameter of the amplitude pulse, by default None
power : float, optional peak_power : float, optional
peak power, by default None peak power, by default None
energy : float, optional energy : float, optional
total energy of the pulse, by default None total energy of the pulse, by default None
@@ -140,17 +149,17 @@ def conform_pulse_params(
indicated by the order in which the parameters are enumerated below holds, indicated by the order in which the parameters are enumerated below holds,
meaning the superflous parameters will be overwritten. meaning the superflous parameters will be overwritten.
choose one of the possible combinations : choose one of the possible combinations :
1 of (width, t0), 1 of (power, energy), gamma and beta2 together optional (not one without the other) 1 of (width, t0), 1 of (peak_power, energy), gamma and beta2 together optional (not one without the other)
soliton_num, gamma, 1 of (width, power, energy, t0) soliton_num, gamma, 1 of (width, peak_power, energy, t0)
examples : examples :
specify width, power and energy -> t0 and energy will be computed specify width, peak_power and energy -> t0 and energy will be computed
specify soliton_num, gamma, power, t0 -> width, t0 and energy will be computed specify soliton_num, gamma, peak_power, t0 -> width, t0 and energy will be computed
Returns Returns
------- -------
width, t0, power, energy width, t0, peak_power, energy
when no gamma is specified when no gamma is specified
width, t0, power, energy, soliton_num width, t0, peak_power, energy, soliton_num
when gamma is specified when gamma is specified
Raises Raises
@@ -167,14 +176,14 @@ def conform_pulse_params(
raise TypeError("gamma must be specified when soliton_num is") raise TypeError("gamma must be specified when soliton_num is")
if width is not None: if width is not None:
power = soliton_num ** 2 * abs(beta2) / (gamma * t0 ** 2) peak_power = soliton_num ** 2 * abs(beta2) / (gamma * t0 ** 2)
elif power is not None: elif peak_power is not None:
t0 = np.sqrt(soliton_num ** 2 * abs(beta2) / (power * gamma)) t0 = np.sqrt(soliton_num ** 2 * abs(beta2) / (peak_power * gamma))
elif energy is not None: elif energy is not None:
t0 = P0T0_to_E0_fac[shape] * soliton_num ** 2 * abs(beta2) / (energy * gamma) t0 = P0T0_to_E0_fac[shape] * soliton_num ** 2 * abs(beta2) / (energy * gamma)
elif t0 is not None: elif t0 is not None:
width = t0 / fwhm_to_T0_fac[shape] width = t0 / fwhm_to_T0_fac[shape]
power = soliton_num ** 2 * abs(beta2) / (gamma * t0 ** 2) peak_power = soliton_num ** 2 * abs(beta2) / (gamma * t0 ** 2)
else: else:
raise TypeError("not enough parameters to determine pulse") raise TypeError("not enough parameters to determine pulse")
@@ -183,26 +192,26 @@ def conform_pulse_params(
else: else:
width = t0 / fwhm_to_T0_fac[shape] width = t0 / fwhm_to_T0_fac[shape]
if power is not None: if peak_power is not None:
energy = P0_to_E0(power, t0, shape) energy = P0_to_E0(peak_power, t0, shape)
else: else:
power = E0_to_P0(energy, t0, shape) peak_power = E0_to_P0(energy, t0, shape)
if gamma is None: if gamma is None:
return width, t0, power, energy return width, t0, peak_power, energy
else: else:
if soliton_num is None: if soliton_num is None:
soliton_num = np.sqrt(power * gamma * t0 ** 2 / abs(beta2)) soliton_num = np.sqrt(peak_power * gamma * t0 ** 2 / abs(beta2))
return width, t0, power, energy, soliton_num return width, t0, peak_power, energy, soliton_num
def E0_to_P0(E0, t0, shape="gaussian"): def E0_to_P0(E0, t0, shape="gaussian"):
"""convert an initial total pulse energy to a pulse peak power""" """convert an initial total pulse energy to a pulse peak peak_power"""
return E0 / (t0 * P0T0_to_E0_fac[shape]) return E0 / (t0 * P0T0_to_E0_fac[shape])
def P0_to_E0(P0, t0, shape="gaussian"): def P0_to_E0(P0, t0, shape="gaussian"):
"""converts initial peak power to pulse energy""" """converts initial peak peak_power to pulse energy"""
return P0 * t0 * P0T0_to_E0_fac[shape] return P0 * t0 * P0T0_to_E0_fac[shape]
@@ -234,7 +243,7 @@ def technical_noise(rms_noise, relative_factor=0.4):
rms_noise : float rms_noise : float
RMS amplitude noise of the laser RMS amplitude noise of the laser
relative factor : float relative factor : float
magnitude of the anticorrelation between power and pulse width noise magnitude of the anticorrelation between peak_power and pulse width noise
Returns Returns
---------- ----------
delta_int : float delta_int : float
@@ -825,9 +834,10 @@ def measure_properties(spectra, t, compress=True, debug=""):
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"""
intensity = abs2(field) intensity = abs2(field)
_, fwhm_lim, _, _ = find_lobe_limits(t, intensity) _, fwhm_lim, _, _ = find_lobe_limits(t, intensity)
fwhm = length(fwhm_lim) fwhm = length(fwhm_lim)
power = intensity.max() peak_power = intensity.max()
energy = np.trapz(intensity, t) energy = np.trapz(intensity, t)
return fwhm, power, energy return fwhm, peak_power, energy

View File

@@ -8,7 +8,7 @@ import numpy as np
from numba import jit from numba import jit
from tqdm import tqdm from tqdm import tqdm
from .. import initialize, io, utils from .. import initialize, io, utils, const
from ..errors import IncompleteDataFolderError from ..errors import IncompleteDataFolderError
from ..logger import get_logger from ..logger import get_logger
from . import pulse from . import pulse
@@ -36,7 +36,7 @@ class RK4IP:
w0 : float w0 : float
central angular frequency of the pulse central angular frequency of the pulse
w_power_fact : numpy.ndarray w_power_fact : numpy.ndarray
precomputed factorial/power operations on w_c (scgenerator.math.power_fact) precomputed factorial/peak_power operations on w_c (scgenerator.math.power_fact)
spec_0 : numpy.ndarray spec_0 : numpy.ndarray
initial spectral envelope as function of w_c initial spectral envelope as function of w_c
z_targets : list z_targets : list
@@ -304,6 +304,31 @@ class RK4IP:
pass pass
class SequentialRK4IP(RK4IP):
def __init__(
self,
sim_params,
overall_pbar: tqdm,
save_data=False,
job_identifier="",
task_id=0,
n_percent=10,
):
self.overall_pbar = overall_pbar
self.pbar = tqdm(**const.pbar_format(1))
super().__init__(
sim_params,
save_data=save_data,
job_identifier=job_identifier,
task_id=task_id,
n_percent=n_percent,
)
def step_saved(self):
self.overall_pbar.update()
self.pbar.update(self.z / self.z_final - self.pbar.n)
class MutliProcRK4IP(RK4IP): class MutliProcRK4IP(RK4IP):
def __init__( def __init__(
self, self,
@@ -360,20 +385,25 @@ class Simulations:
New Simulations child classes can be written and must implement the following New Simulations child classes can be written and must implement the following
""" """
_available_simulation_methods = [] simulation_methods: List[Tuple[Type["Simulations"], int]] = []
_available_simulation_methods_dict: Dict[str, Type["Simulations"]] = dict() simulation_methods_dict: Dict[str, Type["Simulations"]] = dict()
def __init_subclass__(cls, available: bool, priority=0, **kwargs): def __init_subclass__(cls, priority=0, **kwargs):
cls._available = available cls._available = cls.is_available()
if available: Simulations.simulation_methods.append((cls, priority))
Simulations._available_simulation_methods.append((cls, priority)) Simulations.simulation_methods_dict[cls.__name__] = cls
Simulations._available_simulation_methods_dict[cls.__name__] = cls Simulations.simulation_methods.sort(key=lambda el: el[1], reverse=True)
Simulations._available_simulation_methods.sort(key=lambda el: el[1])
super().__init_subclass__(**kwargs) super().__init_subclass__(**kwargs)
@classmethod @classmethod
def get_best_method(cls): def get_best_method(cls):
return Simulations._available_simulation_methods[-1][0] for method, _ in Simulations.simulation_methods:
if method.is_available():
return method
@classmethod
def is_available(cls) -> bool:
return False
def __init__(self, param_seq: initialize.ParamSequence, task_id=0): def __init__(self, param_seq: initialize.ParamSequence, task_id=0):
""" """
@@ -454,11 +484,23 @@ class Simulations:
raise NotImplementedError() raise NotImplementedError()
class SequencialSimulations(Simulations, available=True, priority=0): class SequencialSimulations(Simulations, priority=0):
@classmethod
def is_available(cls):
return True
def __init__(self, param_seq: initialize.ParamSequence, task_id):
super().__init__(param_seq, task_id=task_id)
self.overall_pbar = tqdm(
total=self.param_seq.num_steps, desc="Simulating", unit="step", **const.pbar_format(0)
)
def new_sim(self, variable_list: List[tuple], params: Dict[str, Any]): def new_sim(self, variable_list: List[tuple], params: Dict[str, Any]):
v_list_str = utils.format_variable_list(variable_list) v_list_str = utils.format_variable_list(variable_list)
self.logger.info(f"launching simulation with {v_list_str}") self.logger.info(f"launching simulation with {v_list_str}")
RK4IP(params, save_data=True, job_identifier=v_list_str, task_id=self.id).run() SequentialRK4IP(
params, self.overall_pbar, save_data=True, job_identifier=v_list_str, task_id=self.id
).run()
def stop(self): def stop(self):
pass pass
@@ -467,7 +509,11 @@ class SequencialSimulations(Simulations, available=True, priority=0):
pass pass
class MultiProcSimulations(Simulations, available=True, priority=10): class MultiProcSimulations(Simulations, priority=1):
@classmethod
def is_available(cls):
return True
def __init__(self, param_seq: initialize.ParamSequence, task_id): def __init__(self, param_seq: initialize.ParamSequence, task_id):
super().__init__(param_seq, task_id=task_id) super().__init__(param_seq, task_id=task_id)
self.sim_jobs_per_node = max(1, os.cpu_count() // 2) self.sim_jobs_per_node = max(1, os.cpu_count() // 2)
@@ -481,7 +527,7 @@ class MultiProcSimulations(Simulations, available=True, priority=10):
for i in range(self.sim_jobs_per_node) for i in range(self.sim_jobs_per_node)
] ]
self.p_worker = multiprocessing.Process( self.p_worker = multiprocessing.Process(
target=MultiProcSimulations.progress_worker, target=utils.progress_worker,
args=(self.param_seq.num_steps, self.progress_queue), args=(self.param_seq.num_steps, self.progress_queue),
) )
self.p_worker.start() self.p_worker.start()
@@ -530,33 +576,37 @@ class MultiProcSimulations(Simulations, available=True, priority=10):
).run() ).run()
queue.task_done() queue.task_done()
@staticmethod # @staticmethod
def progress_worker(num_steps: int, progress_queue: multiprocessing.Queue): # def progress_worker(num_steps: int, progress_queue: multiprocessing.Queue):
pbars: Dict[int, tqdm] = {} # pbars: Dict[int, tqdm] = {}
with tqdm(total=num_steps, desc="Simulating", unit="step", position=0) as tq: # with tqdm(total=num_steps, desc="Simulating", unit="step", position=0) as tq:
while True: # while True:
raw = progress_queue.get() # raw = progress_queue.get()
if raw == 0: # if raw == 0:
for pbar in pbars.values(): # for pbar in pbars.values():
pbar.close() # pbar.close()
return # return
i, rel_pos = raw # i, rel_pos = raw
if i not in pbars: # if i not in pbars:
pbars[i] = tqdm( # pbars[i] = tqdm(
total=1, # total=1,
desc=f"Worker {i}", # desc=f"Worker {i}",
position=i, # position=i,
bar_format="{l_bar}{bar}" # bar_format="{l_bar}{bar}"
"|[{elapsed}<{remaining}, " # "|[{elapsed}<{remaining}, "
"{rate_fmt}{postfix}]", # "{rate_fmt}{postfix}]",
) # )
pbars[i].update(rel_pos - pbars[i].n) # pbars[i].update(rel_pos - pbars[i].n)
tq.update() # tq.update()
class RaySimulations(Simulations, available=using_ray, priority=2): class RaySimulations(Simulations, priority=2):
"""runs simulation with the help of the ray module. ray must be initialized before creating an instance of RaySimulations""" """runs simulation with the help of the ray module. ray must be initialized before creating an instance of RaySimulations"""
@classmethod
def is_available(cls):
return using_ray and ray.is_initialized()
def __init__( def __init__(
self, self,
param_seq: initialize.ParamSequence, param_seq: initialize.ParamSequence,
@@ -660,8 +710,17 @@ class RaySimulations(Simulations, available=using_ray, priority=2):
self.p_bars.print() self.p_bars.print()
def new_simulations( def run_simulation_sequence(*config_files: os.PathLike, method=None, final_name: str = None):
config_file: str, prev = None
for config_file in config_files:
sim = new_simulation(config_file, prev, method)
sim.run()
prev = sim.data_folder
io.append_and_merge(prev, final_name)
def new_simulation(
config_file: os.PathLike,
prev_data_folder=None, prev_data_folder=None,
method: Type[Simulations] = None, method: Type[Simulations] = None,
) -> Simulations: ) -> Simulations:
@@ -697,7 +756,7 @@ def _new_simulations(
) -> Simulations: ) -> Simulations:
if method is not None: if method is not None:
if isinstance(method, str): if isinstance(method, str):
method = Simulations._available_simulation_methods_dict[method] method = Simulations.simulation_methods_dict[method]
return method(param_seq, task_id) return method(param_seq, task_id)
elif param_seq.num_sim > 1 and param_seq["simulation", "parallel"] and using_ray: elif param_seq.num_sim > 1 and param_seq["simulation", "parallel"] and using_ray:
return Simulations.get_best_method()(param_seq, task_id) return Simulations.get_best_method()(param_seq, task_id)
@@ -711,4 +770,4 @@ if __name__ == "__main__":
except NameError: except NameError:
pass pass
config_file, *opts = sys.argv[1:] config_file, *opts = sys.argv[1:]
new_simulations(config_file, *opts) new_simulation(config_file, *opts)

View File

@@ -2,8 +2,9 @@
# For example, nm(X) means "I give the number X in nm, figure out the ang. freq." # For example, nm(X) means "I give the number X in nm, figure out the ang. freq."
# to be used especially when giving plotting ranges : (400, 1400, nm), (-4, 8, ps), ... # to be used especially when giving plotting ranges : (400, 1400, nm), (-4, 8, ps), ...
from numba.core.types.misc import Phantom
import numpy as np import numpy as np
from numpy import pi from numpy import isin, pi
c = 299792458.0 c = 299792458.0
hbar = 1.05457148e-34 hbar = 1.05457148e-34
@@ -198,6 +199,30 @@ D_ps_nm_km.label = r"$D$ (ps/(nm km))"
D_ps_nm_km.type = "OTHER" D_ps_nm_km.type = "OTHER"
units_map = dict(
nm=nm,
um=um,
m=m,
THz=THz,
PHz=PHz,
rad_s=rad_s,
Prad_s=Prad_s,
rel_freq=rel_freq,
rel_time=rel_time,
s=s,
us=us,
ns=ns,
ps=ps,
fs=fs,
)
def get_unit(unit):
if isinstance(unit, str):
return units_map[unit]
return unit
def beta2_coef(beta): def beta2_coef(beta):
fac = 1e27 fac = 1e27
out = np.zeros_like(beta) out = np.zeros_like(beta)
@@ -217,11 +242,16 @@ def standardize_dictionary(dico):
same dictionary with units converted same dictionary with units converted
Example Example
---------- ----------
standardize_dictionary({"power": [23, "kW"], "points": [1, 2, 3]}) standardize_dictionary({"peak_power": [23, "kW"], "points": [1, 2, 3]})
{"power": 23000, "points": [1, 2, 3]}) {"peak_power": 23000, "points": [1, 2, 3]})
""" """
for key, item in dico.items(): for key, item in dico.items():
if isinstance(item, list) and len(item) == 2 and isinstance(item[0], (int, float)) and isinstance(item[1], str): if (
isinstance(item, list)
and len(item) == 2
and isinstance(item[0], (int, float))
and isinstance(item[1], str)
):
num, unit = item num, unit = item
fac = 1 fac = 1
if len(unit) == 2: if len(unit) == 2:
@@ -263,7 +293,7 @@ def sort_axis(axis, plt_range):
""" """
r = np.array(plt_range[:2], dtype="float") r = np.array(plt_range[:2], dtype="float")
func = plt_range[2] func = get_unit(plt_range[2])
indices = np.arange(len(axis))[(axis <= np.max(func(r))) & (axis >= np.min(func(r)))] indices = np.arange(len(axis))[(axis <= np.max(func(r))) & (axis >= np.min(func(r)))]
cropped = axis[indices] cropped = axis[indices]

View File

@@ -528,12 +528,7 @@ def plot_results_2D(
print(f"Shape was {values.shape}. plot_results_2D can only plot 2D arrays") print(f"Shape was {values.shape}. plot_results_2D can only plot 2D arrays")
return return
is_spectrum = values.dtype == "complex" is_spectrum, x_axis, plt_range = _prep_plot(values, plt_range, params)
if plt_range[2].type in ["WL", "FREQ", "AFREQ"]:
x_axis = params["w"].copy()
else:
x_axis = params["t"].copy()
# crop and convert # crop and convert
x_axis, ind, ext = units.sort_axis(x_axis[::skip], plt_range) x_axis, ind, ext = units.sort_axis(x_axis[::skip], plt_range)
@@ -648,12 +643,7 @@ def plot_results_1D(
print(f"Shape was {values.shape}. plot_results_1D can only plot 1D arrays") print(f"Shape was {values.shape}. plot_results_1D can only plot 1D arrays")
return return
is_spectrum = values.dtype == "complex" is_spectrum, x_axis, plt_range = _prep_plot(values, plt_range, params)
if plt_range[2].type in ["WL", "FREQ", "AFREQ"]:
x_axis = params["w"].copy()
else:
x_axis = params["t"].copy()
# crop and convert # crop and convert
x_axis, ind, ext = units.sort_axis(x_axis, plt_range) x_axis, ind, ext = units.sort_axis(x_axis, plt_range)
@@ -716,6 +706,16 @@ def plot_results_1D(
return fig, ax return fig, ax
def _prep_plot(values, plt_range, params):
is_spectrum = values.dtype == "complex"
plt_range = (*plt_range[:2], units.get_unit(plt_range[2]))
if plt_range[2].type in ["WL", "FREQ", "AFREQ"]:
x_axis = params["w"].copy()
else:
x_axis = params["t"].copy()
return is_spectrum, x_axis, plt_range
def plot_avg( def plot_avg(
values, values,
plt_range, plt_range,
@@ -795,12 +795,7 @@ def plot_avg(
print(f"Shape was {values.shape}. plot_avg can only plot 2D arrays") print(f"Shape was {values.shape}. plot_avg can only plot 2D arrays")
return return
is_spectrum = values.dtype == "complex" is_spectrum, x_axis, plt_range = _prep_plot(values, plt_range, params)
if plt_range[2].type in ["WL", "FREQ", "AFREQ"]:
x_axis = params["w"].copy()
else:
x_axis = params["t"].copy()
# crop and convert # crop and convert
x_axis, ind, ext = units.sort_axis(x_axis, plt_range) x_axis, ind, ext = units.sort_axis(x_axis, plt_range)

View File

@@ -5,9 +5,8 @@ import shutil
import subprocess import subprocess
from datetime import datetime, timedelta from datetime import datetime, timedelta
from scgenerator.initialize import validate from ..initialize import validate_config_sequence
from scgenerator.io import Paths, load_toml from ..utils import count_variations
from scgenerator.utils import count_variations
def format_time(t): def format_time(t):
@@ -20,7 +19,7 @@ def format_time(t):
def create_parser(): def create_parser():
parser = argparse.ArgumentParser(description="submit a job to a slurm cluster") parser = argparse.ArgumentParser(description="submit a job to a slurm cluster")
parser.add_argument("config", help="path to the toml configuration file") parser.add_argument("configs", nargs="+", help="path to the toml configuration file")
parser.add_argument( parser.add_argument(
"-t", "--time", required=True, type=str, help="time required for the job in hh:mm:ss" "-t", "--time", required=True, type=str, help="time required for the job in hh:mm:ss"
) )
@@ -59,19 +58,23 @@ def main():
"time format must be an integer number of minute or must match the pattern hh:mm:ss" "time format must be an integer number of minute or must match the pattern hh:mm:ss"
) )
config = load_toml(args.config) config_paths = args.configs
config = validate(config) final_config = validate_config_sequence(*config_paths)
sim_num, _ = count_variations(config) sim_num, _ = count_variations(final_config)
file_name = "submit " + config["name"] + "-" + format(datetime.now(), "%Y%m%d%H%M") + ".sh" file_name = (
job_name = f"supercontinuum {config['name']}" "submit " + final_config["name"] + "-" + format(datetime.now(), "%Y%m%d%H%M") + ".sh"
submit_sh = template.format(job_name=job_name, **vars(args)) )
job_name = f"supercontinuum {final_config['name']}"
submit_sh = template.format(
job_name=job_name, configs_list=" ".join(args.configs), **vars(args)
)
with open(file_name, "w") as file: with open(file_name, "w") as file:
file.write(submit_sh) file.write(submit_sh)
subprocess.run(["sbatch", "--test-only", file_name]) subprocess.run(["sbatch", "--test-only", file_name])
submit = input( submit = input(
f"Propagate {sim_num} pulses from config {args.config} with {args.cpus_per_node} cpus" f"Propagate {sim_num} pulses from configs {args.configs} with {args.cpus_per_node} cpus"
+ f" per node on {args.nodes} nodes for {format_time(args.time)} ? (y/[n])\n" + f" per node on {args.nodes} nodes for {format_time(args.time)} ? (y/[n])\n"
) )
if submit.lower() in ["y", "yes"]: if submit.lower() in ["y", "yes"]:

View File

@@ -30,9 +30,10 @@ class Spectrum(np.ndarray):
class Pulse(Sequence): class Pulse(Sequence):
def __init__(self, path: str): def __init__(self, path: str, ensure_2d=True):
self.logger = get_logger(__name__) self.logger = get_logger(__name__)
self.path = path self.path = path
self.__ensure_2d = ensure_2d
if not os.path.isdir(self.path): if not os.path.isdir(self.path):
raise FileNotFoundError(f"Folder {self.path} does not exist") raise FileNotFoundError(f"Folder {self.path} does not exist")
@@ -175,17 +176,18 @@ class Pulse(Sequence):
# Load the spectra # Load the spectra
spectra = [] spectra = []
for i in ind: for i in ind:
spectra.append(io.load_single_spectrum(self.path, i)) spectra.append(self._load1(i))
spectra = np.array(spectra) spectra = np.array(spectra)
self.logger.debug(f"all spectra from {self.path} successfully loaded") self.logger.debug(f"all spectra from {self.path} successfully loaded")
return spectra.squeeze() return spectra
def _load1(self, i: int): def _load1(self, i: int):
return Spectrum( spec = io.load_single_spectrum(self.path, i)
np.atleast_2d(io.load_single_spectrum(self.path, i)), self.wl, self.params["frep"] if self.__ensure_2d:
) spec = np.atleast_2d(spec)
return Spectrum(spec, self.wl, self.params["frep"])
class SpectraCollection(Mapping, Sequence): class SpectraCollection(Mapping, Sequence):

View File

@@ -9,6 +9,7 @@ import collections
import datetime as dt import datetime as dt
import itertools import itertools
import logging import logging
import multiprocessing
import re import re
import socket import socket
from typing import Any, Callable, Dict, Iterator, List, Mapping, Tuple, Union from typing import Any, Callable, Dict, Iterator, List, Mapping, Tuple, Union
@@ -20,7 +21,7 @@ from copy import deepcopy
from tqdm import tqdm from tqdm import tqdm
from .const import PARAM_SEPARATOR, PREFIX_KEY_BASE, valid_variable from .const import PARAM_SEPARATOR, PREFIX_KEY_BASE, valid_variable, pbar_format
from .logger import get_logger from .logger import get_logger
from .math import * from .math import *
@@ -162,6 +163,33 @@ class ProgressBarActor:
return self.counters return self.counters
def progress_worker(num_steps: int, progress_queue: multiprocessing.Queue):
"""keeps track of progress on a separate thread
Parameters
----------
num_steps : int
total number of steps, used for the main progress bar (position 0)
progress_queue : multiprocessing.Queue
values are either
Literal[0] : stop the worker and close the progress bars
Tuple[int, float] : worker id and relative progress between 0 and 1
"""
pbars: Dict[int, tqdm] = {}
with tqdm(total=num_steps, desc="Simulating", unit="step", position=0) as tq:
while True:
raw = progress_queue.get()
if raw == 0:
for pbar in pbars.values():
pbar.close()
return
i, rel_pos = raw
if i not in pbars:
pbars[i] = tqdm(**pbar_format(i))
pbars[i].update(rel_pos - pbars[i].n)
tq.update()
def count_variations(config: dict) -> Tuple[int, int]: def count_variations(config: dict) -> Tuple[int, int]:
"""returns (sim_num, variable_params_num) where sim_num is the total number of simulations required and """returns (sim_num, variable_params_num) where sim_num is the total number of simulations required and
variable_params_num is the number of distinct parameters that will vary.""" variable_params_num is the number of distinct parameters that will vary."""
@@ -347,7 +375,10 @@ def deep_update(d: Mapping, u: Mapping):
return d return d
def override_config(old: Dict[str, Any], new: Dict[str, Any]) -> Dict[str, Any]: def override_config(new: Dict[str, Any], old: Dict[str, Any] = None) -> Dict[str, Any]:
"""makes sure all the parameters set in new are there, leaves untouched parameters in old"""
if old is None:
return new
out = deepcopy(old) out = deepcopy(old)
for section_name, section in new.items(): for section_name, section in new.items():
if isinstance(section, Mapping): if isinstance(section, Mapping):

View File

@@ -9,7 +9,7 @@ pitch_ratio = 0.37
[pulse] [pulse]
intensity_noise = 0.05e-2 intensity_noise = 0.05e-2
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -7,7 +7,7 @@ model = "marcatili"
gas_name = ["air", "helium"] gas_name = ["air", "helium"]
[pulse] [pulse]
power = 100e3 peak_power = 100e3
wavelength = 800e-9 wavelength = 800e-9
[pulse.variable] [pulse.variable]

View File

@@ -7,7 +7,7 @@ model = "marcatili"
gas_name = "air" gas_name = "air"
[pulse] [pulse]
power = 100e3 peak_power = 100e3
wavelength = 800e-9 wavelength = 800e-9
width = 250e-15 width = 250e-15

View File

@@ -7,7 +7,7 @@ model = "marcatili"
gas_name = "air" gas_name = "air"
[pulse] [pulse]
power = 100e3 peak_power = 100e3
wavelength = 800e-9 wavelength = 800e-9
[pulse.variable] [pulse.variable]

View File

@@ -7,7 +7,7 @@ model = "marcatili"
gas_name = "air" gas_name = "air"
[pulse] [pulse]
power = 100e3 peak_power = 100e3
wavelength = 800e-9 wavelength = 800e-9
width = 250e-15 width = 250e-15

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -1,4 +1,4 @@
#t0, width, power or energy missing #t0, width, peak_power or energy missing
name = "test config" name = "test config"

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -12,7 +12,7 @@ length = 1
model = "hasan" model = "hasan"
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -11,7 +11,7 @@ length = 1
model = "hasan" model = "hasan"
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -9,7 +9,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -8,7 +8,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -18,7 +18,7 @@ gas_name = "helium"
temperature = [300, 350, 400] temperature = [300, 350, 400]
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -9,7 +9,7 @@ length = 1
model = "marcatili" model = "marcatili"
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -11,7 +11,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -1,32 +1,32 @@
name = "full anomalous" name = "full anomalous"
[fiber] [fiber]
beta = [ -1.183e-26, 8.1038e-41, -9.5205e-56, 2.0737e-70, -5.3943e-85, 1.3486e-99, -2.5495e-114, 3.0524e-129, -1.714e-144,] beta = [-1.183e-26, 8.1038e-41, -9.5205e-56, 2.0737e-70, -5.3943e-85, 1.3486e-99, -2.5495e-114, 3.0524e-129, -1.714e-144]
gamma = 0.11 gamma = 0.11
input_transmission = 1.0
length = 0.02 length = 0.02
model = "custom" model = "custom"
input_transmission = 1.0
[pulse] [pulse]
power = 10000
t0 = 2.84e-14
shape = "gaussian"
quantum_noise = false
intensity_noise = 0 intensity_noise = 0
peak_power = 10000
quantum_noise = false
shape = "gaussian"
t0 = 2.84e-14
[simulation] [simulation]
behaviors = ["spm", "ss"]
dt = 1e-15 dt = 1e-15
frep = 80000000.0
ideal_gas = false
lower_wavelength_interp_limit = 3e-7
parallel = true parallel = true
raman_type = "measured" raman_type = "measured"
repeat = 3 repeat = 3
t_num = 16384 t_num = 16384
tolerated_error = 1e-9 tolerated_error = 1e-9
z_num = 64
behaviors = [ "spm", "ss",]
frep = 80000000.0
lower_wavelength_interp_limit = 3e-7
upper_wavelength_interp_limit = 1.9e-6 upper_wavelength_interp_limit = 1.9e-6
ideal_gas = false z_num = 64
[pulse.variable] [pulse.variable]
wavelength = [ 8.35e-7, 8.3375e-7,] wavelength = [8.35e-7, 8.3375e-7]

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -11,7 +11,7 @@ pitch_ratio = 0.37
[pulse] [pulse]
intensity_noise = 0.1e-2 intensity_noise = 0.1e-2
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -16,7 +16,7 @@ gamma = 0.11
length = 0.02 length = 0.02
[pulse] [pulse]
power = 10000 peak_power = 10000
t0 = 2.84e-14 t0 = 2.84e-14
[pulse.variable] [pulse.variable]

View File

@@ -9,8 +9,8 @@ model = "pcf"
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
peak_power = 100e3
pitch = 1.5e-6 pitch = 1.5e-6
power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -10,7 +10,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -11,7 +11,7 @@ pitch_ratio = 0.37
[pulse] [pulse]
intensity_noise = 0.05e-2 intensity_noise = 0.05e-2
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -8,7 +8,7 @@ gamma = 0.018
length = 1 length = 1
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -8,7 +8,7 @@ pitch = 1.5e-6
pitch_ratio = 0.37 pitch_ratio = 0.37
[pulse] [pulse]
power = 100e3 peak_power = 100e3
quantum_noise = true quantum_noise = true
shape = "gaussian" shape = "gaussian"
wavelength = 1050e-9 wavelength = 1050e-9

View File

@@ -92,7 +92,7 @@ class TestInitializeMethods(unittest.TestCase):
with self.assertRaisesRegex( with self.assertRaisesRegex(
MissingParameterError, MissingParameterError,
r"1 of '\['power', 'energy', 'width', 't0'\]' is required when 'soliton_num' is specified and no defaults have been set", r"1 of '\['peak_power', 'energy', 'width', 't0'\]' is required when 'soliton_num' is specified and no defaults have been set",
): ):
init._ensure_consistency(conf("bad2")) init._ensure_consistency(conf("bad2"))

View File

@@ -6,13 +6,13 @@ class TestPulseMethods(unittest.TestCase):
def test_conform_pulse_params(self): def test_conform_pulse_params(self):
self.assertNotIn(None, conform_pulse_params("gaussian", t0=5, energy=6)) self.assertNotIn(None, conform_pulse_params("gaussian", t0=5, energy=6))
self.assertNotIn(None, conform_pulse_params("gaussian", width=5, energy=6)) self.assertNotIn(None, conform_pulse_params("gaussian", width=5, energy=6))
self.assertNotIn(None, conform_pulse_params("gaussian", t0=5, power=6)) self.assertNotIn(None, conform_pulse_params("gaussian", t0=5, peak_power=6))
self.assertNotIn(None, conform_pulse_params("gaussian", width=5, power=6)) self.assertNotIn(None, conform_pulse_params("gaussian", width=5, peak_power=6))
self.assertEqual(4, len(conform_pulse_params("gaussian", t0=5, energy=6))) self.assertEqual(4, len(conform_pulse_params("gaussian", t0=5, energy=6)))
self.assertEqual(4, len(conform_pulse_params("gaussian", width=5, energy=6))) self.assertEqual(4, len(conform_pulse_params("gaussian", width=5, energy=6)))
self.assertEqual(4, len(conform_pulse_params("gaussian", t0=5, power=6))) self.assertEqual(4, len(conform_pulse_params("gaussian", t0=5, peak_power=6)))
self.assertEqual(4, len(conform_pulse_params("gaussian", width=5, power=6))) self.assertEqual(4, len(conform_pulse_params("gaussian", width=5, peak_power=6)))
with self.assertRaisesRegex( with self.assertRaisesRegex(
TypeError, "when soliton number is desired, both gamma and beta2 must be specified" TypeError, "when soliton number is desired, both gamma and beta2 must be specified"
@@ -30,10 +30,10 @@ class TestPulseMethods(unittest.TestCase):
5, len(conform_pulse_params("gaussian", width=5, energy=6, gamma=0.01, beta2=2e-6)) 5, len(conform_pulse_params("gaussian", width=5, energy=6, gamma=0.01, beta2=2e-6))
) )
self.assertEqual( self.assertEqual(
5, len(conform_pulse_params("gaussian", t0=5, power=6, gamma=0.01, beta2=2e-6)) 5, len(conform_pulse_params("gaussian", t0=5, peak_power=6, gamma=0.01, beta2=2e-6))
) )
self.assertEqual( self.assertEqual(
5, len(conform_pulse_params("gaussian", width=5, power=6, gamma=0.01, beta2=2e-6)) 5, len(conform_pulse_params("gaussian", width=5, peak_power=6, gamma=0.01, beta2=2e-6))
) )

View File

@@ -58,7 +58,7 @@ class TestUtilsMethods(unittest.TestCase):
old = conf("initial_config") old = conf("initial_config")
new = conf("fiber2") new = conf("fiber2")
over = utils.override_config(old, new) over = utils.override_config(new, old)
self.assertIn("input_transmission", over["fiber"]["variable"]) self.assertIn("input_transmission", over["fiber"]["variable"])
self.assertNotIn("input_transmission", over["fiber"]) self.assertNotIn("input_transmission", over["fiber"])