#!/usr/bin/env python
"""Abstract base classes for Control objects, which govern the iteration of Z2Pack runs."""
import abc
import types
__all__ = [
"ControlContainer",
"AbstractControl",
"StatefulControl",
"DataControl",
"IterationControl",
"ConvergenceControl",
"VolumeControl",
"SurfaceControl",
"LineControl",
]
[docs]class ControlContainer(types.SimpleNamespace):
"""
Container for controls, giving simple access to the different types of controls.
"""
def __init__(
self, *, controls, categories, valid_type
): # pylint: disable=missing-function-docstring
self.all = controls
for ctrl in self.all:
if not isinstance(ctrl, valid_type):
raise ValueError(
f"Invalid type '{type(ctrl)}' of control, should be '{valid_type}'."
)
for name, ctrl_types in categories.items():
setattr(
self,
name,
[
ctrl
for ctrl in controls
if all(isinstance(ctrl, ctrl_t) for ctrl_t in ctrl_types)
],
)
[docs]class AbstractControl(metaclass=abc.ABCMeta):
"""ABC for all control objects. Instances must also have a 'state' attribute to work correctly, which is not enforced by the ABC."""
[docs]class StatefulControl(AbstractControl):
"""
ABC for control objects which have a state. The state must not depend on the given convergence parameters.
**Concepts:**
`Constructor:` ``StatefulControl(state=s).state == s`` for any valid state s.
`State:` The state must be sufficient to uniquely determine the behaviour of the Control, for a given set of input parameters of the constructor. That is, given two equivalent StatefulControl objects, when applying
.. code :: python
sc1 = StatefulControl(*args, **kwargs)
sc2 = StatefulControl(*args, **kwargs)
...working with sc1 and/or sc2...
sc2.state = sc1.state
``sc1`` and ``sc2`` are again equivalent. In particular, it is not necessary to use ``update()`` on ``sc2`` in the case of a DataControl.
"""
@abc.abstractmethod
def __init__(self, *, state=None, **kwargs):
super().__init__(**kwargs)
@property
@abc.abstractmethod
def state(self):
"""Returns the state of the Control."""
@state.setter
@abc.abstractmethod
def state(self, value):
"""Sets the state of the Control."""
[docs]class DataControl(AbstractControl):
"""ABC for control objects which can be updated with data."""
@abc.abstractmethod
def update(self, data):
pass
[docs]class IterationControl(AbstractControl):
"""ABC for iteration control objects. Enforces the existence of ..."""
@abc.abstractmethod
def __next__(self):
pass
[docs]class ConvergenceControl(AbstractControl):
"""ABC for convergence tester objects. Enforces the existence of an update method, and the ``converged`` property.
For LineControl objects, the converged property must be valid (False) also before the first update() call.
This is not required for SurfaceControl objects."""
@property
@abc.abstractmethod
def converged(self):
pass
# The only purpose of these subclasses is to distinguish between
# ConvergenceControls which take a VolumeData, SurfaceData or LineData object.
[docs]class VolumeControl(AbstractControl):
"""Specializes AbstractControl for Volume objects"""
[docs]class SurfaceControl(AbstractControl):
"""Specializes AbstractControl for Surface objects"""
[docs]class LineControl(AbstractControl):
"""Specializes AbstractControl for Line objects"""