Source code for torx.equilibrium.eqdsk_io_m

"""Contains functionality to write eqdsk equilibrium files."""
import numpy as np
from pathlib import Path
from warnings import warn
from typing import Union
from fortranformat import FortranRecordWriter
from torx.autodoc_decorators_m import autodoc_function

_required_keys = ["nw", "nh", "rdim", "zdim", "rcentr", "rleft", "zmid",
                  "rmaxis", "zmaxis", "simag", "sibry", "bcentr", "current",
                  "fpol", "pres", "ffprim", "pprime", "psirz", "qpsi",
                  "nbbbs", "limitr", "rbbbs", "zbbbs", "rlim", "zlim"]

[docs] @autodoc_function def write_eqdsk_file(filepath: Path, content: dict): """ Create an eqdsk equilibrium file with path and name given by filepath. The content will be extracted from the dictionary provided. The presence of all required quantities is assumed (see eqdsk documentation), except the "idum" and "xdum" variables (dummies) and "case". The "case" variable is built automatically from keys "name", "shot", "time" and "comment". These are assumed to consist of 8 chars each (24 for comment) and are truncated if longer. Example for "case": name = "AUG" shot = "30000" time = "1.0" comment = "generated for testing" """ _check_content(content) _check_dimensions(content) case = _create_case(content) idum = 3 xdum = 0.0 f2000 = FortranRecordWriter("(6a8,3i4)") f2020 = FortranRecordWriter("(5e16.9)") f2022 = FortranRecordWriter("(2i5)") with open(filepath, "w") as filew: # Header line = f2000.write(case + [idum, content["nw"], content["nh"]]) filew.write(line + "\n") # Block with scalars line = f2020.write([content["rdim"], content["zdim"], \ content["rcentr"], content["rleft"], \ content["zmid"]]) filew.write(line + "\n") line = f2020.write([content["rmaxis"], content["zmaxis"], \ content["simag"], content["sibry"], \ content["bcentr"]]) filew.write(line + "\n") line = f2020.write([content["current"], content["simag"], xdum, \ content["rmaxis"], xdum]) filew.write(line + "\n") line = f2020.write([content["zmaxis"], xdum, content["sibry"], \ xdum, xdum]) filew.write(line + "\n") # Block with arrays line = f2020.write(content["fpol"]) filew.write(line + "\n") line = f2020.write(content["pres"]) filew.write(line + "\n") line = f2020.write(content["ffprim"]) filew.write(line + "\n") line = f2020.write(content["pprime"]) filew.write(line + "\n") # NOTE: Psi needs to be in row major ordering psi = content["psirz"].flatten(order="C") line = f2020.write(psi) filew.write(line + "\n") line = f2020.write(content["qpsi"]) filew.write(line + "\n") # Block with boundary and limiter line = f2022.write([content["nbbbs"], content["limitr"]]) filew.write(line + "\n") boundary = np.stack([content["rbbbs"], content["zbbbs"]]\ ).flatten(order="C") line = f2020.write(boundary) filew.write(line + "\n") limiter = np.stack([content["rlim"], content["zlim"]]\ ).flatten(order="C") line = f2020.write(limiter) filew.write(line + "\n")
def _create_case(content: dict): """ Create the variable case which consists of a 6 element string array. The elements may contain more than the allowed 8 characters, since it is assumed that the strings will be truncated when writing. A warning will be given for each element that is longer than 8 characters. The content of case will be determined based on the fields "name", "shot", "time" and "comment" in the content dict. From comment, 23 characters will be taken, a space will be prepended. """ case = [""] * 6 keys = content.keys() if "name" in keys: case[0] = content["name"] if "shot" in keys: case[1] = content["shot"] if "time" in keys: case[2] = content["time"] if "comment" in keys: case[3] = "" + content["comment"][0:7] case[4] = content["comment"][7:15] case[5] = content["comment"][15:] for i, c in enumerate(case): if len(c) > 8: warn(f"Case entry {i} which is {c} contains more " \ + "characters than 8 and will be truncated!") case[i] = c[0:8] return case def _check_content(content: dict): """Check the content dictionary if all required keys are present.""" for key in _required_keys: assert key in content.keys(), f"Required key {key} not found!" def _check_dimensions(content: dict): """ Check for all required keys in the content dictionary if they are valid. Validity is described in a separate check array function. """ nw_arrays = ["fpol", "pres", "ffprim", "pprime", "qpsi"] for na in nw_arrays: _check_array(content, na, "nw") _check_array(content, "psirz", ("nw", "nh")) _check_array(content, "rbbbs", "nbbbs") _check_array(content, "zbbbs", "nbbbs") _check_array(content, "rlim", "limitr") _check_array(content, "zlim", "limitr") skip = nw_arrays + ["psirz", "rbbbs", "zbbbs", "rlim", "zlim", \ "name", "shot", "time", "comment"] for key in content.keys(): if key in skip: continue _check_array(content, key, "1") def _check_array(content: dict, array_name: str, dim_name: Union[str, tuple]): """ Check a single array contained in the content dictionary for validity. This includes size and dimensionality checks against the dimension specified in dim_name. For scalars dim_name should be "1". For 2D arrays, a tuple of 2 strings is expected. """ array = content[array_name] assert (isinstance(array, np.ndarray) or isinstance(array, (int, float))), \ f"Array {array_name} must be of float or array type, was {type(array)}!" if isinstance(dim_name, tuple): assert len(dim_name) == 2, \ f"For dim tuple the expected length is 2, was {len(dim_name)}!)" dim = content[dim_name[0]] * content[dim_name[1]] ndim = 2 shape = (content[dim_name[0]], content[dim_name[1]]) assert array.shape == shape, \ f"Shape of {array_name} does not match {shape}!" elif dim_name == "1": dim = 1 ndim = 0 else: dim = content[dim_name] ndim = 1 if not isinstance(array, np.ndarray): array = np.array(array) assert len(array.shape) >= 0, f"Quantity {array_name} must be >= 0D!" assert len(array.shape) <= 2, f"Quantity {array_name} must be <= 2D!" assert len(array.shape) == ndim, f"Quantity {array_name} must be {ndim}D!" assert array.size == dim, f"Size of {array_name} does not match {dim_name}!"