"""Contains classes for storing debug information on the parameters, variables and
constraints in an instance of the :class:`csnlp.Nlp` class."""
from inspect import getframeinfo as _getframeinfo
from itertools import dropwhile as _dropwhile
from traceback import walk_stack as _walk_stack
from types import MappingProxyType as _MappingProxyType
from typing import Literal
from typing import NamedTuple as _NamedTuple
from numpy import prod as _prod
[docs]
class NlpDebugEntry(_NamedTuple):
"""Class representing a single entry of the debug information for an
:class:`csnlp.Nlp` instance."""
name: str
"""Name of the quantity."""
type: Literal[
"Parameter", "Decision variable", "Equality constraint", "Inequality constraint"
]
"""Type of the quantity."""
shape: tuple[int, ...]
"""Shape of the quantity."""
filename: str
"""Name of the file where the quantity is defined."""
function: str
"""Name of the function/method where the quantity is defined."""
lineno: int
"""Line number where the quantity is defined."""
context: str
"""Context in which the quantity is defined."""
def __str__(self) -> str:
shape = "x".join(str(d) for d in self.shape)
return (
f"{self.type} '{self.name}' of shape {shape} defined at\n"
f" filename: {self.filename}\n"
f" function: {self.function}:{self.lineno}\n"
f" context: {self.context}"
)
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.__str__()})"
[docs]
class NlpDebug:
"""NLP debug class for information about variables and constraints in an instance
of the :class:`csnlp.Nlp` class. In particular, it records information on
- the parameters ``p``
- the decision variable ``x``
- the equality constraints ``g``
- the inequality constraints ``h``.
"""
_types = _MappingProxyType(
{
"p": "Parameter",
"x": "Decision variable",
"g": "Equality constraint",
"h": "Inequality constraint",
}
)
"""Possible types of quantities and their definition."""
def __init__(self) -> None:
self._p_info: list[tuple[range, NlpDebugEntry]] = []
self._x_info: list[tuple[range, NlpDebugEntry]] = []
self._g_info: list[tuple[range, NlpDebugEntry]] = []
self._h_info: list[tuple[range, NlpDebugEntry]] = []
[docs]
def p_describe(self, index: int) -> NlpDebugEntry:
"""Returns debug information on the parameter at the given ``index``.
Parameters
----------
index : int
Index of the parameter p to query information about.
Returns
-------
NlpDebugEntry
A class instance containing debug information on the parameter p
at the given index.
Raises
------
IndexError
Index not found, or outside bounds of p.
"""
return self.__describe(self._p_info, index)
[docs]
def x_describe(self, index: int) -> NlpDebugEntry:
"""Returns debug information on the variable at the given ``index``.
Parameters
----------
index : int
Index of the variable x to query information about.
Returns
-------
NlpDebugEntry
A class instance containing debug information on the variable x
at the given index.
Raises
------
IndexError
Index not found, or outside bounds of x.
"""
return self.__describe(self._x_info, index)
[docs]
def g_describe(self, index: int) -> NlpDebugEntry:
"""Returns debug information on the constraint at the given ``index``.
Parameters
----------
index : int
Index of the constraint g to query information about.
Returns
-------
NlpDebugEntry
A class instance containing debug information on the constraint g
at the given index.
Raises
------
IndexError
Index not found, or outside bounds of g.
"""
return self.__describe(self._g_info, index)
[docs]
def h_describe(self, index: int) -> NlpDebugEntry:
"""Returns debug information on the constraint at the given ``index``.
Parameters
----------
index : int
Index of the constraint h to query information about.
Returns
-------
NlpDebugEntry
A class instance containing debug information on the constraint h
at the given index.
Raises
------
IndexError
Index not found, or outside bounds of h.
"""
return self.__describe(self._h_info, index)
[docs]
def register(
self, group: Literal["p", "x", "g", "h"], name: str, shape: tuple[int, ...]
) -> None:
"""Registers debug information on new object name under the specific
group.
Parameters
----------
group : {"p", "x", "g", "h"}
Indentifies the group the object belongs to: parameters, variables,
equality constraints or inequality constraints.
name : str
Name of the object.
shape : Tuple[int, ...]
Shape of the object.
Raises
------
AttributeError
Raises in case the given group is invalid.
"""
stack = _dropwhile(
lambda f: f[0].f_globals["__name__"].startswith("csnlp."), _walk_stack(None)
)
frame, lineno = next(stack)
traceback = _getframeinfo(frame, context=3)
info: list[tuple[range, NlpDebugEntry]] = getattr(self, f"_{group}_info")
last = info[-1][0].stop if info else 0
info.append(
(
range(last, last + _prod(shape)),
NlpDebugEntry(
name,
self._types[group],
shape,
traceback.filename,
traceback.function,
lineno,
(
"".join(traceback.code_context)
if traceback.code_context is not None
else ""
),
),
)
)
def __describe(
self, info: list[tuple[range, NlpDebugEntry]], index: int
) -> NlpDebugEntry:
for range_, description in info:
if index in range_:
return description
raise IndexError(f"Index {index} not found.")