Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ Top level
Name Type Default Description
=================== ======= ========= ==========================================================
``default_cocos`` int 3 COCOS convention assumed when the input does not specify one.
``debug_plots`` bool false Draw (blocking) matplotlib debug plots when the equilibrium
initialization fails. Keep disabled in headless environments.
=================== ======= ========= ==========================================================

``[grid]`` — default computational grids
Expand Down Expand Up @@ -267,3 +269,34 @@ Old name New name
700/1200"); the new fields default to 700/1200 directly. Unprefixed
environment variables (e.g. ``NPSI_GRID``) are no longer read — use the
``PLEQUE_`` prefix.


.. _logging:

Logging
-------

PLEQUE logs through the standard :mod:`logging` machinery under the
``pleque`` logger hierarchy (one logger per module, e.g.
``pleque.core.equilibrium``). As a library, PLEQUE does not configure any
handler by default; to see the messages either use the convenience helper

.. code-block:: python

import logging
import pleque

pleque.set_log_level(logging.DEBUG) # or logging.INFO

or configure the logger yourself:

.. code-block:: python

import logging

logging.basicConfig()
logging.getLogger("pleque").setLevel(logging.INFO)

The ``verbose=True`` argument of :class:`pleque.Equilibrium` is kept for
backward compatibility and simply calls
``pleque.set_log_level(logging.DEBUG)``.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ PLEQUE is a code which allows easy and quick access to tokamak equilibria obtain

first_steps
examples
initialization
coordinates
flux_expansion
naming_convention
Expand Down
75 changes: 75 additions & 0 deletions docs/source/initialization.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
Equilibrium initialisation
==========================

The central object of PLEQUE is the :class:`pleque.Equilibrium` class. It is
usually constructed by one of the readers in :mod:`pleque.io` (e.g.
:func:`pleque.io.readers.read_geqdsk`), which build an ``xarray.Dataset`` with
the poloidal flux function :math:`\psi(R, Z)` on a rectangular grid and 1D
profiles of the toroidal field function :math:`F` and pressure :math:`p` (or
their :math:`\psi`-derivatives ``FFprime`` and ``pprime``) as functions of
:math:`\psi_\mathrm{N}`.

Initialisation sequence
-----------------------

#. **Input parsing.** Spatial data, profiles and optional hints are read from
the dataset and constructor arguments. If no first wall is provided, an
artificial rectangular wall slightly inset with respect to the
:math:`\psi`-grid is synthesized (``grid.synthetic_wall_margin`` and
``grid.synthetic_wall_points_per_side`` settings).
#. **2D spline construction.** :math:`\psi(R, Z)` is interpolated by a
bivariate spline (``splines.psi_order`` / ``splines.psi_smooth`` settings).
#. **Critical points.** Candidate extremes of :math:`|\nabla\psi|^2` are
located on a regular grid and classified by the determinant of the Hessian
of :math:`\psi` into O-points (local extrema) and X-points (saddle points).
The magnetic axis and the relevant X-points are recognized among the
candidates, and the plasma configuration (limiter vs. diverted) is
determined together with the limiter point and :math:`\psi` on the last
closed flux surface.
#. **Plasma boundary.** Strike points are found as intersections of the
LCFS-:math:`\psi` contour with the first wall. The LCFS contour is located
by the marching-squares algorithm on a search grid and refined by an
iterative downhill (gradient-step) method until the relative :math:`\psi`
error drops below ``lcfs.refinement_tolerance``; for diverted plasmas the
X-point is inserted into the contour. Poloidal field-line tracing of the
boundary is *not* used during the initialisation; it is available
separately via :meth:`pleque.Equilibrium.lcfs_field_line`.
#. **1D profile splines.** If derivatives (``pprime``, ``FFprime``) are
provided — the preferred input, as it avoids differentiating noisy data —
the :math:`p` and :math:`F` profiles are obtained by integration (this
requires :math:`\psi_\mathrm{axis}` and :math:`\psi_\mathrm{LCFS}`, which
is why this step runs after the critical-point search). Otherwise the
derivatives are computed from the provided profiles. If neither pressure
nor :math:`F` is available, a vacuum equilibrium (:math:`p = F = 0`) is
constructed.
#. **Midplane mapping.** A 1D spline mapping the outer-midplane radius to
:math:`\psi` is built.

Hints and ``init_method``
-------------------------

The optional arguments ``mg_axis``, ``psi_lcfs``, ``x_points`` and
``strike_points`` act as *hints*. Each may alternatively be provided as a
dataset variable of the same name; an explicitly passed argument takes
precedence (with a logged warning). The ``init_method`` argument controls how
the hints are used:

``"full"``
All hints are ignored; the module recognizes all critical points itself.
``"hints"`` (default)
The hints assist the recognition of the magnetic axis and X-points.
``"fast_forward"``
The ``mg_axis`` and ``x_points`` hints are taken as final and the
critical-point search is skipped; ``psi_lcfs`` and ``strike_points``
hints are likewise used as final when given. If the required hints are
missing, the initialisation falls back to ``"hints"`` with a logged
warning.

Diagnostics
-----------

PLEQUE logs through the standard :mod:`logging` machinery under the
``pleque`` logger; see :ref:`logging` for how to enable it. If the
initialisation fails, the exception is logged and re-raised; enabling the
``debug_plots`` setting (e.g. ``PLEQUE_DEBUG_PLOTS=1``) additionally draws a
debug plot of the partially initialised equilibrium.
32 changes: 31 additions & 1 deletion pleque/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
import logging

__version__ = '0.0.10'

from pleque.core import *
logging.getLogger(__name__).addHandler(logging.NullHandler())

_LOG_HANDLER_ATTR = "_pleque_handler"


def set_log_level(level=logging.INFO):
"""
Convenience helper to make PLEQUE log messages visible.

Sets the level of the ``pleque`` logger and attaches a single
:class:`logging.StreamHandler` to it (idempotent — repeated calls reuse
the same handler). For full control configure the ``pleque`` logger
directly with the standard :mod:`logging` machinery instead.

:param level: logging level, e.g. ``logging.DEBUG`` (default ``logging.INFO``)
"""
logger = logging.getLogger(__name__)
logger.setLevel(level)

handler = next((h for h in logger.handlers if getattr(h, _LOG_HANDLER_ATTR, False)), None)
if handler is None:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(levelname)s:%(name)s: %(message)s"))
setattr(handler, _LOG_HANDLER_ATTR, True)
logger.addHandler(handler)
handler.setLevel(level)


from pleque.core import * # noqa: E402
5 changes: 5 additions & 0 deletions pleque/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,11 @@ class Settings(BaseSettings):
)

default_cocos: int = Field(3, description="COCOS convention assumed when the input does not specify one.")
debug_plots: bool = Field(
False,
description="Draw (blocking) matplotlib debug plots when the equilibrium initialization fails. "
"Keep disabled in headless environments; enable e.g. with PLEQUE_DEBUG_PLOTS=1.",
)
grid: GridSettings = Field(default_factory=GridSettings)
splines: SplineSettings = Field(default_factory=SplineSettings)
critical_points: CriticalPointSettings = Field(default_factory=CriticalPointSettings)
Expand Down
33 changes: 15 additions & 18 deletions pleque/core/coordinates.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import warnings
from collections.abc import Iterable
from typing import ClassVar, Union
Expand All @@ -10,6 +11,8 @@

from .cocos import cocos_coefs

logger = logging.getLogger(__name__)

#: Canonical description of the coordinate input syntax accepted across PLEQUE.
#: It is appended (via :func:`pleque.utils.decorators.append_to_doc`) to the
#: docstrings of :meth:`Coordinates.from_coords` and
Expand Down Expand Up @@ -244,8 +247,8 @@ def _init_fast_path(self, coordinates, coord_type, coords):
self.x3 = self._x3_input = xs[2]

if self.grid and n != 2:
print('WARNING: grid == True is not allowed for dim != 2 (yet).'
'Turning grid = False.')
logger.warning('grid == True is not allowed for dim != 2 (yet).'
'Turning grid = False.')
self.grid = False

return True
Expand Down Expand Up @@ -723,8 +726,8 @@ def _evaluate_input(self, *coordinates, coord_type=None, **coords):
xy = coordinates[0]

if self.grid:
print('WARNING: grid == True is not allowed for this coordinates input. '
'Turning grid = False.')
logger.warning('grid == True is not allowed for this coordinates input. '
'Turning grid = False.')
self.grid = False

if isinstance(xy, Iterable):
Expand Down Expand Up @@ -779,8 +782,8 @@ def _evaluate_input(self, *coordinates, coord_type=None, **coords):
self._convert_to_default_coord_type()

if self.dim != 2 and self.grid:
print('WARNING: grid == True is not allowed for dim != 2 (yet).'
'Turning grid = False.')
logger.warning('grid == True is not allowed for dim != 2 (yet).'
'Turning grid = False.')
self.grid = False

def _verify_coord_type(self, coord_type):
Expand All @@ -796,10 +799,8 @@ def _verify_coord_type(self, coord_type):
ret_coord_type = tuple(coord_type)
else:
ret_coord_type = ('psi_n',)
print("WARNING: _coord_type_input is not correct. \n"
f"{tuple(coord_type)} is not allowed \n"
"Force set _coord_type_input = ('psi_n',)"
)
logger.warning("_coord_type_input is not correct. %s is not allowed. "
"Force set _coord_type_input = ('psi_n',)", tuple(coord_type))
elif self.dim == 2:
if coord_type is None:
ret_coord_type = ('R', 'Z')
Expand All @@ -809,21 +810,17 @@ def _verify_coord_type(self, coord_type):
ret_coord_type = tuple(coord_type[::-1])
else:
ret_coord_type = ('R', 'Z')
print("WARNING: _coord_type_input is not correct. \n"
f"{tuple(coord_type)} is not allowed \n"
"Force set _coord_type_input = ('R', 'Z')"
)
logger.warning("_coord_type_input is not correct. %s is not allowed. "
"Force set _coord_type_input = ('R', 'Z')", tuple(coord_type))
elif self.dim == 3:
if coord_type is None:
ret_coord_type = ('R', 'Z', 'phi')
elif tuple(coord_type) in self._valid_coordinates_3d:
ret_coord_type = tuple(coord_type)
else:
ret_coord_type = ('R', 'Z', 'phi')
print("WARNING: _coord_type_input is not correct. \n"
f"{tuple(coord_type)} is not allowed \n"
"Force set _coord_type_input = ('R', 'Z', 'phi')"
)
logger.warning("_coord_type_input is not correct. %s is not allowed. "
"Force set _coord_type_input = ('R', 'Z', 'phi')", tuple(coord_type))

else:
raise ValueError(f'Operation in {self.dim} space has not be en allowed yet. Sorry.'
Expand Down
Loading
Loading