Source code for at.latticetools.matching

"""Matching of lattice parameters.

The matching acts on a :py:class:`.VariableList` to statisfy the constraints
expressed in an :py:class:`.ObservableList`.

Examples of use of such classes are available in:

* :doc:`/p/notebooks/variables`
* :doc:`/p/notebooks/observables`

Examples of matching are given in :doc:`/p/notebooks/matching`.
"""

from __future__ import annotations

__all__ = ["match"]


import numpy as np
from scipy.optimize import least_squares

from .observablelist import ObservableList
from ..lattice import Lattice, VariableList
from ..lattice.lattice_variables import ElementVariable


def _match(
    ring: Lattice,
    variables: VariableList,
    constraints: ObservableList,
    *,
    method: str | None = None,
    verbose: int = 2,
    max_nfev: int = 1000,
    dp: float | None = None,
    dct: float | None = None,
    df: float | None = None,
    **kwargs,
) -> None:
    def fun(vals):
        """Evaluation function for the minimiser."""
        variables.set(vals, ring=ring)
        constraints.evaluate(ring, dp=dp, dct=dct, df=df)
        return constraints.get_flat_weighted_deviations(err=1.0e6)

    vini = variables.get(ring=ring, initial=True, check_bounds=True)
    bounds = np.array([var.bounds for var in variables])

    constraints.evaluate(ring, initial=True, dp=dp, dct=dct, df=df)
    constraints.check()
    ntargets = constraints.flat_values.size

    if method is None:
        if np.any(np.isfinite(bounds)) or ntargets < len(variables):
            method = "trf"
        else:
            method = "lm"
    if verbose >= 1:
        print(
            f"\n{ntargets} constraints, {len(variables)} variables,"
            f" using method {method}\n"
        )

    least_squares(
        fun,
        vini,
        bounds=(bounds[:, 0], bounds[:, 1]),
        verbose=verbose,
        max_nfev=max_nfev,
        method=method,
        **kwargs,
    )

    if verbose >= 1:
        print("\nConstraints:")
        print(constraints)
        print("\nVariables:")
        print(variables)


[docs] def match( ring: Lattice, variables: VariableList, constraints: ObservableList, *, copy: bool = False, **kwargs, ) -> Lattice | None: """Match constraints by varying variables. Minimisation is performed by the :py:func:`~scipy.optimize.least_squares` function. Args: ring: Lattice description variables: Variable parameters constraints: Constraints to fulfill copy: If :py:obj:`True`, return a modified copy of *ring*, otherwise perform the match in-line Keyword Args: method: Minimisation algorithm (see :py:func:`~scipy.optimize.least_squares`). If :py:obj:`None`, use 'lm' for unbounded problems, 'trf' otherwise. verbose: Level of verbosity max_nfev: Maximum number of function evaluation dp: Momentum deviation. dct: Path lengthening. df: Deviation from the nominal RF frequency. **kwargs: Keyword arguments sent to :py:func:`~scipy.optimize.least_squares` Returns: Modified Lattice if copy=True, None otherwise Raises: TypeError: If copy=True and ElementVariable is present RuntimeError: If optimisation fails Examples: See :doc:`/p/notebooks/matching` """ if copy: # Check that there is no ElementVariable for var in variables: if isinstance(var, ElementVariable): raise TypeError("When 'copy' is True, no ElementVariable is accepted") newring = ring.deepcopy() _match(newring, variables, constraints, **kwargs) return newring else: _match(ring, variables, constraints, **kwargs) return None