Skip to content

Commit

Permalink
Release 2.0.5 -- Merge pull request #147 from n-claes/develop
Browse files Browse the repository at this point in the history
Minor release: 2.0.5
  • Loading branch information
n-claes authored Jun 20, 2023
2 parents 104dee2 + 1f4b4b1 commit e23e276
Show file tree
Hide file tree
Showing 43 changed files with 1,103 additions and 302 deletions.
2 changes: 1 addition & 1 deletion docs/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ and parametric studies, including a myriad of physical effects.

Involved with everything & provides overall guidance. Responsible for maintenance and general development of both Legolas and Pylbo.

- **Drs. Jordi De Jonghe** ([<i class="fas fa-envelope" aria-hidden="true"></i>](mailto:[email protected])): development team.
- **Dr. Jordi De Jonghe** ([<i class="fas fa-envelope" aria-hidden="true"></i>](mailto:[email protected])): development team.

Mainly involved with resistive/viscous/Hall MHD, along with source code contributions to both Legolas and Pylbo.

Expand Down
11 changes: 5 additions & 6 deletions docs/general/parameter_file.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ This namelist includes all physics-related variables.
| flow | logical | inclusion of background flow effects | `.false.` |
| radiative_cooling | logical | whether to include optically thin radiative losses | `.false.` |
| ncool | int | number of points used when interpolating cooling curves | 4000 |
| cooling_curve | string | which cooling curve to use, can be `{"jc_corona", "dalgarno", "dalgarno2", "ml_solar", "spex", "spex_dalgarno", "rosner", "colgan", "colgan_dm"}` | `"jc_corona"` |
| cooling_curve | string | which cooling curve to use, can be `{"nothing", "jc_corona", "dalgarno", "dalgarno2", "ml_solar", "spex", "spex_dalgarno", "rosner", "colgan", "colgan_dm"}`. In the case of `"nothing"` you should define your own $\Lambda(T)$ cooling function and its temperature derivative. | `"nothing"` |
| heating | logical | whether to include background heating | `.false.` |
| force_thermal_balance | logical | whether to set the heating in such a way to enforce thermal equilibrium | `.true.` |
| external_gravity | logical | whether to include external gravity | `.false.` |
Expand Down Expand Up @@ -86,7 +86,7 @@ This namelist includes all solver-related variables. For more information, see [

| Parameter | Type | Description | Default value |
| :--- | :---: | :---- | :---: |
| solver | string | which solver to use, can be `{"none", "arnoldi", "QR-invert", "QR-cholesky", "QZ-direct", "inverse-it"}` | `"QR-invert"` |
| solver | string | which solver to use, can be `{"none", "arnoldi", "QR-invert", "QR-cholesky", "QZ-direct", "inverse-iteration"}` | `"QR-invert"` |
| arpack_mode | string | the mode for arpack (Arnoldi) | `"general"` |
| which_eigenvalues | string | which eigenvalues to calculate (Arnoldi). Can be `{"LM", "SM", "LR", "SR", "LI", "SI"}` | `"LM"` |
| number_of_eigenvalues | int | number of eigenvalues ($k$) to calculate (Arnoldi) | 10 |
Expand Down Expand Up @@ -127,9 +127,9 @@ The level of output is controlled through the integer `logging_level`:

| Parameter | Type | Description | Default value |
| :--- | :---: | :---- | :---: |
| write_matrices | logical | whether to write the matrices to the datfile | `.false.` |
| write_matrices | logical | whether to write the matrices to the datfile | `.false.` |
| write_eigenvectors | logical | whether to write the eigenvectors to the datfile | `.false.` |
| write_residuals | logial | whether to write the residuals to the datfile | `.false.` |
| write_residuals | logical | whether to write the residuals to the datfile | `.false.` |
| write_background | logical | whether to write the background to the datfile | `.true.` |
| write_eigenfunctions | logical | whether to write the eigenfunctions to the datfile | `.true.` |
| write_derived_eigenfunctions | logical | whether to write the derived eigenfunctions to the datfile | `.false.` |
Expand All @@ -143,8 +143,7 @@ The level of output is controlled through the integer `logging_level`:


## paramlist
This namelist handles constant parameters which are used in the equilibrium prescriptions, This is meant to be used in parameter studies, for a comprehensive list of all
the possible parameters we refer to [this page](../../ford/module/mod_equilibrium_params.html) in the
This namelist handles constant parameters which are used in the equilibrium prescriptions. This is meant to be used in parameter studies, for a comprehensive list of all the possible parameters we refer to [this page](../../ford/module/mod_equilibrium_params.html) in the
Legolas source code documentation. To see which parameters are used in which equilibria, take a look
[here](../equilibria/).

Expand Down
15 changes: 2 additions & 13 deletions legolas_config.par
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,15 @@
/

&solvelist
solver = "QR-invert"
/

&physicslist
! flow = .true.
solver = "QR-cholesky"
/

&equilibriumlist
equilibrium_type = "suydam_cluster"
! equilibrium_type = "user_defined"
use_defaults = .true.
/

&savelist
show_results = .true.
write_eigenfunctions = .true.
write_derived_eigenfunctions = .true.
/

&unitslist
/

&paramlist
/
1 change: 1 addition & 0 deletions post_processing/pylbo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pylbo.automation.api import generate_parfiles, run_legolas
from pylbo.utilities import logger
from pylbo.utilities.datfiles.file_loader import load, load_logfile, load_series
from pylbo.utilities.eq_balance import get_equilibrium_balance
from pylbo.utilities.logger import disable_logging, set_loglevel
from pylbo.visualisation.api import (
plot_continua,
Expand Down
2 changes: 1 addition & 1 deletion post_processing/pylbo/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from packaging import version

# The current pylbo version number.
__version__ = "2.0.0"
__version__ = "2.0.2"


class VersionHandler:
Expand Down
41 changes: 35 additions & 6 deletions post_processing/pylbo/automation/api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
from __future__ import annotations

import os
from typing import Union

from pylbo.automation.generator import ParfileGenerator
from pylbo.automation.runner import LegolasRunner


def generate_parfiles(parfile_dict, basename=None, output_dir=None, subdir=True):
def generate_parfiles(
parfile_dict: dict,
basename: str = None,
output_dir: Union[str, os.PathLike] = None,
subdir: bool = True,
prefix_numbers: bool = True,
nb_prefix_digits: int = 4,
) -> list[str]:
"""
Generates parfiles based on a given configuration dictionary.
The separate namelists do not have to be taken into account, and a normal
Expand All @@ -24,7 +36,13 @@ def generate_parfiles(parfile_dict, basename=None, output_dir=None, subdir=True)
working directory if not specified. A subdirectory called `parfiles` will be
created in which the parfiles will be saved.
subdir : boolean
If `.true.` (default), creates a subdirectory `parfiles` in the output folder.
If `True` (default), creates a subdirectory `parfiles` in the output folder.
prefix_numbers : boolean
If `True` prepends the `basename` by a n-digit number (e.g. xxxxmyparfile.par).
The number of digits is specified by `nb_prefix_digits`.
nb_prefix_digits : int
Number of digits to prepend to the `basename` if `prefix_numbers` is `True`.
Defaults to 4.
Notes
-----
Expand Down Expand Up @@ -76,13 +94,24 @@ def generate_parfiles(parfile_dict, basename=None, output_dir=None, subdir=True)
>>> }
>>> parfile_list = pylbo.generate_parfiles(config, output_dir="my_parfiles")
"""
pfgen = ParfileGenerator(parfile_dict, basename, output_dir, subdir)
pfgen = ParfileGenerator(
parfile_dict=parfile_dict,
basename=basename,
output_dir=output_dir,
subdir=subdir,
prefix_numbers=prefix_numbers,
nb_prefix_digits=nb_prefix_digits,
)
pfgen.create_namelist_from_dict()
parfiles = pfgen.generate_parfiles()
return parfiles
return pfgen.generate_parfiles()


def run_legolas(parfiles, remove_parfiles=False, nb_cpus=1, executable=None):
def run_legolas(
parfiles: Union[str, list, os.PathLike],
remove_parfiles: bool = False,
nb_cpus: int = 1,
executable: Union[str, os.PathLike] = None,
) -> None:
"""
Runs the legolas executable for a given list of parfiles. If more than one parfile
is passed, the runs can be performed in parallel using the multiprocessing module.
Expand Down
98 changes: 76 additions & 22 deletions post_processing/pylbo/automation/generator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import annotations

import copy
from pathlib import Path
from typing import Union

import f90nml
from pylbo.automation.defaults import namelist_items
Expand All @@ -8,24 +11,56 @@
from pylbo.utilities.toolbox import transform_to_list


def _validate_basename(basename):
def _ensure_nb_names_and_nb_runs_matches(
names: Union[str, list[str]], nb_runs: int
) -> list[str]:
"""
Ensures that the number of names matches the number of runs.
Parameters
----------
names : str, list[str]
The basename(s) for the parfile(s).
nb_runs : int
Number of runs for which the parfiles are generated.
Returns
-------
names : list[str]
The basename(s) for the parfile(s).
"""
names = transform_to_list(names)
if len(names) == 1:
names = names * nb_runs
elif len(names) != nb_runs:
raise ValueError(
f"Number of parfile names ({len(names)}) does not match number of runs "
f"({nb_runs})."
)
return names


def _validate_basenames(basenames: list[str]) -> list[str]:
"""
Validates the basename for a given parfile.
Validates the basenames for given parfiles.
Parameters
----------
basename : str
The basename for a parfile. If not given, defaults to "parfile".
basename : list[str]
The basenames for the parfiles. If not given, defaults to "parfile[i]".
Returns
-------
basename : str
basename : list[str]
The basename for a parfile.
"""
if basename is None:
basename = "parfile"
return basename
if len(basenames) == 1:
return ["parfile"] if basenames[0] is None else basenames
for i, name in enumerate(basenames):
basenames[i] = f"parfile{i + 1:04d}" if name is None else name
return basenames


def _validate_output_dir(output_dir, subdir):
Expand Down Expand Up @@ -71,24 +106,41 @@ class ParfileGenerator:
----------
parfile_dict : dict
Dictionary containing the keys to be placed in the parfile.
basename : str
basename : str, list[str]
The basename for the parfile, the `.par` suffix is added automatically and is
not needed. If multiple parfiles are generated, these
will be prepended by a 4-digit number (e.g. 0003myparfile.par).
If not provided, the basename will default to `parfile`.
not needed. If not provided, the basename will default to `parfile`.
Can be a list of names as well if multiple parfiles are being generated.
output_dir : str, ~os.PathLike
Output directory where the parfiles are saved, defaults to the current
working directory if not specified.
subdir : boolean
If `.true.` (default), creates a subdirectory under `output_dir` called
If `True` (default), creates a subdirectory under `output_dir` called
`parfiles` in which the parfiles will be saved.
prefix_numbers : boolean
If `True` prepends the `basename` by a n-digit number (e.g. xxxxmyparfile.par).
The number of digits is specified by `nb_prefix_digits`.
nb_prefix_digits : int
Number of digits to prepend to the `basename` if `prefix_numbers` is `True`.
Defaults to 4.
"""

def __init__(self, parfile_dict, basename=None, output_dir=None, subdir=True):
def __init__(
self,
parfile_dict,
basename=None,
output_dir=None,
subdir=True,
prefix_numbers=True,
nb_prefix_digits=4,
):
self.parfile_dict = copy.deepcopy(parfile_dict)
self.basename = _validate_basename(basename)
self.output_dir = _validate_output_dir(output_dir, subdir)
self.nb_runs = self.parfile_dict.pop("number_of_runs", 1)

names = _ensure_nb_names_and_nb_runs_matches(basename, self.nb_runs)
self.basenames = _validate_basenames(transform_to_list(names))
self.output_dir = _validate_output_dir(output_dir, subdir)
self._use_prefix = prefix_numbers
self._nb_prefix_digits = nb_prefix_digits
self.parfiles = []
self.container = {}

Expand Down Expand Up @@ -198,18 +250,20 @@ def generate_parfiles(self):
run_dict.update({"savelist": {}})

for current_run in range(self.nb_runs):
prefix = "{:04d}".format(current_run + 1)
if self.nb_runs == 1:
prefix = ""
prefix = (
f"{current_run + 1:0{self._nb_prefix_digits}d}"
if self._use_prefix and self.nb_runs > 1
else ""
)
# generate dictionary for this specific run
for namelist, items in self.container.items():
for key, values in items.items():
run_dict[namelist].update({key: values[current_run]})
# parfile name
parfile_name = f"{prefix}{self.basename}.par"
basename = self.basenames[current_run]
parfile_name = f"{prefix}{basename}.par"
# datfile name (no extension .dat needed)
datfile_name = (
f"{prefix}{run_dict['savelist'].get('basename_datfile', self.basename)}"
f"{prefix}{run_dict['savelist'].get('basename_datfile', basename)}"
)
run_dict["savelist"].update({"basename_datfile": datfile_name})
# set paths and write parfile
Expand Down
16 changes: 7 additions & 9 deletions post_processing/pylbo/automation/runner.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import multiprocessing
import os
import subprocess
import signal
import multiprocessing
import psutil
import tqdm
import subprocess
from pathlib import Path

import psutil
import tqdm
from pylbo.utilities.logger import pylboLogger
from pylbo.utilities.toolbox import transform_to_list

Expand All @@ -32,8 +32,7 @@ def _validate_executable(executable):
"""
if executable is None:
exec_dir = Path(__file__).parents[3]
executable = (exec_dir / "legolas").resolve()
raise TypeError("No executable was specified.")
executable = Path(executable).resolve()
if not executable.is_file():
raise FileNotFoundError(f"Executable was not found: {executable}")
Expand Down Expand Up @@ -111,11 +110,10 @@ class LegolasRunner:
parallelisation is disabled. Defaults to the maximum number of CPUs available
if a number larger than the available number is specified.
executable : str, ~os.PathLike
The path to the legolas executable. If not specified, defaults to the
standard one in the legolas home directory.
The path to the legolas executable.
"""

def __init__(self, parfiles, remove_parfiles, nb_cpus, executable):
def __init__(self, parfiles, remove_parfiles, nb_cpus, executable=None):
self.parfiles = _validate_parfiles(parfiles)
self.parfile_dir = self.parfiles[0].parent
self.executable = _validate_executable(executable)
Expand Down
21 changes: 21 additions & 0 deletions post_processing/pylbo/data_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def __init__(self, datfile):
self.cgs = self.units["cgs"]
self.eq_names = self.header["equilibrium_names"]

self._ensure_compatibility()
self._continua = calculate_continua(self)

def __iter__(self):
Expand Down Expand Up @@ -288,6 +289,26 @@ def has_residuals(self) -> bool:
"""Checks if residuals are present."""
return self.header["has_residuals"]

@property
def is_mhd(self) -> bool:
"""Checks if the dataset is MHD."""
return "mhd" in self.header.get("physics_type", None) and any(
self.equilibria["B0"] != 0
)

def _ensure_compatibility(self) -> None:
"""
Makes sure that the dataset is backwards compatible with new changes.
Mainly used for older (<2.0.0) datasets.
"""
if not self.has_background:
return
bg_keys_added_in_v200 = ["L0"]
for key in bg_keys_added_in_v200:
if self.equilibria.get(key, None) is None:
pylboLogger.debug(f"added '{key}' to equilibrium of '{self.datfile}'")
self.equilibria[key] = np.zeros_like(self.grid_gauss)

def get_sound_speed(self, which_values=None) -> Union[float, np.ndarray]:
"""
Calculates the sound speed based on the equilibrium arrays,
Expand Down
2 changes: 2 additions & 0 deletions post_processing/pylbo/utilities/datfiles/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ def _read_equilibrium_names(self, istream: BinaryIO) -> dict:
if self.data["has_background"]
else []
)
# due to a typo older datfiles contain 'db03' instead of 'dB03'
names = [name.replace("db03", "dB03") for name in names]
return {"equilibrium_names": names}

def _get_eigenfunction_offsets(self, istream: BinaryIO) -> dict:
Expand Down
Loading

0 comments on commit e23e276

Please sign in to comment.