From 5b9e882d449d98d94e046ecc6f1bc2f54d742767 Mon Sep 17 00:00:00 2001 From: Stefan Possanner Date: Mon, 12 Oct 2026 20:54:11 +0200 Subject: [PATCH 1/4] Profiling examples: long-time particle equilibria for Vlasov and GuidingCenter One params file and one submit script per model and geometry (variants via --variant, ranks 1 to 256), as for the existing profiling cases: - Vlasov/cuboid_strong_scaling: Maxwellian3D with constant moments in a periodic cube (Cuboid, side 2 pi) with a uniform magnetic field (HomogenSlab), an exact equilibrium. Binned distributions in position (e1, e1_e2) and velocity (v1, v3, v1_v2) are compared with their initial and exact profiles. Variants: push_vxb analytic/implicit, push_eta forward Euler/Heun2/Heun3/RK4, Strang splitting, Sobol loading. - GuidingCenter/polar_cylinder_strong_scaling: straight cylinder without hole (IGAPolarCylinder, polar splines) with ScrewPinch (Lz = 2 pi R0, pressure offset); GyroMaxwellian2D with n(r) and vth(r) = sqrt(p/n) of the equilibrium, an exact equilibrium (the drifts keep r constant). - GuidingCenter/polar_torus_strong_scaling: full torus without hole (IGAPolarTorus, polar splines) with AdhocTorus; CanonicalMaxwellian2D with n and vth functions of psi_c (an exact equilibrium), and a GyroMaxwellian2D with the same profiles in the flux label of the position as a control. - GuidingCenter variants: discrete gradient 1st order (fixed point, Newton), 2nd order, explicit RK4/Heun3. - profiling/examples/particle_diagnostics.py: shared post-processing (binning plots at the first and last time, relative deviations over time, drift of the scalars) into sim_/results. Co-Authored-By: Claude Opus 5.5 --- .../params_guiding_center.py | 222 ++++++++++++++++ .../params_guiding_center.py | 247 ++++++++++++++++++ ...ng_center_polar_cylinder_strong_scaling.py | 111 ++++++++ ...iding_center_polar_torus_strong_scaling.py | 120 +++++++++ .../cuboid_strong_scaling/params_vlasov.py | 188 +++++++++++++ .../submit_vlasov_cuboid_strong_scaling.py | 118 +++++++++ profiling/examples/particle_diagnostics.py | 145 ++++++++++ 7 files changed, 1151 insertions(+) create mode 100644 profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/params_guiding_center.py create mode 100644 profiling/examples/GuidingCenter/polar_torus_strong_scaling/params_guiding_center.py create mode 100644 profiling/examples/GuidingCenter/submit_guiding_center_polar_cylinder_strong_scaling.py create mode 100644 profiling/examples/GuidingCenter/submit_guiding_center_polar_torus_strong_scaling.py create mode 100644 profiling/examples/Vlasov/cuboid_strong_scaling/params_vlasov.py create mode 100644 profiling/examples/Vlasov/submit_vlasov_cuboid_strong_scaling.py create mode 100644 profiling/examples/particle_diagnostics.py diff --git a/profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/params_guiding_center.py b/profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/params_guiding_center.py new file mode 100644 index 000000000..d636fd266 --- /dev/null +++ b/profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/params_guiding_center.py @@ -0,0 +1,222 @@ +# ----------------------------- +# Description of the simulation +# ----------------------------- +# Please fill in a verbal description of the simulation. +# It will be printed at the beginning of the simulation and can be used to keep track of the different runs. + +import argparse + +# Command line options +# `--id` distinguishes runs that share a rank count but differ in something else; the +# profiling driver passes its launch counter (see `ProfilingJob.build_commands`). +# The other flags select the variant (algorithms of the propagators), see the submit script. +# Unknown flags are ignored so the driver can forward other parameters as well. +parser = argparse.ArgumentParser() +parser.add_argument("--id", type=int, default=0, help="Run id, used to name the output folder.") +parser.add_argument( + "--algo", + default="discrete_gradient_1st_order", + choices=[ + "discrete_gradient_1st_order", + "discrete_gradient_1st_order_newton", + "discrete_gradient_2nd_order", + "explicit", + ], + help="Algorithm of both guiding-center propagators (push_bxe and push_parallel).", +) +parser.add_argument( + "--butcher", + default="rk4", + choices=["rk4", "forward_euler", "heun2", "rk2", "heun3", "3/8 rule"], + help="Runge-Kutta method of the explicit algorithm.", +) +parser.add_argument("--epsilon", type=float, default=0.05, help="Normalized cyclotron period (size of the drifts).") +parser.add_argument("--ppc", type=int, default=100, help="Markers per cell.") +parser.add_argument("--num-elements", type=int, nargs=3, default=[16, 18, 16], help="Number of elements per direction.") +parser.add_argument("--dt", type=float, default=0.1, help="Time step.") +parser.add_argument("--tend", type=float, default=200.0, help="End time.") +parser.add_argument("--save-step", type=int, default=20, help="Save (and bin) every save_step time steps.") +args, _ = parser.parse_known_args() + +name = f"GuidingCenter gyro-Maxwellian equilibrium in a screw pinch, {args.algo}" +description = f""" +Long-time equilibrium test of the GuidingCenter model in a straight cylinder without hole +(IGAPolarCylinder, minor radius 1, length 2 pi R0, C1 polar splines) with a screw pinch equilibrium (ScrewPinch). +The guiding-center drifts keep the radius constant, hence a gyro-Maxwellian whose density and thermal speed +depend only on r (taken from the equilibrium) is an exact equilibrium. Markers move through the axis. +The binned distributions in position (eta1, eta1-eta2) and velocity space (v_parallel, mu, eta1-v_parallel) +are compared with their initial profiles (and the radial density with the exact one) up to t={args.tend} (dt={args.dt}). +Propagators push_bxe and push_parallel: {args.algo} (Butcher {args.butcher} if explicit), epsilon={args.epsilon}, +{args.ppc} markers per cell on a grid with {args.num_elements} elements. +""" + +import logging + +from struphy import set_logging_level + +set_logging_level(logging.INFO) + +# ------------------ +# Import Struphy API +# ------------------ +import numpy as np + +from struphy import ( + BaseUnits, + BinningPlot, + BoundaryParameters, + DerhamOptions, + EnvironmentOptions, + LoadingParameters, + SavingParameters, + Simulation, + SortingParameters, + Time, + WeightsParameters, + domains, + equils, + grids, + maxwellians, +) + +# --------------------- +# Instance of the model +# --------------------- +from struphy.models import GuidingCenter +from struphy.ode.utils import ButcherTableau + +model = GuidingCenter(base_units=BaseUnits(), epsilon=args.epsilon) +model.kinetic_ions.var.save_data = True + +# -------------------------- +# Instance of the simulation +# -------------------------- + +env = EnvironmentOptions(sim_folder=f"sim_{args.id:02d}", save_step=args.save_step, max_runtime=24 * 60) + +time_opts = Time(dt=args.dt, Tend=args.tend, split_algo="LieTrotter") + +# Grid (polar splines need a multiple of 3 elements in eta2) +num_elements = tuple(args.num_elements) +degree = (3, 3, 3) +grid = grids.TensorProductGrid(num_elements=num_elements, mpi_dims_mask=(True, True, True)) + +# Geometry: cylinder without hole, the spline mapping has the resolution of the grid in the poloidal plane +a = 1.0 +R0 = 5.0 +domain = domains.IGAPolarCylinder(num_elements=num_elements[:2], degree=degree[:2], a=a, Lz=2 * np.pi * R0) +derham_opts = DerhamOptions(degree=degree, bcs=(("free", "free"), None, None), polar_splines=True) + +# Screw pinch with a peaked density; the pressure offset p0 keeps the thermal speed finite at r=a +equil = equils.ScrewPinch(a=a, R0=R0, B0=1.0, q0=1.05, q1=1.8, n1=2.0, n2=1.0, na=0.2, p0=0.005) + +sim = Simulation( + model=model, + name=name, + description=description, + params_path=__file__, + env=env, + time_opts=time_opts, + domain=domain, + equil=equil, + grid=grid, + derham_opts=derham_opts, +) + +# ------------------- +# Particle parameters +# ------------------- + +# radial profiles of the gyro-Maxwellian: density and thermal speed sqrt(p/n) of the equilibrium +r_eval = np.linspace(0.0, a, 101) +vth_max = float(np.max(np.sqrt(equil.p_r(r_eval) / equil.n_r(r_eval)))) +B_min = 1.0 + + +def n_profile(*etas): + """Density n(r) of the screw pinch, r = a * eta1 (called with eta1, eta2, eta3 or one (n, 3) array).""" + eta1 = etas[0] if len(etas) == 3 else etas[0][:, 0] + r = a * eta1 + return 0.2 + 0.8 * (1.0 - r**2 / a**2) + + +def vth_profile(*etas): + """Thermal speed sqrt(p(r)/n(r)) of the screw pinch.""" + eta1 = etas[0] if len(etas) == 3 else etas[0][:, 0] + r = a * eta1 + q = 1.05 + (1.8 - 1.05) * r**2 / a**2 + p = 0.005 + 1.0**2 * a**2 * 1.05 / (2 * R0**2 * (1.8 - 1.05)) * (1 / q**2 - 1 / 1.8**2) + return np.sqrt(p / n_profile(*etas)) + + +# the profiles above must be those of the equilibrium +assert np.allclose(n_profile(r_eval / a, 0 * r_eval, 0 * r_eval), equil.n_r(r_eval)) +assert np.allclose(vth_profile(r_eval / a, 0 * r_eval, 0 * r_eval), np.sqrt(equil.p_r(r_eval) / equil.n_r(r_eval))) + +v_max = 5.0 * vth_max +mu_max = 12.0 * vth_max**2 / B_min +binning_plots = ( + BinningPlot(slice="e1", n_bins=32, ranges=(0.0, 1.0)), + BinningPlot(slice="e1_e2", n_bins=(32, 36), ranges=((0.0, 1.0), (0.0, 1.0))), + BinningPlot(slice="v1", n_bins=64, ranges=(-v_max, v_max)), + BinningPlot(slice="v2", n_bins=64, ranges=(0.0, mu_max)), + BinningPlot(slice="e1_v1", n_bins=(32, 64), ranges=((0.0, 1.0), (-v_max, v_max))), +) + +model.kinetic_ions.set_markers( + loading_params=LoadingParameters( + ppc=args.ppc, seed=1234, moments=(0.0, 0.0, vth_max, vth_max), B0=1.0, spatial="disc" + ), + weights_params=WeightsParameters(), + boundary_params=BoundaryParameters(bc=("remove", "periodic", "periodic")), + sorting_params=SortingParameters(do_sort=True, sorting_frequency=10), + saving_params=SavingParameters(n_markers=0, binning_plots=binning_plots), + # the discrete-gradient pushers sort at mid-points in each iteration + bufsize=2.0, +) + +# ------------------ +# Propagator options +# ------------------ + +butcher = ButcherTableau(algo=args.butcher) if args.algo == "explicit" else None +model.propagators.push_bxe.options = model.propagators.push_bxe.Options(algo=args.algo, butcher=butcher) +model.propagators.push_parallel.options = model.propagators.push_parallel.Options(algo=args.algo, butcher=butcher) + +# ------------------ +# Initial conditions +# ------------------ + +background = maxwellians.GyroMaxwellian2D( + n=(n_profile, None), + vth_para=(vth_profile, None), + vth_perp=(vth_profile, None), + B0=equil.absB0, +) +model.kinetic_ions.var.add_background(background) + +# exact radial density (binned with divide_by_jac, normalized to the binned data) +exact = {"e1_density": lambda e1: n_profile(e1, 0 * e1, 0 * e1)} + +if __name__ == "__main__": + import sys + from pathlib import Path + + # the shared post-processing of the particle profiling cases lives in profiling/examples + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from particle_diagnostics import save_equilibrium_diagnostics + + # switches for debugging and testing + run = True + pproc = True + + if run: + out = sim.run(profiling_activated=True) + else: + # use this for working with existing sim data from a previous run + out = sim.output + + if pproc: + save_equilibrium_diagnostics( + out, "kinetic_ions", exact=exact, scalars=("en_tot", "n_lost_particles"), comm=sim.comm + ) diff --git a/profiling/examples/GuidingCenter/polar_torus_strong_scaling/params_guiding_center.py b/profiling/examples/GuidingCenter/polar_torus_strong_scaling/params_guiding_center.py new file mode 100644 index 000000000..6b0e2ffdf --- /dev/null +++ b/profiling/examples/GuidingCenter/polar_torus_strong_scaling/params_guiding_center.py @@ -0,0 +1,247 @@ +# ----------------------------- +# Description of the simulation +# ----------------------------- +# Please fill in a verbal description of the simulation. +# It will be printed at the beginning of the simulation and can be used to keep track of the different runs. + +import argparse + +# Command line options +# `--id` distinguishes runs that share a rank count but differ in something else; the +# profiling driver passes its launch counter (see `ProfilingJob.build_commands`). +# The other flags select the variant (initial condition, algorithms of the propagators), see the submit script. +# Unknown flags are ignored so the driver can forward other parameters as well. +parser = argparse.ArgumentParser() +parser.add_argument("--id", type=int, default=0, help="Run id, used to name the output folder.") +parser.add_argument( + "--init", + default="canonical", + choices=["canonical", "gyro"], + help="Canonical Maxwellian (function of the constants of motion, an equilibrium) or gyro-Maxwellian " + "(same profiles in the flux label of the position, not an equilibrium in a torus).", +) +parser.add_argument( + "--algo", + default="discrete_gradient_1st_order", + choices=[ + "discrete_gradient_1st_order", + "discrete_gradient_1st_order_newton", + "discrete_gradient_2nd_order", + "explicit", + ], + help="Algorithm of both guiding-center propagators (push_bxe and push_parallel).", +) +parser.add_argument( + "--butcher", + default="rk4", + choices=["rk4", "forward_euler", "heun2", "rk2", "heun3", "3/8 rule"], + help="Runge-Kutta method of the explicit algorithm.", +) +parser.add_argument("--epsilon", type=float, default=0.05, help="Normalized cyclotron period (size of the drifts).") +parser.add_argument("--ppc", type=int, default=100, help="Markers per cell.") +parser.add_argument("--num-elements", type=int, nargs=3, default=[16, 18, 16], help="Number of elements per direction.") +parser.add_argument("--dt", type=float, default=0.1, help="Time step.") +parser.add_argument("--tend", type=float, default=200.0, help="End time.") +parser.add_argument("--save-step", type=int, default=20, help="Save (and bin) every save_step time steps.") +args, _ = parser.parse_known_args() + +name = f"GuidingCenter {args.init} Maxwellian in an ad hoc tokamak, {args.algo}" +description = f""" +Long-time equilibrium test of the GuidingCenter model in a full torus without hole (IGAPolarTorus, minor radius 1, +major radius 3, C1 polar splines) with an ad hoc tokamak equilibrium (AdhocTorus). Markers drift through the axis. +Initial condition: {args.init} Maxwellian. A canonical Maxwellian (density and thermal speed functions of the canonical +toroidal momentum psi_c) is an exact equilibrium; a gyro-Maxwellian with the same profiles in the flux label of the +position is not (radial drifts in the torus), it serves as a control. +The binned distributions in position (eta1, eta1-eta2) and velocity space (v_parallel, mu, eta1-v_parallel) +are compared with their initial profiles up to t={args.tend} (dt={args.dt}). +Propagators push_bxe and push_parallel: {args.algo} (Butcher {args.butcher} if explicit), epsilon={args.epsilon}, +{args.ppc} markers per cell on a grid with {args.num_elements} elements. +""" + +import logging + +from struphy import set_logging_level + +set_logging_level(logging.INFO) + +# ------------------ +# Import Struphy API +# ------------------ +import numpy as np + +from struphy import ( + BaseUnits, + BinningPlot, + BoundaryParameters, + DerhamOptions, + EnvironmentOptions, + LoadingParameters, + SavingParameters, + Simulation, + SortingParameters, + Time, + WeightsParameters, + domains, + equils, + grids, + maxwellians, +) + +# --------------------- +# Instance of the model +# --------------------- +from struphy.models import GuidingCenter +from struphy.ode.utils import ButcherTableau + +model = GuidingCenter(base_units=BaseUnits(), epsilon=args.epsilon) +model.kinetic_ions.var.save_data = True + +# -------------------------- +# Instance of the simulation +# -------------------------- + +env = EnvironmentOptions(sim_folder=f"sim_{args.id:02d}", save_step=args.save_step, max_runtime=24 * 60) + +time_opts = Time(dt=args.dt, Tend=args.tend, split_algo="LieTrotter") + +# Grid (polar splines need a multiple of 3 elements in eta2) +num_elements = tuple(args.num_elements) +degree = (3, 3, 3) +grid = grids.TensorProductGrid(num_elements=num_elements, mpi_dims_mask=(True, True, True)) + +# Geometry: full torus without hole, the spline mapping has the resolution of the grid in the poloidal plane +a = 1.0 +R0 = 3.0 +domain = domains.IGAPolarTorus(num_elements=num_elements[:2], degree=degree[:2], a=a, R0=R0, sfl=False, tor_period=1) +derham_opts = DerhamOptions(degree=degree, bcs=(("free", "free"), None, None), polar_splines=True) + +# Ad hoc tokamak with circular concentric flux surfaces matching the domain (same a and R0) +B0 = 1.0 +equil = equils.AdhocTorus(a=a, R0=R0, B0=B0, q_kind=0, q0=1.71, q1=1.87) + +sim = Simulation( + model=model, + name=name, + description=description, + params_path=__file__, + env=env, + time_opts=time_opts, + domain=domain, + equil=equil, + grid=grid, + derham_opts=derham_opts, +) + +# ------------------- +# Particle parameters +# ------------------- + +# profiles in the normalized poloidal flux s = (psi - psi_axis) / (psi_edge - psi_axis), in [0, 1] +PSI_AXIS, PSI_EDGE = (float(psi) for psi in equil.psi_range) +VTH_AXIS = 0.25 +VTH_EDGE = 0.15 + + +def n_of_s(s): + s = np.clip(s, 0.0, 1.0) + return 0.2 + 0.8 * (1.0 - s) + + +def vth_of_s(s): + s = np.clip(s, 0.0, 1.0) + return VTH_EDGE + (VTH_AXIS - VTH_EDGE) * (1.0 - s) + + +def n_canonical(psi_c): + """Density as a function of the canonical toroidal momentum.""" + return n_of_s((psi_c - PSI_AXIS) / (PSI_EDGE - PSI_AXIS)) + + +def vth_canonical(psi_c): + """Thermal speed as a function of the canonical toroidal momentum.""" + return vth_of_s((psi_c - PSI_AXIS) / (PSI_EDGE - PSI_AXIS)) + + +def n_position(*etas): + """Density at the position, with the flux label s = eta1**2 (r = a * eta1, nearly constant q).""" + eta1 = etas[0] if len(etas) == 3 else etas[0][:, 0] + return n_of_s(eta1**2) + + +def vth_position(*etas): + """Thermal speed at the position, with the flux label s = eta1**2.""" + eta1 = etas[0] if len(etas) == 3 else etas[0][:, 0] + return vth_of_s(eta1**2) + + +B_min = B0 * R0 / (R0 + a) +v_max = 5.0 * VTH_AXIS +mu_max = 12.0 * VTH_AXIS**2 / B_min +binning_plots = ( + BinningPlot(slice="e1", n_bins=32, ranges=(0.0, 1.0)), + BinningPlot(slice="e1_e2", n_bins=(32, 36), ranges=((0.0, 1.0), (0.0, 1.0))), + BinningPlot(slice="v1", n_bins=64, ranges=(-v_max, v_max)), + BinningPlot(slice="v2", n_bins=64, ranges=(0.0, mu_max)), + BinningPlot(slice="e1_v1", n_bins=(32, 64), ranges=((0.0, 1.0), (-v_max, v_max))), +) + +model.kinetic_ions.set_markers( + loading_params=LoadingParameters( + ppc=args.ppc, seed=1234, moments=(0.0, 0.0, VTH_AXIS, VTH_AXIS), B0=B0, spatial="disc" + ), + weights_params=WeightsParameters(), + boundary_params=BoundaryParameters(bc=("remove", "periodic", "periodic")), + sorting_params=SortingParameters(do_sort=True, sorting_frequency=10), + saving_params=SavingParameters(n_markers=0, binning_plots=binning_plots), + # the discrete-gradient pushers sort at mid-points in each iteration + bufsize=2.0, +) + +# ------------------ +# Propagator options +# ------------------ + +butcher = ButcherTableau(algo=args.butcher) if args.algo == "explicit" else None +model.propagators.push_bxe.options = model.propagators.push_bxe.Options(algo=args.algo, butcher=butcher) +model.propagators.push_parallel.options = model.propagators.push_parallel.Options(algo=args.algo, butcher=butcher) + +# ------------------ +# Initial conditions +# ------------------ + +if args.init == "canonical": + background = maxwellians.CanonicalMaxwellian2D( + n=(n_canonical, None), + vth=(vth_canonical, None), + equil=equil, + epsilon=args.epsilon, + ) +else: + background = maxwellians.GyroMaxwellian2D( + n=(n_position, None), + vth_para=(vth_position, None), + vth_perp=(vth_position, None), + B0=equil.absB0, + ) +model.kinetic_ions.var.add_background(background) + +if __name__ == "__main__": + import sys + from pathlib import Path + + # the shared post-processing of the particle profiling cases lives in profiling/examples + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from particle_diagnostics import save_equilibrium_diagnostics + + # switches for debugging and testing + run = True + pproc = True + + if run: + out = sim.run(profiling_activated=True) + else: + # use this for working with existing sim data from a previous run + out = sim.output + + if pproc: + save_equilibrium_diagnostics(out, "kinetic_ions", scalars=("en_tot", "n_lost_particles"), comm=sim.comm) diff --git a/profiling/examples/GuidingCenter/submit_guiding_center_polar_cylinder_strong_scaling.py b/profiling/examples/GuidingCenter/submit_guiding_center_polar_cylinder_strong_scaling.py new file mode 100644 index 000000000..0b061de62 --- /dev/null +++ b/profiling/examples/GuidingCenter/submit_guiding_center_polar_cylinder_strong_scaling.py @@ -0,0 +1,111 @@ +"""GuidingCenter polar cylinder strong scaling profiling case: long-time gyro-Maxwellian equilibrium in a screw pinch. + +This file defines one variant of the GuidingCenter strong scaling profiling case in a straight cylinder without +hole (IGAPolarCylinder, C1 polar splines) with a screw pinch equilibrium (the `ProfilingCase`): a gyro-Maxwellian +whose moments depend only on the radius is an exact equilibrium and must stay so for long times, while markers +move through the axis. For each rank count, `ProfilingCase.launch` builds and submits a SLURM script (using +`clusters.SLURM_PRESETS` by default), or, without a batch system, runs directly on this machine. +`finalize_run` then packages and uploads each run as soon as its own job finishes. +Each generated script runs the simulation itself by invoking `params_guiding_center.py` directly (its `__main__` +block is the worker), with the flags of the variant. + +The variant (algorithm of the guiding-center propagators) is chosen with `--variant`; run this script once per +variant. +""" + +import argparse +import sys +from pathlib import Path + +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + +from profiling_job import ProfilingCase # noqa: E402 + +# variant -> (description, flags of params_guiding_center.py) +VARIANTS = { + "dg_1st": ( + "discrete gradient, 1st order (fixed point)", + ["--algo", "discrete_gradient_1st_order"], + ), + "dg_1st_newton": ( + "discrete gradient, 1st order (Newton)", + ["--algo", "discrete_gradient_1st_order_newton"], + ), + "dg_2nd": ( + "discrete gradient, 2nd order", + ["--algo", "discrete_gradient_2nd_order"], + ), + "explicit_rk4": ( + "explicit RK4", + ["--algo", "explicit", "--butcher", "rk4"], + ), + "explicit_heun3": ( + "explicit Heun3", + ["--algo", "explicit", "--butcher", "heun3"], + ), +} + + +def main() -> None: + + # Parse arguments, do not remove --upload + parser = argparse.ArgumentParser( + description=("Submit profiling jobs to a SLURM cluster and package the results for upload."), + ) + parser.add_argument( + "--variant", + required=True, + choices=list(VARIANTS), + help="Algorithm of the guiding-center propagators of the runs.", + ) + parser.add_argument( + "--upload", + action="store_true", + help="Upload the packaged profiling results to the profiling-data repo.", + ) + parser.add_argument( + "--language", + default="fortran", + choices=["fortran", "c"], + help="Pyccel language to compile the Struphy kernels with.", + ) + parser.add_argument( + "--compiler", + default="GNU", + help='Pyccel compiler family ("GNU", "intel", "PGI", "nvidia" or "LLVM").', + ) + args = parser.parse_args() + variant_description, param_flags = VARIANTS[args.variant] + + # Paths relative to this script's location, so it can be run from anywhere. + script_dir = Path(__file__).resolve().parent + params_dir = script_dir / "polar_cylinder_strong_scaling" + + profiling_case = ProfilingCase( + label=f"guiding_center_polar_cylinder_strong_scaling_{args.variant}", + name=f"GuidingCenter screw pinch equilibrium strong scaling test, {variant_description}", + description=( + "Strong scaling of the GuidingCenter model in a straight cylinder without hole (IGAPolarCylinder, C1 polar " + "splines) with a screw pinch equilibrium: a gyro-Maxwellian with radial moments is an exact equilibrium and " + "must stay so for long times while markers move through the axis; binned distributions in position and " + f"velocity space are compared with their initial profiles: {variant_description}." + ), + physics_problem="Long-time guiding-center equilibrium in a screw pinch.", + struphy_model_used="GuidingCenter", + params_source=params_dir / "params_guiding_center.py", + language=args.language, + compiler=args.compiler, + upload=args.upload, + ) + + # Launch one run per rank count + for num_tasks in (1, 2, 4, 8, 16, 32, 64, 128, 256): + profiling_case.launch(num_tasks, param_flags=param_flags) + + # Package and push each run as its own job finishes. + profiling_case.finalize_run() + + +if __name__ == "__main__": + main() diff --git a/profiling/examples/GuidingCenter/submit_guiding_center_polar_torus_strong_scaling.py b/profiling/examples/GuidingCenter/submit_guiding_center_polar_torus_strong_scaling.py new file mode 100644 index 000000000..f5a3d074f --- /dev/null +++ b/profiling/examples/GuidingCenter/submit_guiding_center_polar_torus_strong_scaling.py @@ -0,0 +1,120 @@ +"""GuidingCenter polar torus strong scaling profiling case: long-time canonical Maxwellian equilibrium in a tokamak. + +This file defines one variant of the GuidingCenter strong scaling profiling case in a full torus without hole +(IGAPolarTorus, C1 polar splines) with an ad hoc tokamak equilibrium (the `ProfilingCase`): a canonical Maxwellian +(moments as functions of the canonical toroidal momentum) is an exact equilibrium and must stay so for long times, +while markers drift through the axis; a gyro-Maxwellian with the same profiles in the flux label of the position is +not an equilibrium and serves as a control. For each rank count, `ProfilingCase.launch` builds and submits a SLURM +script (using `clusters.SLURM_PRESETS` by default), or, without a batch system, runs directly on this machine. +`finalize_run` then packages and uploads each run as soon as its own job finishes. +Each generated script runs the simulation itself by invoking `params_guiding_center.py` directly (its `__main__` +block is the worker), with the flags of the variant. + +The variant (initial condition, algorithm of the guiding-center propagators) is chosen with `--variant`; run this +script once per variant. +""" + +import argparse +import sys +from pathlib import Path + +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + +from profiling_job import ProfilingCase # noqa: E402 + +# variant -> (description, flags of params_guiding_center.py) +VARIANTS = { + "canonical_dg_1st": ( + "canonical Maxwellian, discrete gradient, 1st order (fixed point)", + ["--init", "canonical", "--algo", "discrete_gradient_1st_order"], + ), + "canonical_dg_1st_newton": ( + "canonical Maxwellian, discrete gradient, 1st order (Newton)", + ["--init", "canonical", "--algo", "discrete_gradient_1st_order_newton"], + ), + "canonical_dg_2nd": ( + "canonical Maxwellian, discrete gradient, 2nd order", + ["--init", "canonical", "--algo", "discrete_gradient_2nd_order"], + ), + "canonical_explicit_rk4": ( + "canonical Maxwellian, explicit RK4", + ["--init", "canonical", "--algo", "explicit", "--butcher", "rk4"], + ), + "canonical_explicit_heun3": ( + "canonical Maxwellian, explicit Heun3", + ["--init", "canonical", "--algo", "explicit", "--butcher", "heun3"], + ), + "gyro_dg_1st": ( + "gyro-Maxwellian (control, not an equilibrium), discrete gradient, 1st order (fixed point)", + ["--init", "gyro", "--algo", "discrete_gradient_1st_order"], + ), + "gyro_explicit_rk4": ( + "gyro-Maxwellian (control, not an equilibrium), explicit RK4", + ["--init", "gyro", "--algo", "explicit", "--butcher", "rk4"], + ), +} + + +def main() -> None: + + # Parse arguments, do not remove --upload + parser = argparse.ArgumentParser( + description=("Submit profiling jobs to a SLURM cluster and package the results for upload."), + ) + parser.add_argument( + "--variant", + required=True, + choices=list(VARIANTS), + help="Initial condition and algorithm of the guiding-center propagators of the runs.", + ) + parser.add_argument( + "--upload", + action="store_true", + help="Upload the packaged profiling results to the profiling-data repo.", + ) + parser.add_argument( + "--language", + default="fortran", + choices=["fortran", "c"], + help="Pyccel language to compile the Struphy kernels with.", + ) + parser.add_argument( + "--compiler", + default="GNU", + help='Pyccel compiler family ("GNU", "intel", "PGI", "nvidia" or "LLVM").', + ) + args = parser.parse_args() + variant_description, param_flags = VARIANTS[args.variant] + + # Paths relative to this script's location, so it can be run from anywhere. + script_dir = Path(__file__).resolve().parent + params_dir = script_dir / "polar_torus_strong_scaling" + + profiling_case = ProfilingCase( + label=f"guiding_center_polar_torus_strong_scaling_{args.variant}", + name=f"GuidingCenter ad hoc tokamak equilibrium strong scaling test, {variant_description}", + description=( + "Strong scaling of the GuidingCenter model in a full torus without hole (IGAPolarTorus, C1 polar splines) " + "with an ad hoc tokamak equilibrium: a canonical Maxwellian is an exact equilibrium and must stay so for " + "long times while markers drift through the axis (a gyro-Maxwellian serves as a control); binned " + f"distributions in position and velocity space are compared with their initial profiles: {variant_description}." + ), + physics_problem="Long-time guiding-center equilibrium in a tokamak.", + struphy_model_used="GuidingCenter", + params_source=params_dir / "params_guiding_center.py", + language=args.language, + compiler=args.compiler, + upload=args.upload, + ) + + # Launch one run per rank count + for num_tasks in (1, 2, 4, 8, 16, 32, 64, 128, 256): + profiling_case.launch(num_tasks, param_flags=param_flags) + + # Package and push each run as its own job finishes. + profiling_case.finalize_run() + + +if __name__ == "__main__": + main() diff --git a/profiling/examples/Vlasov/cuboid_strong_scaling/params_vlasov.py b/profiling/examples/Vlasov/cuboid_strong_scaling/params_vlasov.py new file mode 100644 index 000000000..186bc036c --- /dev/null +++ b/profiling/examples/Vlasov/cuboid_strong_scaling/params_vlasov.py @@ -0,0 +1,188 @@ +# ----------------------------- +# Description of the simulation +# ----------------------------- +# Please fill in a verbal description of the simulation. +# It will be printed at the beginning of the simulation and can be used to keep track of the different runs. + +import argparse + +# Command line options +# `--id` distinguishes runs that share a rank count but differ in something else; the +# profiling driver passes its launch counter (see `ProfilingJob.build_commands`). +# The other flags select the variant (algorithms of the propagators, loading), see the submit script. +# Unknown flags are ignored so the driver can forward other parameters as well. +parser = argparse.ArgumentParser() +parser.add_argument("--id", type=int, default=0, help="Run id, used to name the output folder.") +parser.add_argument( + "--vxb-algo", default="analytic", choices=["analytic", "implicit"], help="Algorithm of the push_vxb propagator." +) +parser.add_argument( + "--butcher", + default="rk4", + choices=["rk4", "forward_euler", "heun2", "rk2", "heun3", "3/8 rule"], + help="Runge-Kutta method of the push_eta propagator.", +) +parser.add_argument("--split", default="LieTrotter", choices=["LieTrotter", "Strang"], help="Splitting algorithm.") +parser.add_argument( + "--loading", default="pseudo_random", choices=["pseudo_random", "sobol_standard"], help="Marker loading." +) +parser.add_argument("--ppc", type=int, default=100, help="Markers per cell.") +parser.add_argument("--num-elements", type=int, nargs=3, default=[16, 16, 16], help="Number of elements per direction.") +parser.add_argument("--dt", type=float, default=0.05, help="Time step.") +parser.add_argument("--tend", type=float, default=100.0, help="End time (about 16 cyclotron periods).") +parser.add_argument("--save-step", type=int, default=20, help="Save (and bin) every save_step time steps.") +args, _ = parser.parse_known_args() + +name = f"Vlasov Maxwellian equilibrium in a periodic cube, push_vxb {args.vxb_algo}, push_eta {args.butcher}" +description = f""" +Long-time equilibrium test of the Vlasov model (particles in a static magnetic field): +a Maxwellian with constant moments (n=1, u=0, vth=1) in a periodic cube (Cuboid, side 2 pi) with +a uniform magnetic field B0 = e_z (HomogenSlab) is an exact equilibrium. The binned distributions in +position (eta1, eta1-eta2) and velocity (v1, v3, v1-v2) are compared with their initial and exact profiles +up to t={args.tend} (dt={args.dt}, {args.split} splitting). +Propagators: push_vxb ({args.vxb_algo}), push_eta ({args.butcher}). Loading {args.loading}, {args.ppc} markers per cell +on a grid with {args.num_elements} elements. +""" + +import logging + +from struphy import set_logging_level + +set_logging_level(logging.INFO) + +# ------------------ +# Import Struphy API +# ------------------ +import numpy as np + +from struphy import ( + BaseUnits, + BinningPlot, + BoundaryParameters, + DerhamOptions, + EnvironmentOptions, + LoadingParameters, + SavingParameters, + Simulation, + SortingParameters, + Time, + WeightsParameters, + domains, + equils, + grids, + maxwellians, +) + +# --------------------- +# Instance of the model +# --------------------- +from struphy.models import Vlasov +from struphy.ode.utils import ButcherTableau + +model = Vlasov(base_units=BaseUnits()) +model.kinetic_ions.var.save_data = True + +# -------------------------- +# Instance of the simulation +# -------------------------- + +env = EnvironmentOptions(sim_folder=f"sim_{args.id:02d}", save_step=args.save_step, max_runtime=24 * 60) + +# Time stepping (the cyclotron period is 2 pi in units of the model) +time_opts = Time(dt=args.dt, Tend=args.tend, split_algo=args.split) + +# Geometry: periodic cube of side 2 pi +L = 2 * np.pi +domain = domains.Cuboid(r1=L, r2=L, r3=L) + +# Uniform magnetic field along z +equil = equils.HomogenSlab(B0x=0.0, B0y=0.0, B0z=1.0) + +# Grid and Derham options (only the projection of the magnetic field) +num_elements = tuple(args.num_elements) +grid = grids.TensorProductGrid(num_elements=num_elements, mpi_dims_mask=(True, True, True)) +derham_opts = DerhamOptions(degree=(2, 2, 2)) + +sim = Simulation( + model=model, + name=name, + description=description, + params_path=__file__, + env=env, + time_opts=time_opts, + domain=domain, + equil=equil, + grid=grid, + derham_opts=derham_opts, +) + +# ------------------- +# Particle parameters +# ------------------- + +vth = 1.0 +v_max = 5.0 * vth +binning_plots = ( + BinningPlot(slice="e1", n_bins=32, ranges=(0.0, 1.0)), + BinningPlot(slice="e1_e2", n_bins=(32, 32), ranges=((0.0, 1.0), (0.0, 1.0))), + BinningPlot(slice="v1", n_bins=64, ranges=(-v_max, v_max)), + BinningPlot(slice="v3", n_bins=64, ranges=(-v_max, v_max)), + BinningPlot(slice="v1_v2", n_bins=(64, 64), ranges=((-v_max, v_max), (-v_max, v_max))), +) + +model.kinetic_ions.set_markers( + loading_params=LoadingParameters(ppc=args.ppc, loading=args.loading, seed=1234), + weights_params=WeightsParameters(), + boundary_params=BoundaryParameters(bc=("periodic", "periodic", "periodic")), + sorting_params=SortingParameters(do_sort=True, sorting_frequency=10), + saving_params=SavingParameters(n_markers=0, binning_plots=binning_plots), +) + +# ------------------ +# Propagator options +# ------------------ + +model.propagators.push_vxb.options = model.propagators.push_vxb.Options(algo=args.vxb_algo) +model.propagators.push_eta.options = model.propagators.push_eta.Options(butcher=ButcherTableau(algo=args.butcher)) + +# ------------------ +# Initial conditions +# ------------------ + +background = maxwellians.Maxwellian3D(n=(1.0, None), vth1=(vth, None), vth2=(vth, None), vth3=(vth, None)) +model.kinetic_ions.var.add_background(background) + + +def gaussian(v): + return np.exp(-(v**2) / (2 * vth**2)) + + +# exact equilibrium profiles of the binned distributions (normalized to the binned data) +exact = { + "e1_density": lambda e1: np.ones_like(e1), + "e1_e2_density": lambda e1, e2: np.ones_like(e1), + "v1_density": gaussian, + "v3_density": gaussian, + "v1_v2_density": lambda v1, v2: gaussian(v1) * gaussian(v2), +} + +if __name__ == "__main__": + import sys + from pathlib import Path + + # the shared post-processing of the particle profiling cases lives in profiling/examples + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from particle_diagnostics import save_equilibrium_diagnostics + + # switches for debugging and testing + run = True + pproc = True + + if run: + out = sim.run(profiling_activated=True) + else: + # use this for working with existing sim data from a previous run + out = sim.output + + if pproc: + save_equilibrium_diagnostics(out, "kinetic_ions", exact=exact, scalars=("kinetic_energy",), comm=sim.comm) diff --git a/profiling/examples/Vlasov/submit_vlasov_cuboid_strong_scaling.py b/profiling/examples/Vlasov/submit_vlasov_cuboid_strong_scaling.py new file mode 100644 index 000000000..58518253d --- /dev/null +++ b/profiling/examples/Vlasov/submit_vlasov_cuboid_strong_scaling.py @@ -0,0 +1,118 @@ +"""Vlasov cuboid strong scaling profiling case: long-time Maxwellian equilibrium in a periodic cube. + +This file defines one variant of the Vlasov strong scaling profiling case (the `ProfilingCase`): +a Maxwellian with constant moments in a periodic cube with a uniform magnetic field, which must stay in +equilibrium for long times (binned distributions in position and velocity space are compared with the exact +profiles). For each rank count, `ProfilingCase.launch` builds and submits a SLURM script (using +`clusters.SLURM_PRESETS` by default), or, without a batch system, runs directly on this machine. +`finalize_run` then packages and uploads each run as soon as its own job finishes. +Each generated script runs the simulation itself by invoking `params_vlasov.py` directly (its `__main__` +block is the worker), with the flags of the variant. + +The variant (algorithms of the propagators, splitting, loading) is chosen with `--variant`; run this script once +per variant. +""" + +import argparse +import sys +from pathlib import Path + +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + +from profiling_job import ProfilingCase # noqa: E402 + +# variant -> (description, flags of params_vlasov.py) +VARIANTS = { + "analytic_rk4": ( + "analytic push_vxb, RK4 push_eta", + ["--vxb-algo", "analytic", "--butcher", "rk4"], + ), + "implicit_rk4": ( + "implicit push_vxb, RK4 push_eta", + ["--vxb-algo", "implicit", "--butcher", "rk4"], + ), + "analytic_forward_euler": ( + "analytic push_vxb, forward Euler push_eta", + ["--vxb-algo", "analytic", "--butcher", "forward_euler"], + ), + "analytic_heun2": ( + "analytic push_vxb, Heun2 push_eta", + ["--vxb-algo", "analytic", "--butcher", "heun2"], + ), + "analytic_heun3": ( + "analytic push_vxb, Heun3 push_eta", + ["--vxb-algo", "analytic", "--butcher", "heun3"], + ), + "analytic_rk4_strang": ( + "analytic push_vxb, RK4 push_eta, Strang splitting", + ["--vxb-algo", "analytic", "--butcher", "rk4", "--split", "Strang"], + ), + "analytic_rk4_sobol": ( + "analytic push_vxb, RK4 push_eta, Sobol loading", + ["--vxb-algo", "analytic", "--butcher", "rk4", "--loading", "sobol_standard"], + ), +} + + +def main() -> None: + + # Parse arguments, do not remove --upload + parser = argparse.ArgumentParser( + description=("Submit profiling jobs to a SLURM cluster and package the results for upload."), + ) + parser.add_argument( + "--variant", + required=True, + choices=list(VARIANTS), + help="Algorithms of the propagators, splitting and loading of the runs.", + ) + parser.add_argument( + "--upload", + action="store_true", + help="Upload the packaged profiling results to the profiling-data repo.", + ) + parser.add_argument( + "--language", + default="fortran", + choices=["fortran", "c"], + help="Pyccel language to compile the Struphy kernels with.", + ) + parser.add_argument( + "--compiler", + default="GNU", + help='Pyccel compiler family ("GNU", "intel", "PGI", "nvidia" or "LLVM").', + ) + args = parser.parse_args() + variant_description, param_flags = VARIANTS[args.variant] + + # Paths relative to this script's location, so it can be run from anywhere. + script_dir = Path(__file__).resolve().parent + params_dir = script_dir / "cuboid_strong_scaling" + + profiling_case = ProfilingCase( + label=f"vlasov_cuboid_strong_scaling_{args.variant}", + name=f"Vlasov Maxwellian equilibrium in a periodic cube strong scaling test, {variant_description}", + description=( + "Strong scaling of the Vlasov model (particles in a static, uniform magnetic field) with a Maxwellian of " + "constant moments in a periodic cube, which must stay in equilibrium for long times; binned distributions " + f"in position and velocity space are compared with the exact profiles: {variant_description}." + ), + physics_problem="Long-time Maxwellian equilibrium of charged particles in a uniform magnetic field.", + struphy_model_used="Vlasov", + params_source=params_dir / "params_vlasov.py", + language=args.language, + compiler=args.compiler, + upload=args.upload, + ) + + # Launch one run per rank count + for num_tasks in (1, 2, 4, 8, 16, 32, 64, 128, 256): + profiling_case.launch(num_tasks, param_flags=param_flags) + + # Package and push each run as its own job finishes. + profiling_case.finalize_run() + + +if __name__ == "__main__": + main() diff --git a/profiling/examples/particle_diagnostics.py b/profiling/examples/particle_diagnostics.py new file mode 100644 index 000000000..d44c63b52 --- /dev/null +++ b/profiling/examples/particle_diagnostics.py @@ -0,0 +1,145 @@ +"""Post-processing shared by the particle profiling cases (Vlasov, GuidingCenter). + +The cases test whether an equilibrium distribution stays in equilibrium for long times. From the binned +distributions (``BinningPlot``) of a run, :func:`save_equilibrium_diagnostics` writes to ``/results``: + +* ``binning_.png``: the binned distribution at the first and last time (and the exact profile if given); + 2d slices as maps of the first and last time and of their difference, +* ``deviation.png`` and ``deviation_.npy``: the relative L2 deviation of the binned distribution from + the first time (and from the exact profile) over time, +* ``scalars.png`` and ``.npy``: the relative drift of the given scalars (e.g. the energy) over time, +* ``time.npy``. + +The profiling packaging picks these files up from ``results`` and uploads them with the run. +""" + +import os + +import numpy as np + + +def _rel_l2(f, ref): + """Relative L2 deviation of each time slice of f (time first) from ref.""" + axes = tuple(range(1, f.ndim)) + return np.sqrt(np.sum((f - ref) ** 2, axis=axes)) / np.sqrt(np.sum(ref**2)) + + +def save_equilibrium_diagnostics(out, species, results_dir=None, exact=None, scalars=(), comm=None): + """Plot and save the binned distributions and scalars of a run, see the module docstring. + + Parameters + ---------- + out : struphy.post_processing.output.Output + Output of the run (``sim.run()`` or ``sim.output``). + + species : str + Name of the kinetic species, e.g. ``"kinetic_ions"``. + + results_dir : str, optional + Folder for the results (default ``/results``). + + exact : dict[str, callable], optional + Exact (unnormalized) equilibrium profile per slice name (e.g. ``"e1_density"``), called with the + meshgrid of the bin centers (``indexing="ij"``). It is scaled to the integral of the first time. + + scalars : tuple[str] + Names of scalars whose relative drift is saved. + + comm : MPI communicator, optional + Post-processing needs all ranks, plotting is done on rank 0. + """ + from matplotlib import pyplot as plt + + rank = 0 if comm is None else comm.Get_rank() + + # explicit post-processing on all ranks (lazy evaluation does not work under MPI) + out.pproc() + if rank != 0: + return + + if results_dir is None: + results_dir = os.path.join(out.path_out, "results") + os.makedirs(results_dir, exist_ok=True) + exact = {} if exact is None else exact + + time = None + fig_dev, ax_dev = plt.subplots(figsize=(7, 4.5)) + for key in out.distribution_catalog: + spec, slice_name, name = key.split("/") + if spec != species or name != "f": + continue + + f_arr = out.distribution_catalog[key] + dims = [dim for dim in f_arr.dims if dim != "t"] + time = f_arr.coords["t"].values + f = f_arr.values + centers = [f_arr.coords[dim].values for dim in dims] + + dev_initial = _rel_l2(f, f[0]) + np.save(os.path.join(results_dir, f"deviation_{slice_name}.npy"), dev_initial) + ax_dev.semilogy(time[1:], dev_initial[1:], label=f"{slice_name} (from t=0)") + + f_exact = None + if slice_name in exact: + f_exact = exact[slice_name](*np.meshgrid(*centers, indexing="ij")) + f_exact = f_exact * np.sum(f[0]) / np.sum(f_exact) + dev_exact = _rel_l2(f, f_exact) + np.save(os.path.join(results_dir, f"deviation_exact_{slice_name}.npy"), dev_exact) + ax_dev.semilogy(time, dev_exact, "--", label=f"{slice_name} (from exact)") + + if len(dims) == 1: + fig, ax = plt.subplots(figsize=(6, 4)) + ax.plot(centers[0], f[0], label=f"t={time[0]:.3g}") + ax.plot(centers[0], f[-1], label=f"t={time[-1]:.3g}") + if f_exact is not None: + ax.plot(centers[0], f_exact, "k--", label="exact") + ax.set_xlabel(dims[0]) + ax.set_ylabel("f") + ax.legend() + elif len(dims) == 2: + fig, axs = plt.subplots(1, 3, figsize=(15, 4)) + vmin, vmax = np.min(f[[0, -1]]), np.max(f[[0, -1]]) + for ax, values, title in zip( + axs, + (f[0], f[-1], f[-1] - f[0]), + (f"t={time[0]:.3g}", f"t={time[-1]:.3g}", "difference"), + ): + kwargs = {} if title == "difference" else {"vmin": vmin, "vmax": vmax} + im = ax.pcolormesh(centers[0], centers[1], values.T, shading="nearest", **kwargs) + fig.colorbar(im, ax=ax) + ax.set_xlabel(dims[0]) + ax.set_ylabel(dims[1]) + ax.set_title(title) + else: + continue + fig.suptitle(f"{species}: {slice_name}") + fig.tight_layout() + fig.savefig(os.path.join(results_dir, f"binning_{slice_name}.png")) + plt.close(fig) + + ax_dev.set_xlabel("t") + ax_dev.set_ylabel("relative L2 deviation") + ax_dev.legend(fontsize=7) + fig_dev.tight_layout() + fig_dev.savefig(os.path.join(results_dir, "deviation.png")) + plt.close(fig_dev) + + if scalars: + fig, ax = plt.subplots(figsize=(7, 4.5)) + for name in scalars: + values = out.scalars[name].values + t = out.scalars[name].coords["t"].values + drift = (values - values[0]) / np.abs(values[0]) + np.save(os.path.join(results_dir, f"{name}.npy"), values) + ax.plot(t, drift, label=name) + if time is None: + time = t + ax.set_xlabel("t") + ax.set_ylabel("relative drift") + ax.legend() + fig.tight_layout() + fig.savefig(os.path.join(results_dir, "scalars.png")) + plt.close(fig) + + if time is not None: + np.save(os.path.join(results_dir, "time.npy"), time) From cb3265fc0809ca94f14af225d3a9403be2947f7d Mon Sep 17 00:00:00 2001 From: Stefan Possanner Date: Mon, 12 Oct 2026 21:40:35 +0200 Subject: [PATCH 2/4] Particle profiling examples: review fixes - GuidingCenter: default grid 16x48x48 decomposed only in eta2 and eta3 (mpi_dims_mask=(False, True, True)), such that 1 to 256 processes work (at least degree elements per process, all polar rings on the process with the pole); 16x18x16 could not be decomposed onto 128 and 256 processes. ppc 25, dt 0.2, Tend 100. - GuidingCenter torus: the density vanishes at the edge (markers on orbits that leave the plasma are removed), loading with B0 = B_min. - GuidingCenter cylinder: the exact radial profile of the binning is n(r)/|B0|(r) (velocity-space Jacobian). - particle_diagnostics: scalars that start at zero (number of lost markers) are plotted as absolute values. - The marker loss in the first step on several processes (disc loading) is fixed on particle-pole (merged). Co-Authored-By: Claude Opus 5.5 --- .../params_guiding_center.py | 24 +++++++++++------ .../params_guiding_center.py | 27 ++++++++++++------- profiling/examples/particle_diagnostics.py | 16 ++++++++--- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/params_guiding_center.py b/profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/params_guiding_center.py index d636fd266..386a04b27 100644 --- a/profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/params_guiding_center.py +++ b/profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/params_guiding_center.py @@ -31,10 +31,16 @@ help="Runge-Kutta method of the explicit algorithm.", ) parser.add_argument("--epsilon", type=float, default=0.05, help="Normalized cyclotron period (size of the drifts).") -parser.add_argument("--ppc", type=int, default=100, help="Markers per cell.") -parser.add_argument("--num-elements", type=int, nargs=3, default=[16, 18, 16], help="Number of elements per direction.") -parser.add_argument("--dt", type=float, default=0.1, help="Time step.") -parser.add_argument("--tend", type=float, default=200.0, help="End time.") +parser.add_argument("--ppc", type=int, default=25, help="Markers per cell.") +parser.add_argument( + "--num-elements", + type=int, + nargs=3, + default=[16, 48, 48], + help="Number of elements per direction (eta1 is not decomposed; up to 256 processes for the default).", +) +parser.add_argument("--dt", type=float, default=0.2, help="Time step.") +parser.add_argument("--tend", type=float, default=100.0, help="End time.") parser.add_argument("--save-step", type=int, default=20, help="Save (and bin) every save_step time steps.") args, _ = parser.parse_known_args() @@ -96,10 +102,11 @@ time_opts = Time(dt=args.dt, Tend=args.tend, split_algo="LieTrotter") -# Grid (polar splines need a multiple of 3 elements in eta2) +# Grid (polar splines need a multiple of 3 elements in eta2); eta1 is not decomposed, such that the process +# with the pole has all rings of the polar splines and each process has at least degree elements per direction num_elements = tuple(args.num_elements) degree = (3, 3, 3) -grid = grids.TensorProductGrid(num_elements=num_elements, mpi_dims_mask=(True, True, True)) +grid = grids.TensorProductGrid(num_elements=num_elements, mpi_dims_mask=(False, True, True)) # Geometry: cylinder without hole, the spline mapping has the resolution of the grid in the poloidal plane a = 1.0 @@ -195,8 +202,9 @@ def vth_profile(*etas): ) model.kinetic_ions.var.add_background(background) -# exact radial density (binned with divide_by_jac, normalized to the binned data) -exact = {"e1_density": lambda e1: n_profile(e1, 0 * e1, 0 * e1)} +# exact radial profile of the binned distribution (normalized to the binned data): the binning divides by the +# spatial Jacobian only, the velocity-space Jacobian |B0| of (v_parallel, mu) remains +exact = {"e1_density": lambda e1: n_profile(e1, 0 * e1, 0 * e1) / equil.absB0(np.stack((e1, 0 * e1, 0 * e1), axis=1))} if __name__ == "__main__": import sys diff --git a/profiling/examples/GuidingCenter/polar_torus_strong_scaling/params_guiding_center.py b/profiling/examples/GuidingCenter/polar_torus_strong_scaling/params_guiding_center.py index 6b0e2ffdf..23e59f9c6 100644 --- a/profiling/examples/GuidingCenter/polar_torus_strong_scaling/params_guiding_center.py +++ b/profiling/examples/GuidingCenter/polar_torus_strong_scaling/params_guiding_center.py @@ -38,10 +38,16 @@ help="Runge-Kutta method of the explicit algorithm.", ) parser.add_argument("--epsilon", type=float, default=0.05, help="Normalized cyclotron period (size of the drifts).") -parser.add_argument("--ppc", type=int, default=100, help="Markers per cell.") -parser.add_argument("--num-elements", type=int, nargs=3, default=[16, 18, 16], help="Number of elements per direction.") -parser.add_argument("--dt", type=float, default=0.1, help="Time step.") -parser.add_argument("--tend", type=float, default=200.0, help="End time.") +parser.add_argument("--ppc", type=int, default=25, help="Markers per cell.") +parser.add_argument( + "--num-elements", + type=int, + nargs=3, + default=[16, 48, 48], + help="Number of elements per direction (eta1 is not decomposed; up to 256 processes for the default).", +) +parser.add_argument("--dt", type=float, default=0.2, help="Time step.") +parser.add_argument("--tend", type=float, default=100.0, help="End time.") parser.add_argument("--save-step", type=int, default=20, help="Save (and bin) every save_step time steps.") args, _ = parser.parse_known_args() @@ -53,7 +59,8 @@ toroidal momentum psi_c) is an exact equilibrium; a gyro-Maxwellian with the same profiles in the flux label of the position is not (radial drifts in the torus), it serves as a control. The binned distributions in position (eta1, eta1-eta2) and velocity space (v_parallel, mu, eta1-v_parallel) -are compared with their initial profiles up to t={args.tend} (dt={args.dt}). +are compared with their initial profiles up to t={args.tend} (dt={args.dt}). The density vanishes at the edge r=a, +where markers on drift orbits leaving the plasma are removed; this small loss is not part of the equilibrium. Propagators push_bxe and push_parallel: {args.algo} (Butcher {args.butcher} if explicit), epsilon={args.epsilon}, {args.ppc} markers per cell on a grid with {args.num_elements} elements. """ @@ -104,10 +111,11 @@ time_opts = Time(dt=args.dt, Tend=args.tend, split_algo="LieTrotter") -# Grid (polar splines need a multiple of 3 elements in eta2) +# Grid (polar splines need a multiple of 3 elements in eta2); eta1 is not decomposed, such that the process +# with the pole has all rings of the polar splines and each process has at least degree elements per direction num_elements = tuple(args.num_elements) degree = (3, 3, 3) -grid = grids.TensorProductGrid(num_elements=num_elements, mpi_dims_mask=(True, True, True)) +grid = grids.TensorProductGrid(num_elements=num_elements, mpi_dims_mask=(False, True, True)) # Geometry: full torus without hole, the spline mapping has the resolution of the grid in the poloidal plane a = 1.0 @@ -143,8 +151,9 @@ def n_of_s(s): + # vanishes at the edge: markers on drift orbits that leave the plasma at r=a are removed (not replaced) s = np.clip(s, 0.0, 1.0) - return 0.2 + 0.8 * (1.0 - s) + return (1.0 - s) ** 2 def vth_of_s(s): @@ -187,7 +196,7 @@ def vth_position(*etas): model.kinetic_ions.set_markers( loading_params=LoadingParameters( - ppc=args.ppc, seed=1234, moments=(0.0, 0.0, VTH_AXIS, VTH_AXIS), B0=B0, spatial="disc" + ppc=args.ppc, seed=1234, moments=(0.0, 0.0, VTH_AXIS, VTH_AXIS), B0=B_min, spatial="disc" ), weights_params=WeightsParameters(), boundary_params=BoundaryParameters(bc=("remove", "periodic", "periodic")), diff --git a/profiling/examples/particle_diagnostics.py b/profiling/examples/particle_diagnostics.py index d44c63b52..6564e644f 100644 --- a/profiling/examples/particle_diagnostics.py +++ b/profiling/examples/particle_diagnostics.py @@ -126,17 +126,27 @@ def save_equilibrium_diagnostics(out, species, results_dir=None, exact=None, sca if scalars: fig, ax = plt.subplots(figsize=(7, 4.5)) + ax_abs = None for name in scalars: values = out.scalars[name].values t = out.scalars[name].coords["t"].values - drift = (values - values[0]) / np.abs(values[0]) np.save(os.path.join(results_dir, f"{name}.npy"), values) - ax.plot(t, drift, label=name) + if values[0] != 0.0: + ax.plot(t, (values - values[0]) / np.abs(values[0]), label=name) + else: + # e.g. the number of lost markers: absolute values on a second axis + if ax_abs is None: + ax_abs = ax.twinx() + ax_abs.plot(t, values, "--", label=f"{name} (absolute, right axis)") if time is None: time = t ax.set_xlabel("t") ax.set_ylabel("relative drift") - ax.legend() + handles, labels = ax.get_legend_handles_labels() + if ax_abs is not None: + handles_abs, labels_abs = ax_abs.get_legend_handles_labels() + handles, labels = handles + handles_abs, labels + labels_abs + ax.legend(handles, labels) fig.tight_layout() fig.savefig(os.path.join(results_dir, "scalars.png")) plt.close(fig) From 97235236ccbc0728b9c316de1fc36189844ad0be Mon Sep 17 00:00:00 2001 From: Stefan Possanner Date: Mon, 12 Oct 2026 21:44:39 +0200 Subject: [PATCH 3/4] particle_diagnostics: label the axis of the absolute scalars Co-Authored-By: Claude Opus 5.5 --- profiling/examples/particle_diagnostics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/profiling/examples/particle_diagnostics.py b/profiling/examples/particle_diagnostics.py index 6564e644f..416602203 100644 --- a/profiling/examples/particle_diagnostics.py +++ b/profiling/examples/particle_diagnostics.py @@ -137,6 +137,7 @@ def save_equilibrium_diagnostics(out, species, results_dir=None, exact=None, sca # e.g. the number of lost markers: absolute values on a second axis if ax_abs is None: ax_abs = ax.twinx() + ax_abs.set_ylabel("absolute") ax_abs.plot(t, values, "--", label=f"{name} (absolute, right axis)") if time is None: time = t From ce44c6dba2bd7f98a1b1687768db1bbb04eab9c4 Mon Sep 17 00:00:00 2001 From: Stefan Possanner Date: Tue, 13 Oct 2026 08:38:54 +0200 Subject: [PATCH 4/4] profiling: move submit scripts next to their params files Each submit_*.py now lives in the example folder of the params file it launches, e.g. profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/. The sys.path insert goes one level further up (parents[3]) and params_source is taken from the script's own directory. The ToyGyrokinetic path in the profiling-clusters workflow is updated accordingly. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/reusable-profiling-clusters.yml | 2 +- .../submit_curl_curl_cube_strong_scaling.py | 7 +++---- .../submit_curl_curl_hollow_cylinder_strong_scaling.py | 7 +++---- .../submit_curl_curl_hollow_torus_strong_scaling.py | 7 +++---- .../submit_curl_curl_polar_cylinder_strong_scaling.py | 7 +++---- .../submit_curl_curl_polar_torus_strong_scaling.py | 7 +++---- .../submit_guiding_center_polar_cylinder_strong_scaling.py | 7 +++---- .../submit_guiding_center_polar_torus_strong_scaling.py | 7 +++---- ...t_gyrokinetic_poisson_hollow_cylinder_strong_scaling.py | 7 +++---- ...bmit_gyrokinetic_poisson_hollow_torus_strong_scaling.py | 7 +++---- ...it_gyrokinetic_poisson_polar_cylinder_strong_scaling.py | 7 +++---- ...ubmit_gyrokinetic_poisson_polar_torus_strong_scaling.py | 7 +++---- .../submit_poisson_cube_strong_scaling.py | 7 +++---- .../submit_poisson_hollow_cylinder_strong_scaling.py | 7 +++---- .../submit_poisson_hollow_torus_strong_scaling.py | 7 +++---- .../submit_poisson_polar_cylinder_strong_scaling.py | 7 +++---- .../submit_poisson_polar_torus_strong_scaling.py | 7 +++---- .../submit_diocotron_strong_scaling.py | 7 +++---- .../submit_vlasov_cuboid_strong_scaling.py | 7 +++---- 19 files changed, 55 insertions(+), 73 deletions(-) rename profiling/examples/CurlCurl/{ => cube_strong_scaling}/submit_curl_curl_cube_strong_scaling.py (95%) rename profiling/examples/CurlCurl/{ => hollow_cylinder_strong_scaling}/submit_curl_curl_hollow_cylinder_strong_scaling.py (95%) rename profiling/examples/CurlCurl/{ => hollow_torus_strong_scaling}/submit_curl_curl_hollow_torus_strong_scaling.py (95%) rename profiling/examples/CurlCurl/{ => polar_cylinder_strong_scaling}/submit_curl_curl_polar_cylinder_strong_scaling.py (95%) rename profiling/examples/CurlCurl/{ => polar_torus_strong_scaling}/submit_curl_curl_polar_torus_strong_scaling.py (95%) rename profiling/examples/GuidingCenter/{ => polar_cylinder_strong_scaling}/submit_guiding_center_polar_cylinder_strong_scaling.py (95%) rename profiling/examples/GuidingCenter/{ => polar_torus_strong_scaling}/submit_guiding_center_polar_torus_strong_scaling.py (95%) rename profiling/examples/GyrokineticPoisson/{ => hollow_cylinder_strong_scaling}/submit_gyrokinetic_poisson_hollow_cylinder_strong_scaling.py (95%) rename profiling/examples/GyrokineticPoisson/{ => hollow_torus_strong_scaling}/submit_gyrokinetic_poisson_hollow_torus_strong_scaling.py (95%) rename profiling/examples/GyrokineticPoisson/{ => polar_cylinder_strong_scaling}/submit_gyrokinetic_poisson_polar_cylinder_strong_scaling.py (95%) rename profiling/examples/GyrokineticPoisson/{ => polar_torus_strong_scaling}/submit_gyrokinetic_poisson_polar_torus_strong_scaling.py (95%) rename profiling/examples/Poisson/{ => cube_strong_scaling}/submit_poisson_cube_strong_scaling.py (94%) rename profiling/examples/Poisson/{ => hollow_cylinder_strong_scaling}/submit_poisson_hollow_cylinder_strong_scaling.py (94%) rename profiling/examples/Poisson/{ => hollow_torus_strong_scaling}/submit_poisson_hollow_torus_strong_scaling.py (94%) rename profiling/examples/Poisson/{ => polar_cylinder_strong_scaling}/submit_poisson_polar_cylinder_strong_scaling.py (95%) rename profiling/examples/Poisson/{ => polar_torus_strong_scaling}/submit_poisson_polar_torus_strong_scaling.py (95%) rename profiling/examples/ToyGyrokinetic/{ => diocotron_instability}/submit_diocotron_strong_scaling.py (92%) rename profiling/examples/Vlasov/{ => cuboid_strong_scaling}/submit_vlasov_cuboid_strong_scaling.py (95%) diff --git a/.github/workflows/reusable-profiling-clusters.yml b/.github/workflows/reusable-profiling-clusters.yml index c01ef9870..653f85bcb 100644 --- a/.github/workflows/reusable-profiling-clusters.yml +++ b/.github/workflows/reusable-profiling-clusters.yml @@ -104,7 +104,7 @@ jobs: - name: Submit SLURM job, package and push profiling data run: | source .venv-fortran/bin/activate - python profiling/examples/ToyGyrokinetic/submit_diocotron_strong_scaling.py \ + python profiling/examples/ToyGyrokinetic/diocotron_instability/submit_diocotron_strong_scaling.py \ --language ${{ matrix.compile-language }} \ --compiler GNU \ --upload diff --git a/profiling/examples/CurlCurl/submit_curl_curl_cube_strong_scaling.py b/profiling/examples/CurlCurl/cube_strong_scaling/submit_curl_curl_cube_strong_scaling.py similarity index 95% rename from profiling/examples/CurlCurl/submit_curl_curl_cube_strong_scaling.py rename to profiling/examples/CurlCurl/cube_strong_scaling/submit_curl_curl_cube_strong_scaling.py index 5e789bd8f..5886d380c 100644 --- a/profiling/examples/CurlCurl/submit_curl_curl_cube_strong_scaling.py +++ b/profiling/examples/CurlCurl/cube_strong_scaling/submit_curl_curl_cube_strong_scaling.py @@ -15,8 +15,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -83,7 +83,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "cube_strong_scaling" profiling_case = ProfilingCase( label=label, @@ -91,7 +90,7 @@ def main() -> None: description=description, physics_problem="Occurs in implicit time stepping of Maxwell's equations and in magnetostatics.", struphy_model_used="CurlCurl", - params_source=params_dir / "params_curl_curl.py", + params_source=script_dir / "params_curl_curl.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/CurlCurl/submit_curl_curl_hollow_cylinder_strong_scaling.py b/profiling/examples/CurlCurl/hollow_cylinder_strong_scaling/submit_curl_curl_hollow_cylinder_strong_scaling.py similarity index 95% rename from profiling/examples/CurlCurl/submit_curl_curl_hollow_cylinder_strong_scaling.py rename to profiling/examples/CurlCurl/hollow_cylinder_strong_scaling/submit_curl_curl_hollow_cylinder_strong_scaling.py index bce7d037f..3a34631ca 100644 --- a/profiling/examples/CurlCurl/submit_curl_curl_hollow_cylinder_strong_scaling.py +++ b/profiling/examples/CurlCurl/hollow_cylinder_strong_scaling/submit_curl_curl_hollow_cylinder_strong_scaling.py @@ -15,8 +15,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -83,7 +83,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "hollow_cylinder_strong_scaling" profiling_case = ProfilingCase( label=label, @@ -91,7 +90,7 @@ def main() -> None: description=description, physics_problem="Occurs in implicit time stepping of Maxwell's equations and in magnetostatics.", struphy_model_used="CurlCurl", - params_source=params_dir / "params_curl_curl.py", + params_source=script_dir / "params_curl_curl.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/CurlCurl/submit_curl_curl_hollow_torus_strong_scaling.py b/profiling/examples/CurlCurl/hollow_torus_strong_scaling/submit_curl_curl_hollow_torus_strong_scaling.py similarity index 95% rename from profiling/examples/CurlCurl/submit_curl_curl_hollow_torus_strong_scaling.py rename to profiling/examples/CurlCurl/hollow_torus_strong_scaling/submit_curl_curl_hollow_torus_strong_scaling.py index a034ae4b8..75caf3c5e 100644 --- a/profiling/examples/CurlCurl/submit_curl_curl_hollow_torus_strong_scaling.py +++ b/profiling/examples/CurlCurl/hollow_torus_strong_scaling/submit_curl_curl_hollow_torus_strong_scaling.py @@ -15,8 +15,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -83,7 +83,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "hollow_torus_strong_scaling" profiling_case = ProfilingCase( label=label, @@ -91,7 +90,7 @@ def main() -> None: description=description, physics_problem="Occurs in implicit time stepping of Maxwell's equations and in magnetostatics.", struphy_model_used="CurlCurl", - params_source=params_dir / "params_curl_curl.py", + params_source=script_dir / "params_curl_curl.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/CurlCurl/submit_curl_curl_polar_cylinder_strong_scaling.py b/profiling/examples/CurlCurl/polar_cylinder_strong_scaling/submit_curl_curl_polar_cylinder_strong_scaling.py similarity index 95% rename from profiling/examples/CurlCurl/submit_curl_curl_polar_cylinder_strong_scaling.py rename to profiling/examples/CurlCurl/polar_cylinder_strong_scaling/submit_curl_curl_polar_cylinder_strong_scaling.py index 8ff2176ae..9d69e5a69 100644 --- a/profiling/examples/CurlCurl/submit_curl_curl_polar_cylinder_strong_scaling.py +++ b/profiling/examples/CurlCurl/polar_cylinder_strong_scaling/submit_curl_curl_polar_cylinder_strong_scaling.py @@ -14,8 +14,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -93,7 +93,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "polar_cylinder_strong_scaling" profiling_case = ProfilingCase( label=f"curl_curl_polar_cylinder_strong_scaling_{args.variant}", @@ -105,7 +104,7 @@ def main() -> None: ), physics_problem="Occurs in implicit time stepping of Maxwell's equations and in magnetostatics.", struphy_model_used="CurlCurl", - params_source=params_dir / "params_curl_curl.py", + params_source=script_dir / "params_curl_curl.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/CurlCurl/submit_curl_curl_polar_torus_strong_scaling.py b/profiling/examples/CurlCurl/polar_torus_strong_scaling/submit_curl_curl_polar_torus_strong_scaling.py similarity index 95% rename from profiling/examples/CurlCurl/submit_curl_curl_polar_torus_strong_scaling.py rename to profiling/examples/CurlCurl/polar_torus_strong_scaling/submit_curl_curl_polar_torus_strong_scaling.py index bb0b9315d..c3371e8c0 100644 --- a/profiling/examples/CurlCurl/submit_curl_curl_polar_torus_strong_scaling.py +++ b/profiling/examples/CurlCurl/polar_torus_strong_scaling/submit_curl_curl_polar_torus_strong_scaling.py @@ -14,8 +14,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -93,7 +93,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "polar_torus_strong_scaling" profiling_case = ProfilingCase( label=f"curl_curl_polar_torus_strong_scaling_{args.variant}", @@ -105,7 +104,7 @@ def main() -> None: ), physics_problem="Occurs in implicit time stepping of Maxwell's equations and in magnetostatics.", struphy_model_used="CurlCurl", - params_source=params_dir / "params_curl_curl.py", + params_source=script_dir / "params_curl_curl.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/GuidingCenter/submit_guiding_center_polar_cylinder_strong_scaling.py b/profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/submit_guiding_center_polar_cylinder_strong_scaling.py similarity index 95% rename from profiling/examples/GuidingCenter/submit_guiding_center_polar_cylinder_strong_scaling.py rename to profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/submit_guiding_center_polar_cylinder_strong_scaling.py index 0b061de62..18fcc2c79 100644 --- a/profiling/examples/GuidingCenter/submit_guiding_center_polar_cylinder_strong_scaling.py +++ b/profiling/examples/GuidingCenter/polar_cylinder_strong_scaling/submit_guiding_center_polar_cylinder_strong_scaling.py @@ -17,8 +17,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -80,7 +80,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "polar_cylinder_strong_scaling" profiling_case = ProfilingCase( label=f"guiding_center_polar_cylinder_strong_scaling_{args.variant}", @@ -93,7 +92,7 @@ def main() -> None: ), physics_problem="Long-time guiding-center equilibrium in a screw pinch.", struphy_model_used="GuidingCenter", - params_source=params_dir / "params_guiding_center.py", + params_source=script_dir / "params_guiding_center.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/GuidingCenter/submit_guiding_center_polar_torus_strong_scaling.py b/profiling/examples/GuidingCenter/polar_torus_strong_scaling/submit_guiding_center_polar_torus_strong_scaling.py similarity index 95% rename from profiling/examples/GuidingCenter/submit_guiding_center_polar_torus_strong_scaling.py rename to profiling/examples/GuidingCenter/polar_torus_strong_scaling/submit_guiding_center_polar_torus_strong_scaling.py index f5a3d074f..0e856a554 100644 --- a/profiling/examples/GuidingCenter/submit_guiding_center_polar_torus_strong_scaling.py +++ b/profiling/examples/GuidingCenter/polar_torus_strong_scaling/submit_guiding_center_polar_torus_strong_scaling.py @@ -18,8 +18,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -89,7 +89,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "polar_torus_strong_scaling" profiling_case = ProfilingCase( label=f"guiding_center_polar_torus_strong_scaling_{args.variant}", @@ -102,7 +101,7 @@ def main() -> None: ), physics_problem="Long-time guiding-center equilibrium in a tokamak.", struphy_model_used="GuidingCenter", - params_source=params_dir / "params_guiding_center.py", + params_source=script_dir / "params_guiding_center.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_hollow_cylinder_strong_scaling.py b/profiling/examples/GyrokineticPoisson/hollow_cylinder_strong_scaling/submit_gyrokinetic_poisson_hollow_cylinder_strong_scaling.py similarity index 95% rename from profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_hollow_cylinder_strong_scaling.py rename to profiling/examples/GyrokineticPoisson/hollow_cylinder_strong_scaling/submit_gyrokinetic_poisson_hollow_cylinder_strong_scaling.py index 5d4fa6a3d..f7bbf35f6 100644 --- a/profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_hollow_cylinder_strong_scaling.py +++ b/profiling/examples/GyrokineticPoisson/hollow_cylinder_strong_scaling/submit_gyrokinetic_poisson_hollow_cylinder_strong_scaling.py @@ -15,8 +15,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -77,7 +77,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "hollow_cylinder_strong_scaling" profiling_case = ProfilingCase( label=label, @@ -85,7 +84,7 @@ def main() -> None: description=description, physics_problem="Field equation of gyrokinetic simulations with adiabatic electrons.", struphy_model_used="GyrokineticPoisson", - params_source=params_dir / "params_gyrokinetic_poisson.py", + params_source=script_dir / "params_gyrokinetic_poisson.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_hollow_torus_strong_scaling.py b/profiling/examples/GyrokineticPoisson/hollow_torus_strong_scaling/submit_gyrokinetic_poisson_hollow_torus_strong_scaling.py similarity index 95% rename from profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_hollow_torus_strong_scaling.py rename to profiling/examples/GyrokineticPoisson/hollow_torus_strong_scaling/submit_gyrokinetic_poisson_hollow_torus_strong_scaling.py index 07d57fde6..941066ce1 100644 --- a/profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_hollow_torus_strong_scaling.py +++ b/profiling/examples/GyrokineticPoisson/hollow_torus_strong_scaling/submit_gyrokinetic_poisson_hollow_torus_strong_scaling.py @@ -15,8 +15,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -77,7 +77,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "hollow_torus_strong_scaling" profiling_case = ProfilingCase( label=label, @@ -85,7 +84,7 @@ def main() -> None: description=description, physics_problem="Field equation of gyrokinetic simulations with adiabatic electrons.", struphy_model_used="GyrokineticPoisson", - params_source=params_dir / "params_gyrokinetic_poisson.py", + params_source=script_dir / "params_gyrokinetic_poisson.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_polar_cylinder_strong_scaling.py b/profiling/examples/GyrokineticPoisson/polar_cylinder_strong_scaling/submit_gyrokinetic_poisson_polar_cylinder_strong_scaling.py similarity index 95% rename from profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_polar_cylinder_strong_scaling.py rename to profiling/examples/GyrokineticPoisson/polar_cylinder_strong_scaling/submit_gyrokinetic_poisson_polar_cylinder_strong_scaling.py index 87f2c832e..7bbeae928 100644 --- a/profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_polar_cylinder_strong_scaling.py +++ b/profiling/examples/GyrokineticPoisson/polar_cylinder_strong_scaling/submit_gyrokinetic_poisson_polar_cylinder_strong_scaling.py @@ -14,8 +14,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -93,7 +93,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "polar_cylinder_strong_scaling" profiling_case = ProfilingCase( label=f"gyrokinetic_poisson_polar_cylinder_strong_scaling_{args.variant}", @@ -105,7 +104,7 @@ def main() -> None: ), physics_problem="Field equation of gyrokinetic simulations with adiabatic electrons.", struphy_model_used="GyrokineticPoisson", - params_source=params_dir / "params_gyrokinetic_poisson.py", + params_source=script_dir / "params_gyrokinetic_poisson.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_polar_torus_strong_scaling.py b/profiling/examples/GyrokineticPoisson/polar_torus_strong_scaling/submit_gyrokinetic_poisson_polar_torus_strong_scaling.py similarity index 95% rename from profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_polar_torus_strong_scaling.py rename to profiling/examples/GyrokineticPoisson/polar_torus_strong_scaling/submit_gyrokinetic_poisson_polar_torus_strong_scaling.py index 74570ce17..569e85c69 100644 --- a/profiling/examples/GyrokineticPoisson/submit_gyrokinetic_poisson_polar_torus_strong_scaling.py +++ b/profiling/examples/GyrokineticPoisson/polar_torus_strong_scaling/submit_gyrokinetic_poisson_polar_torus_strong_scaling.py @@ -14,8 +14,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -93,7 +93,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "polar_torus_strong_scaling" profiling_case = ProfilingCase( label=f"gyrokinetic_poisson_polar_torus_strong_scaling_{args.variant}", @@ -105,7 +104,7 @@ def main() -> None: ), physics_problem="Field equation of gyrokinetic simulations with adiabatic electrons.", struphy_model_used="GyrokineticPoisson", - params_source=params_dir / "params_gyrokinetic_poisson.py", + params_source=script_dir / "params_gyrokinetic_poisson.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/Poisson/submit_poisson_cube_strong_scaling.py b/profiling/examples/Poisson/cube_strong_scaling/submit_poisson_cube_strong_scaling.py similarity index 94% rename from profiling/examples/Poisson/submit_poisson_cube_strong_scaling.py rename to profiling/examples/Poisson/cube_strong_scaling/submit_poisson_cube_strong_scaling.py index 6a73610bf..da6ef004a 100644 --- a/profiling/examples/Poisson/submit_poisson_cube_strong_scaling.py +++ b/profiling/examples/Poisson/cube_strong_scaling/submit_poisson_cube_strong_scaling.py @@ -15,8 +15,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -77,7 +77,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "cube_strong_scaling" profiling_case = ProfilingCase( label=label, @@ -85,7 +84,7 @@ def main() -> None: description=description, physics_problem="Occurs in many plasma applications.", struphy_model_used="Poisson", - params_source=params_dir / "params_poisson.py", + params_source=script_dir / "params_poisson.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/Poisson/submit_poisson_hollow_cylinder_strong_scaling.py b/profiling/examples/Poisson/hollow_cylinder_strong_scaling/submit_poisson_hollow_cylinder_strong_scaling.py similarity index 94% rename from profiling/examples/Poisson/submit_poisson_hollow_cylinder_strong_scaling.py rename to profiling/examples/Poisson/hollow_cylinder_strong_scaling/submit_poisson_hollow_cylinder_strong_scaling.py index 61f8356f1..c862f10f5 100644 --- a/profiling/examples/Poisson/submit_poisson_hollow_cylinder_strong_scaling.py +++ b/profiling/examples/Poisson/hollow_cylinder_strong_scaling/submit_poisson_hollow_cylinder_strong_scaling.py @@ -15,8 +15,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -77,7 +77,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "hollow_cylinder_strong_scaling" profiling_case = ProfilingCase( label=label, @@ -85,7 +84,7 @@ def main() -> None: description=description, physics_problem="Occurs in many plasma applications.", struphy_model_used="Poisson", - params_source=params_dir / "params_poisson.py", + params_source=script_dir / "params_poisson.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/Poisson/submit_poisson_hollow_torus_strong_scaling.py b/profiling/examples/Poisson/hollow_torus_strong_scaling/submit_poisson_hollow_torus_strong_scaling.py similarity index 94% rename from profiling/examples/Poisson/submit_poisson_hollow_torus_strong_scaling.py rename to profiling/examples/Poisson/hollow_torus_strong_scaling/submit_poisson_hollow_torus_strong_scaling.py index 39792aba8..df38b4673 100644 --- a/profiling/examples/Poisson/submit_poisson_hollow_torus_strong_scaling.py +++ b/profiling/examples/Poisson/hollow_torus_strong_scaling/submit_poisson_hollow_torus_strong_scaling.py @@ -15,8 +15,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -77,7 +77,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "hollow_torus_strong_scaling" profiling_case = ProfilingCase( label=label, @@ -85,7 +84,7 @@ def main() -> None: description=description, physics_problem="Occurs in many plasma applications.", struphy_model_used="Poisson", - params_source=params_dir / "params_poisson.py", + params_source=script_dir / "params_poisson.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/Poisson/submit_poisson_polar_cylinder_strong_scaling.py b/profiling/examples/Poisson/polar_cylinder_strong_scaling/submit_poisson_polar_cylinder_strong_scaling.py similarity index 95% rename from profiling/examples/Poisson/submit_poisson_polar_cylinder_strong_scaling.py rename to profiling/examples/Poisson/polar_cylinder_strong_scaling/submit_poisson_polar_cylinder_strong_scaling.py index ebcd74cbb..4a4aa54bb 100644 --- a/profiling/examples/Poisson/submit_poisson_polar_cylinder_strong_scaling.py +++ b/profiling/examples/Poisson/polar_cylinder_strong_scaling/submit_poisson_polar_cylinder_strong_scaling.py @@ -14,8 +14,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -93,7 +93,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "polar_cylinder_strong_scaling" profiling_case = ProfilingCase( label=f"poisson_polar_cylinder_strong_scaling_{args.variant}", @@ -105,7 +104,7 @@ def main() -> None: ), physics_problem="Occurs in many plasma applications.", struphy_model_used="Poisson", - params_source=params_dir / "params_poisson.py", + params_source=script_dir / "params_poisson.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/Poisson/submit_poisson_polar_torus_strong_scaling.py b/profiling/examples/Poisson/polar_torus_strong_scaling/submit_poisson_polar_torus_strong_scaling.py similarity index 95% rename from profiling/examples/Poisson/submit_poisson_polar_torus_strong_scaling.py rename to profiling/examples/Poisson/polar_torus_strong_scaling/submit_poisson_polar_torus_strong_scaling.py index 23f7bceff..4d818afc5 100644 --- a/profiling/examples/Poisson/submit_poisson_polar_torus_strong_scaling.py +++ b/profiling/examples/Poisson/polar_torus_strong_scaling/submit_poisson_polar_torus_strong_scaling.py @@ -14,8 +14,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -93,7 +93,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "polar_torus_strong_scaling" profiling_case = ProfilingCase( label=f"poisson_polar_torus_strong_scaling_{args.variant}", @@ -105,7 +104,7 @@ def main() -> None: ), physics_problem="Occurs in many plasma applications.", struphy_model_used="Poisson", - params_source=params_dir / "params_poisson.py", + params_source=script_dir / "params_poisson.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/ToyGyrokinetic/submit_diocotron_strong_scaling.py b/profiling/examples/ToyGyrokinetic/diocotron_instability/submit_diocotron_strong_scaling.py similarity index 92% rename from profiling/examples/ToyGyrokinetic/submit_diocotron_strong_scaling.py rename to profiling/examples/ToyGyrokinetic/diocotron_instability/submit_diocotron_strong_scaling.py index cd0f930ff..7f8d8ab30 100644 --- a/profiling/examples/ToyGyrokinetic/submit_diocotron_strong_scaling.py +++ b/profiling/examples/ToyGyrokinetic/diocotron_instability/submit_diocotron_strong_scaling.py @@ -15,8 +15,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -47,7 +47,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "diocotron_instability" profiling_case = ProfilingCase( label="diocotron_instability", @@ -55,7 +54,7 @@ def main() -> None: description="Scaling test running the diocotron profiling setup with multiple MPI ranks.", physics_problem="Diocotron instability in a non-neutral plasma.", struphy_model_used="ToyDrift", - params_source=params_dir / "params_diocotron.py", + params_source=script_dir / "params_diocotron.py", language=args.language, compiler=args.compiler, upload=args.upload, diff --git a/profiling/examples/Vlasov/submit_vlasov_cuboid_strong_scaling.py b/profiling/examples/Vlasov/cuboid_strong_scaling/submit_vlasov_cuboid_strong_scaling.py similarity index 95% rename from profiling/examples/Vlasov/submit_vlasov_cuboid_strong_scaling.py rename to profiling/examples/Vlasov/cuboid_strong_scaling/submit_vlasov_cuboid_strong_scaling.py index 58518253d..88f98a265 100644 --- a/profiling/examples/Vlasov/submit_vlasov_cuboid_strong_scaling.py +++ b/profiling/examples/Vlasov/cuboid_strong_scaling/submit_vlasov_cuboid_strong_scaling.py @@ -17,8 +17,8 @@ import sys from pathlib import Path -# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, two levels up -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +# the profiling driver (profiling_job.py, clusters.py, ...) lives in profiling/, three levels up +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from profiling_job import ProfilingCase # noqa: E402 @@ -88,7 +88,6 @@ def main() -> None: # Paths relative to this script's location, so it can be run from anywhere. script_dir = Path(__file__).resolve().parent - params_dir = script_dir / "cuboid_strong_scaling" profiling_case = ProfilingCase( label=f"vlasov_cuboid_strong_scaling_{args.variant}", @@ -100,7 +99,7 @@ def main() -> None: ), physics_problem="Long-time Maxwellian equilibrium of charged particles in a uniform magnetic field.", struphy_model_used="Vlasov", - params_source=params_dir / "params_vlasov.py", + params_source=script_dir / "params_vlasov.py", language=args.language, compiler=args.compiler, upload=args.upload,