From cebb86df67d7d42f5cf31c0cb9b6d7462feec2ae Mon Sep 17 00:00:00 2001 From: Teresia Olsson Date: Thu, 16 Jul 2026 12:41:38 +0200 Subject: [PATCH 1/3] Remove config model from csv curve and move tests to magnet package. --- pyaml/magnet/csvcurve.py | 68 +++++++++++++------ tests/{configuration => magnet}/test_curve.py | 5 +- 2 files changed, 48 insertions(+), 25 deletions(-) rename tests/{configuration => magnet}/test_curve.py (82%) diff --git a/pyaml/magnet/csvcurve.py b/pyaml/magnet/csvcurve.py index 475b5211..df61eb14 100644 --- a/pyaml/magnet/csvcurve.py +++ b/pyaml/magnet/csvcurve.py @@ -1,51 +1,75 @@ -from pathlib import Path - import numpy as np -from pydantic import BaseModel, ConfigDict +from numpy.typing import NDArray +from ..common.element import __pyaml_repr__ from ..common.exception import PyAMLException from ..configuration.fileloader import ROOT +from ..validation import DynamicValidation, register_schema from .curve import Curve # Define the main class name for this module PYAMLCLASS = "CSVCurve" -class ConfigModel(BaseModel): +@register_schema +class CSVCurve(Curve, DynamicValidation): """ - Configuration model for CSV curve + Curve loaded from a CSV file. - Parameters - ---------- - file : str - CSV file that contains the curve (n rows, 2 columns) - """ + This class reads a CSV file containing a two-column numeric dataset + representing ``(x, y)`` coordinate pairs. The file is loaded during + initialization and stored internally as a NumPy array. - model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") + The CSV file must: - file: str + - contain exactly two columns, + - use commas as the delimiter, + - contain values that can be converted to ``float``. + Parameters + ---------- + file : str + Path to the CSV file. Relative paths are resolved using the + project's configured root directory. -class CSVCurve(Curve): - """ - Class for load CSV (x,y) curve + Attributes + ---------- + file : str + Path to the CSV file provided during initialization. + + Raises + ------ + PyAMLException + If the file cannot be parsed as a numeric CSV or if the loaded + data does not have shape ``(n, 2)``. + + Notes + ----- + The loaded curve is stored internally as a NumPy array with shape + ``(n, 2)``, where the first column contains x-values and the second + column contains y-values. The data can be accessed using + :meth:`get_curve`. """ - def __init__(self, cfg: ConfigModel): - self._cfg = cfg + def __init__(self, file: str): + self._file = file # Load CSV curve - path = ROOT.expand_path(cfg.file) + path = ROOT.expand_path(self._file) try: self._curve = np.genfromtxt(path, delimiter=",", dtype=float, loose=False) except ValueError as e: - raise PyAMLException(f"CSVCurve(file='{cfg.file}',dtype=float): {str(e)}") from None + raise PyAMLException(f"CSVCurve(file='{self._file}',dtype=float): {str(e)}") from None _s = np.shape(self._curve) if len(_s) != 2 or _s[1] != 2: - raise PyAMLException(f"CSVCurve(file='{cfg.file}',dtype=float):wrong shape (2,2) expected but got {str(_s)}") + raise PyAMLException(f"CSVCurve(file='{self._file}',dtype=float):wrong shape (2,2) expected but got {str(_s)}") + + @property + def file(self): + return self._file - def get_curve(self) -> np.array: + def get_curve(self) -> NDArray[np.float64]: """ Get the curve data. @@ -57,4 +81,4 @@ def get_curve(self) -> np.array: return self._curve def __repr__(self): - return repr(self._cfg).replace("ConfigModel", self.__class__.__name__) + return __pyaml_repr__(self) diff --git a/tests/configuration/test_curve.py b/tests/magnet/test_curve.py similarity index 82% rename from tests/configuration/test_curve.py rename to tests/magnet/test_curve.py index 68848a7d..cf8597d1 100644 --- a/tests/configuration/test_curve.py +++ b/tests/magnet/test_curve.py @@ -1,13 +1,12 @@ import numpy as np from pyaml.configuration import ROOT -from pyaml.magnet.csvcurve import ConfigModel, CSVCurve +from pyaml.magnet.csvcurve import CSVCurve from pyaml.magnet.curve import Curve def curve_test(file: str, current: float, strength: float): - curveConfig = ConfigModel(file=file) - curve = CSVCurve(curveConfig) + curve = CSVCurve(file=file) curveData = curve.get_curve() icurveData = Curve.inverse(curveData) x1 = np.interp(current, curveData[:, 0], curveData[:, 1]) From e3827baa5cc7b1bd8c3d35667c14780807111d04 Mon Sep 17 00:00:00 2001 From: Teresia Olsson Date: Thu, 16 Jul 2026 12:50:06 +0200 Subject: [PATCH 2/3] Remove config model for csvmatrix. --- pyaml/magnet/csvmatrix.py | 47 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/pyaml/magnet/csvmatrix.py b/pyaml/magnet/csvmatrix.py index 6726f944..a912ec99 100644 --- a/pyaml/magnet/csvmatrix.py +++ b/pyaml/magnet/csvmatrix.py @@ -1,46 +1,51 @@ -from pathlib import Path - import numpy as np -from pydantic import BaseModel, ConfigDict +from numpy.typing import NDArray +from ..common.element import __pyaml_repr__ from ..common.exception import PyAMLException from ..configuration.fileloader import ROOT +from ..validation import DynamicValidation, register_schema from .matrix import Matrix # Define the main class name for this module PYAMLCLASS = "CSVMatrix" -class ConfigModel(BaseModel): +@register_schema +class CSVMatrix(Matrix, DynamicValidation): """ - Configuration model for CSV matrix + Matrix loaded from a CSV file. + + This class reads a CSV file containing numeric values and stores the + resulting matrix as a NumPy array. Parameters ---------- file : str - CSV file that contains the matrix - """ - - model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") - - file: str + Path to the CSV file. Relative paths are resolved using the + project's configured root directory. - -class CSVMatrix(Matrix): - """ - Class for loading CSV matrix + Raises + ------ + PyAMLException + If the CSV file cannot be parsed as a numeric array. """ - def __init__(self, cfg: ConfigModel): - self._cfg = cfg + def __init__(self, file: str): + self._file = file + # Load CSV matrix - path = ROOT.expand_path(cfg.file) + path = ROOT.expand_path(self._file) try: self._mat = np.genfromtxt(path, delimiter=",", dtype=float, loose=False) except ValueError as e: - raise PyAMLException(f"CSVMatrix(file='{cfg.file}',dtype=float): {str(e)}") from None + raise PyAMLException(f"CSVMatrix(file='{self._file}',dtype=float): {str(e)}") from None + + @property + def file(self): + return self._file - def get_matrix(self) -> np.array: + def get_matrix(self) -> NDArray[np.float64]: """ Get the matrix data. @@ -52,4 +57,4 @@ def get_matrix(self) -> np.array: return self._mat def __repr__(self): - return repr(self._cfg).replace("ConfigModel", self.__class__.__name__) + return __pyaml_repr__(self) From ee87842b737110e1660c6cc62b28cec19d9468f5 Mon Sep 17 00:00:00 2001 From: Teresia Olsson Date: Thu, 16 Jul 2026 13:17:09 +0200 Subject: [PATCH 3/3] Remove config models from inline curve. --- pyaml/magnet/inline_curve.py | 49 ++++++++++++++----------- pyaml/magnet/linear_serialized_model.py | 3 +- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/pyaml/magnet/inline_curve.py b/pyaml/magnet/inline_curve.py index 1ae6e2a3..064d3fe7 100644 --- a/pyaml/magnet/inline_curve.py +++ b/pyaml/magnet/inline_curve.py @@ -1,45 +1,52 @@ -from pathlib import Path - import numpy as np -from pydantic import BaseModel, ConfigDict +from numpy.typing import NDArray +from ..common.element import __pyaml_repr__ from ..common.exception import PyAMLException +from ..validation import DynamicValidation, register_schema from .curve import Curve # Define the main class name for this module PYAMLCLASS = "InlineCurve" -class ConfigModel(BaseModel): +@register_schema +class InlineCurve(Curve, DynamicValidation): """ - Configuration model for inline curve + Curve defined directly from an in-memory matrix of (x, y) points. + + This class stores curve data provided as a list of rows, where each row must + contain exactly two values: the x-coordinate and the y-coordinate. The data is + converted to a NumPy array and validated to ensure it has shape ``(n, 2)``. Parameters ---------- mat : list[list[float]] - Curve data (n rows, 2 columns) + Curve data as a two-column matrix. Each row represents one point in the + curve, with ``mat[i][0]`` being the x-value and ``mat[i][1]`` being the + y-value. + + Raises + ------ + PyAMLException + If the provided matrix does not have two columns. """ - model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") - - mat: list[list[float]] - + def __init__(self, mat: list[list[float]]): + self._mat = mat -class InlineCurve(Curve): - """ - Class for load CSV (x,y) curve - """ - - def __init__(self, cfg: ConfigModel): - self._cfg = cfg # Load the curve - self._curve = np.array(self._cfg.mat) + self._curve = np.array(self._mat) _s = np.shape(self._curve) if len(_s) != 2 or _s[1] != 2: - raise PyAMLException(f"InlineCurve(mat='{cfg.mat}',dtype=float): wrong shape (2,2) expected but got {str(_s)}") + raise PyAMLException(f"InlineCurve(mat='{self._mat}',dtype=float): wrong shape (2,2) expected but got {str(_s)}") + + @property + def mat(self): + return self._mat - def get_curve(self) -> np.array: + def get_curve(self) -> NDArray[np.float64]: """ Get the curve data. @@ -51,4 +58,4 @@ def get_curve(self) -> np.array: return self._curve def __repr__(self): - return repr(self._cfg).replace("ConfigModel", self.__class__.__name__) + return __pyaml_repr__(self) diff --git a/pyaml/magnet/linear_serialized_model.py b/pyaml/magnet/linear_serialized_model.py index bb2eb6f4..e1c93a70 100644 --- a/pyaml/magnet/linear_serialized_model.py +++ b/pyaml/magnet/linear_serialized_model.py @@ -5,7 +5,6 @@ from ..common.exception import PyAMLException from ..control.deviceaccess import DeviceAccess from .curve import Curve -from .inline_curve import ConfigModel as InlineCurveModel from .inline_curve import InlineCurve from .linear_model import ConfigModel as LinearConfigModel from .linear_model import LinearMagnetModel @@ -122,7 +121,7 @@ def __initialize(self): else: self.__curves: list[Curve] = [] for _ in range(self.__nbMagnets): - curve = InlineCurve(InlineCurveModel(mat=self._cfg.curves.get_curve())) + curve = InlineCurve(mat=self._cfg.curves.get_curve()) self.__curves.append(curve) _check_len(self.__calibration_factors, "calibration_factors", self.__nbMagnets)