Source code for quantarhei.core.valueaxis

"""Linear array of values which are used as variables of numerical functions
and parameter dependent matrices



Properties
----------

start : float
Starting value of the axis

step : float
Step between consecutive values of the axis

length : int
Length of the `data` array

data : numpy.array of float
Values of the axis


Examples
--------
    Creation of the `ValueAxis` object

>>> va = ValueAxis(0.0,100,1.0)

Its standard attributes are the following:

>>> print(va.length)
100
>>> print(va.step)
1.0
>>> print(va.start)
0.0

The attribute `data` is an array of floats

>>> print(va.data[3:5])
[ 3.  4.]

Attributes `min` and `max` are provided for convenience

>>> print(va.min)
0.0
>>> print(va.max)
99.0

You can locate an index of a specific value. The lower neighbor and
the difference to it is returned.

>>> i,diff = va.locate(16.3)
>>> print(i)
16
>>> print("{0:.1f}".format(diff))
0.3

The following returns the nearest index on the axis

>>> i = va.nearest(16.3)
>>> print(i)
16
>>> i = va.nearest(16.7)
>>> print(i)
17
>>> i = va.nearest(16.5)
>>> print(i)
17

Mutual compatibility of the two ValueAxis objects can be tested as follows

>>> va1 = ValueAxis(0.0, 2000, 1.0)
>>> va2 = ValueAxis(0.0, 1000, 1.0)
>>> va2.is_subsection_of(va1)
True
>>> va1.is_subsection_of(va2)
False
>>> va1.is_extension_of(va2)
True
>>> va2.is_subsection_of(va1)
True
>>> va2.is_extension_of(va1)
False
>>> va1.is_equal_to(va2)
False
>>> va1.is_equal_to(va1)
True
>>> va3 = ValueAxis(0.0, 2000, 1.0)
>>> va3.is_equal_to(va1)
True

Class Details
-------------

"""

from __future__ import annotations

import numpy

from .. import REAL
from ..exceptions import QuantarheiError
from ..utils import Float, Integer
from .saveable import Saveable


[docs] class ValueAxis(Saveable): """Linear array of values used as the argument axis of numerical functions. Parameters ---------- start : float First value of the axis. length : int Number of points on the axis. step : float Spacing between consecutive axis values. Attributes ---------- data : numpy.ndarray Array of axis values, length ``length``. min : float Minimum (first) value on the axis. max : float Maximum (last) value on the axis. """ step = Float("step") start = Float("start") length = Integer("length") def __init__(self, start: float = 0.0, length: int = 1, step: float = 1.0) -> None: self.step = step self.start = start self.length = length self.data: numpy.ndarray = numpy.linspace( start, start + (length - 1) * step, length, dtype=REAL ) @property def min(self) -> float: """Returns the minimum value on the axis""" return self.start @property def max(self) -> float: """Returns the maximum value on the axis""" return self.data[self.length - 1]
[docs] def locate(self, val: float) -> tuple[int, float]: """Return the index of the lower neighbor of ``val`` and the distance. Parameters ---------- val : float A value within the ``[min, max]`` range of the axis. Returns ------- int Index of the largest axis point that is ``<= val``. float Distance from that axis point to ``val``. Raises ------ Exception If ``val`` is outside the axis bounds. """ # nearest smaller neighbor index nsni = int(numpy.floor((val - self.start) / self.step)) # if n0 is within bounds calculate distance # from the lower neighbor if (nsni >= 0) and (nsni < self.length): dval = val - self.data[nsni] return nsni, dval raise QuantarheiError("Value out of bounds")
[docs] def nearest(self, val: float) -> int: """Return the index of the nearest axis point to ``val``. If ``val`` is within one step above the upper bound, the index of the last point is returned. Parameters ---------- val : float A value within (or just above) the ``[min, max]`` range of the axis. Returns ------- int Index of the axis point closest to ``val``. Raises ------ Exception If ``val`` is outside the axis bounds. """ # nearest smaller neighbor index nsni = int(numpy.floor((val - self.start) / self.step)) if (nsni >= 0) and (nsni < self.length): # if n0 is with bounds calculate difference # from the lower neighbor diff1 = numpy.abs(val - self.data[nsni]) # if the upper neighbor is within bounds calculate difference # from the upper neighbor if nsni + 1 < self.length: diff2 = numpy.abs(val - self.data[nsni + 1]) else: diff2 = 5 * self.step # return the closer neighbor if diff1 < diff2: return nsni if nsni + 1 < self.length: return nsni + 1 # if the upper neigbor is out of bounds # return the lower one return nsni raise QuantarheiError("Value out of bounds")
[docs] def is_equal_to(self, axis: ValueAxis) -> bool: """Returns True if the axis is equal to this ValueAxis""" return ( numpy.isclose(self.start, axis.start) and numpy.isclose(self.step, axis.step) and (self.length == axis.length) )
def __eq__(self, other: object) -> bool: return self.is_equal_to(other) # type: ignore[arg-type]
[docs] def is_extension_of(self, axis: ValueAxis) -> bool: """Returns True if the axis is contained in this ValueAxis""" ret = True ret = ret and ( self.start <= axis.start or numpy.isclose(self.start, axis.start) ) ret = ret and numpy.isclose(self.step, axis.step) ret = ret and (self.max >= axis.max or numpy.isclose(self.max, axis.max)) offsets = (axis.data - self.start) / self.step ret = ret and numpy.any(numpy.isclose(offsets, numpy.rint(offsets))) return ret
[docs] def is_subsection_of(self, axis: ValueAxis) -> bool: """Returns True if the axis contains this ValueAxis""" ret = True ret = ret and numpy.isclose(self.step, axis.step) ret = ret and _axis_value_is_in_axis(self.start, axis) ret = ret and _axis_value_is_in_axis(self.max, axis) return ret
[docs] def is_subset_of(self, axis: ValueAxis) -> bool: """Returns True if the ValueAxis is a subset of axis We check if all values of this axis are also values of the submitted axis object. Examples -------- >>> ta1 = ValueAxis(0.0, 1000, 1.0) >>> ta2 = ValueAxis(0.0, 490, 2.0) >>> ta2.is_subset_of(ta1) True >>> ta1 = ValueAxis(0.0, 1000, 1.0) >>> ta2 = ValueAxis(3.0, 900, 1.0) >>> ta2.is_subset_of(ta1) True >>> ta1 = ValueAxis(0.0, 1000, 1.0) >>> ta2 = ValueAxis(3.0, 400, 2.0) >>> ta2.is_subset_of(ta1) True >>> ta1 = ValueAxis(0.0, 1000, 1.12345) >>> ta2 = ValueAxis(3*1.12345, 400, 2*1.12345) >>> ta2.is_subset_of(ta1) True >>> ta1 = ValueAxis(0.0, 1000, 1.12345) >>> ta2 = ValueAxis(3.0, 400, 2*1.12345) >>> ta2.is_subset_of(ta1) False >>> ta1 = ValueAxis(0.0, 1000, 1.0) >>> ta2 = ValueAxis(3.0, 500, 2.0) >>> ta2.is_subset_of(ta1) False """ ret = True Nst = round(self.step / axis.step) ret = ret and numpy.isclose(Nst * axis.step, self.step) ret = ret and _axis_value_is_in_axis(self.start, axis) ret = ret and (self.start < axis.max or numpy.isclose(self.start, axis.max)) ret = ret and _axis_value_is_in_axis(self.max, axis) return ret
[docs] def is_superset_of(self, axis: ValueAxis) -> bool: """Returns True if the ValueAxis is a superset of axis We check if all values of this axis are also values of the submitted axis object. Examples -------- >>> ta1 = ValueAxis(0.0, 1000, 1.0) >>> ta2 = ValueAxis(0.0, 490, 2.0) >>> ta1.is_superset_of(ta2) True >>> ta1 = ValueAxis(0.0, 1000, 1.0) >>> ta2 = ValueAxis(3.0, 900, 1.0) >>> ta1.is_superset_of(ta2) True >>> ta1 = ValueAxis(0.0, 1000, 1.0) >>> ta2 = ValueAxis(3.0, 400, 2.0) >>> ta1.is_superset_of(ta2) True >>> ta1 = ValueAxis(0.0, 1000, 1.12345) >>> ta2 = ValueAxis(3*1.12345, 400, 2*1.12345) >>> ta1.is_superset_of(ta2) True >>> ta1 = ValueAxis(0.0, 1000, 1.12345) >>> ta2 = ValueAxis(3.0, 400, 2*1.12345) >>> ta1.is_superset_of(ta2) False >>> ta1 = ValueAxis(0.0, 1000, 1.0) >>> ta2 = ValueAxis(3.0, 500, 2.0) >>> ta1.is_superset_of(ta2) False """ ret = True Nst = round(axis.step / self.step) ret = ret and numpy.isclose(Nst * self.step, axis.step) ret = ret and _axis_value_is_in_axis(axis.start, self) ret = ret and (axis.start < self.max or numpy.isclose(axis.start, self.max)) ret = ret and _axis_value_is_in_axis(axis.max, self) return ret
def __str__(self) -> str: """String representation of the ValueAxis object""" out = "\nquantarhei.ValueAxis object" out += "\n==========================" out += "\nstart = " + str(self.start) out += "\nlength = " + str(self.length) out += "\nstep = " + str(self.step) return out
def _axis_value_is_in_axis(value: float, axis: ValueAxis) -> bool: """Returns True when ``value`` lies on an axis grid point.""" if value < axis.min and not numpy.isclose(value, axis.min): return False if value > axis.max and not numpy.isclose(value, axis.max): return False nstep = round((value - axis.start) / axis.step) return numpy.isclose(axis.start + nstep * axis.step, value)