Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESRF Changes #142

Merged
merged 8 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ dev = [
"pydata-sphinx-theme>=0.12",
"pytest",
"pytest-cov",
"pytest-lazy-fixture",
"sphinx-autobuild",
"sphinx-copybutton",
"sphinx-design",
Expand Down
2 changes: 1 addition & 1 deletion src/pytac/data/DIAD/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
2 changes: 1 addition & 1 deletion src/pytac/data/DIADSP/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
2 changes: 1 addition & 1 deletion src/pytac/data/DIADTHz/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
2 changes: 1 addition & 1 deletion src/pytac/data/I04/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
2 changes: 1 addition & 1 deletion src/pytac/data/I04SP/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
2 changes: 1 addition & 1 deletion src/pytac/data/I04THz/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
2 changes: 1 addition & 1 deletion src/pytac/data/SRI0913_MOGA/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
2 changes: 1 addition & 1 deletion src/pytac/data/VMX/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
2 changes: 1 addition & 1 deletion src/pytac/data/VMXSP/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
2 changes: 1 addition & 1 deletion src/pytac/data/VMXTHz/simple_devices.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
el_id,field,value,readonly
0,energy,3e9,true
0,energy,3000000000,True
4 changes: 2 additions & 2 deletions utils/load_mml.m → src/pytac/data/utils/load_mml.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function load_mml(ringmode)

dir = fileparts(mfilename('fullpath'));
cd(dir);
datadir = fullfile(dir, '..', 'pytac', 'data', ringmode);
datadir = fullfile(dir, '..', ringmode);
if ~exist(datadir, 'dir')
fprintf('Data directory %s does not exist. Please create it.\n', datadir);
fprintf('Script will exit.\n');
Expand All @@ -43,7 +43,7 @@ function load_mml(ringmode)
ao = getao();

% Hard-coded beam energy value.
fprintf(f_simple_devices, '0,energy,3e9,true\n');
fprintf(f_simple_devices, '0,energy,3e9,True\n');

% The individual BPM PVs are not stored in middlelayer.
BPMS = get_bpm_pvs(ao);
Expand Down
14 changes: 11 additions & 3 deletions utils/load_unitconv.m → src/pytac/data/utils/load_unitconv.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
function load_unitconv(ringmode, renamedIndexes)
dir = fileparts(mfilename('fullpath'));
cd(dir);
units_file = fullfile(dir, '..', 'pytac', 'data', ringmode, 'unitconv.csv');
poly_file = fullfile(dir, '..', 'pytac', 'data', ringmode, 'uc_poly_data.csv');
pchip_file = fullfile(dir, '..', 'pytac', 'data', ringmode, 'uc_pchip_data.csv');
datadir = fullfile(dir, '..', ringmode);
if ~exist(datadir, 'dir')
fprintf('Data directory %s does not exist. Please create it.\n', datadir);
fprintf('Script will exit.\n');
return;
end

% Open the CSV files that store the Pytac data.
units_file = fullfile(datadir, 'unitconv.csv');
poly_file = fullfile(datadir, 'uc_poly_data.csv');
pchip_file = fullfile(datadir, 'uc_pchip_data.csv');

fprintf('Loading unit conversions...\n');

Expand Down
1 change: 1 addition & 0 deletions src/pytac/data_source.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Module containing pytac data source classes."""

import pytac
from pytac.exceptions import DataSourceException, FieldException

Expand Down
1 change: 1 addition & 0 deletions src/pytac/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
DLS is a sextupole magnet that contains also horizontal and vertical corrector
magnets and a skew quadrupole.
"""

from typing import List, Union

import pytac
Expand Down
11 changes: 6 additions & 5 deletions src/pytac/element.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Module containing the element class."""

import pytac
from pytac.data_source import DataSource, DataSourceManager
from pytac.exceptions import DataSourceException, FieldException
Expand Down Expand Up @@ -89,13 +90,13 @@ def __str__(self):
"""
repn = "<Element "
if self.name is not None:
repn += "'{0}', ".format(self.name)
repn += f"'{self.name}', "
if self.index is not None:
repn += "index {0}, ".format(self.index)
repn += "length {0} m, ".format(self.length)
repn += f"index {self.index}, "
repn += f"length {self.length} m, "
if self.cell is not None:
repn += "cell {0}, ".format(self.cell)
repn += "families {0}>".format(", ".join(f for f in self.families))
repn += f"cell {self.cell}, "
repn += f"families {', '.join(f for f in self.families)}>"
return repn

__repr__ = __str__
Expand Down
1 change: 1 addition & 0 deletions src/pytac/lattice.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Representation of a lattice object which contains all the elements of the
machine.
"""

import logging
from typing import List, Optional

Expand Down
146 changes: 93 additions & 53 deletions src/pytac/load_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@
* uc_poly_data.csv
* uc_pchip_data.csv
"""

import ast
import collections
import contextlib
import copy
import csv
import logging
from pathlib import Path
from typing import Dict, Iterator

import pytac
from pytac import data_source, element, utils
from pytac.device import EpicsDevice, SimpleDevice
from pytac.exceptions import ControlSystemException
from pytac.exceptions import ControlSystemException, UnitsException
from pytac.lattice import EpicsLattice, Lattice
from pytac.units import NullUnitConv, PchipUnitConv, PolyUnitConv, UnitConv

# Create a default unit conversion object that returns the input unchanged.
DEFAULT_UC = NullUnitConv()

ELEMENTS_FILENAME = "elements.csv"
EPICS_DEVICES_FILENAME = "epics_devices.csv"
SIMPLE_DEVICES_FILENAME = "simple_devices.csv"
Expand Down Expand Up @@ -86,6 +86,52 @@ def load_pchip_unitconv(filepath: Path) -> Dict[int, PchipUnitConv]:
return unitconvs


def resolve_unitconv(
uc_params: Dict, unitconvs: Dict, polyconv_file: Path, pchipconv_file: Path
) -> UnitConv:
"""Create a unit conversion object based on the dictionary of parameters passed.

Args:
uc_params (Dict): A dictionary of parameters specifying the unit conversion
object's properties.
unitconvs (Dict): A dictionary of all loaded unit conversion objects.
polyconv_file (Path): The path to the .csv file from which all PolyUnitConv
objects are loaded.
pchipconv_file (Path): The path to the .csv file from which all PchipUnitConv
objects are loaded.
Returns:
UnitConv: The unit conversion object as specified by uc_params.

Raises:
UnitsException: if the "uc_id" given in uc_params isn't in the unitconvs Dict.
"""
error_msg = (
f"Unable to resolve {uc_params['uc_type']} unit conversion with ID "
f"{uc_params['uc_id']}, "
)
if uc_params["uc_type"] == "null":
uc = NullUnitConv(uc_params["eng_units"], uc_params["phys_units"])
else:
# Each element needs its own UnitConv object as it may have different limits.
try:
uc = copy.copy(unitconvs[int(uc_params["uc_id"])])
except KeyError:
if uc_params["uc_type"] == "poly" and not polyconv_file.exists():
raise UnitsException(error_msg + f"{polyconv_file} not found.")
elif uc_params["uc_type"] == "pchip" and not pchipconv_file.exists():
raise UnitsException(error_msg + f"{pchipconv_file} not found.")
else:
raise UnitsException(error_msg + "unrecognised UnitConv type.")
uc.phys_units = uc_params["phys_units"]
uc.eng_units = uc_params["eng_units"]
lower, upper = [
float(lim) if lim != "" else None
for lim in [uc_params["lower_lim"], uc_params["upper_lim"]]
]
uc.set_conversion_limits(lower, upper)
return uc


def load_unitconv(mode_dir: Path, lattice: Lattice) -> None:
"""Load the unit conversion objects from a file.

Expand All @@ -95,56 +141,38 @@ def load_unitconv(mode_dir: Path, lattice: Lattice) -> None:
"""
unitconvs: Dict[int, UnitConv] = {}
# Assemble datasets from the polynomial file
unitconvs.update(load_poly_unitconv(mode_dir / POLY_FILENAME))
polyconv_file = mode_dir / POLY_FILENAME
if polyconv_file.exists():
unitconvs.update(load_poly_unitconv(polyconv_file))
else:
logging.warning(f"{polyconv_file} not found, unable to load PolyUnitConvs.")
# Assemble datasets from the pchip file
unitconvs.update(load_pchip_unitconv(mode_dir / PCHIP_FILENAME))
pchipconv_file = mode_dir / PCHIP_FILENAME
if pchipconv_file.exists():
unitconvs.update(load_pchip_unitconv(pchipconv_file))
else:
logging.warning(f"{pchipconv_file} not found, unable to load PchipUnitConvs.")
# Add the unitconv objects to the elements
with csv_loader(mode_dir / UNITCONV_FILENAME) as csv_reader:
for item in csv_reader:
uc = resolve_unitconv(item, unitconvs, polyconv_file, pchipconv_file)
# Special case for element 0: the lattice itself.
if int(item["el_id"]) == 0:
if item["uc_type"] != "null":
# Each element needs its own unitconv object as
# it may for example have different limit.
uc = copy.copy(unitconvs[int(item["uc_id"])])
uc.phys_units = item["phys_units"]
uc.eng_units = item["eng_units"]
upper, lower = (
float(lim) if lim != "" else None
for lim in [item["upper_lim"], item["lower_lim"]]
)
uc.set_conversion_limits(lower, upper)
else:
uc = NullUnitConv(item["eng_units"], item["phys_units"])
lattice.set_unitconv(item["field"], uc)
else:
element = lattice[int(item["el_id"]) - 1]
# For certain magnet types, we need an additional rigidity
# conversion factor as well as the raw conversion.
if item["uc_type"] == "null":
uc = NullUnitConv(item["eng_units"], item["phys_units"])
else:
# Each element needs its own unitconv object as
# it may for example have different limit.
uc = copy.copy(unitconvs[int(item["uc_id"])])
if any(
element.is_in_family(f)
for f in ("HSTR", "VSTR", "Quadrupole", "Sextupole", "Bend")
):
energy = lattice.get_value("energy", units=pytac.PHYS)
uc.set_post_eng_to_phys(utils.get_div_rigidity(energy))
uc.set_pre_phys_to_eng(utils.get_mult_rigidity(energy))
uc.phys_units = item["phys_units"]
uc.eng_units = item["eng_units"]
upper, lower = (
float(lim) if lim != "" else None
for lim in [item["upper_lim"], item["lower_lim"]]
)
uc.set_conversion_limits(lower, upper)
# TODO: This should probably be moved into the .csv files somewhere.
rigidity_families = {"hstr", "vstr", "quadrupole", "sextupole", "bend"}
if item["uc_type"] != "null" and element._families & rigidity_families:
energy = lattice.get_value("energy", units=pytac.PHYS)
uc.set_post_eng_to_phys(utils.get_div_rigidity(energy))
uc.set_pre_phys_to_eng(utils.get_mult_rigidity(energy))
element.set_unitconv(item["field"], uc)


def load(mode, control_system=None, directory=None, symmetry=None):
def load(mode, control_system=None, directory=None, symmetry=None) -> EpicsLattice:
"""Load the elements of a lattice from a directory.

Args:
Expand Down Expand Up @@ -173,9 +201,8 @@ def load(mode, control_system=None, directory=None, symmetry=None):
control_system = cothread_cs.CothreadControlSystem()
except ImportError:
raise ControlSystemException(
"Please install cothread to load a "
"lattice using the default control "
"system (found in cothread_cs.py)."
"Please install cothread to load a lattice using the default control system"
" (found in cothread_cs.py)."
)
if directory is None:
directory = Path(__file__).resolve().parent / "data"
Expand All @@ -191,31 +218,44 @@ def load(mode, control_system=None, directory=None, symmetry=None):
lat.add_element(e)
with csv_loader(mode_dir / EPICS_DEVICES_FILENAME) as csv_reader:
for item in csv_reader:
name = item["name"]
index = int(item["el_id"])
get_pv = item["get_pv"] if item["get_pv"] else None
set_pv = item["set_pv"] if item["set_pv"] else None
pve = True
d = EpicsDevice(name, control_system, pve, get_pv, set_pv)
# Devices on index 0 are attached to the lattice not elements.
target = lat if index == 0 else lat[index - 1]
target.add_device(item["field"], d, DEFAULT_UC)
# Create with a default UnitConv that returns the input unchanged.
target.add_device( # type: ignore[attr-defined]
item["field"],
EpicsDevice(item["name"], control_system, rb_pv=get_pv, sp_pv=set_pv),
NullUnitConv(),
)
# Add basic devices to the lattice.
positions = []
for elem in lat:
for elem in lat: # type: ignore[attr-defined]
positions.append(elem.s)
lat.add_device("s_position", SimpleDevice(positions, readonly=True), True)
lat.add_device(
"s_position", SimpleDevice(positions, readonly=True), NullUnitConv()
)
simple_devices_file = mode_dir / SIMPLE_DEVICES_FILENAME
if simple_devices_file.exists():
with csv_loader(simple_devices_file) as csv_reader:
for item in csv_reader:
index = int(item["el_id"])
field = item["field"]
value = float(item["value"])
readonly = item["readonly"].lower() == "true"
try:
readonly = ast.literal_eval(item["readonly"])
assert isinstance(readonly, bool)
except (ValueError, AssertionError):
raise ValueError(
f"Unable to evaluate {item['readonly']} as a boolean."
)
# Devices on index 0 are attached to the lattice not elements.
target = lat if index == 0 else lat[index - 1]
target.add_device(field, SimpleDevice(value, readonly=readonly), True)
# Create with a default UnitConv that returns the input unchanged.
target.add_device( # type: ignore[attr-defined]
item["field"],
SimpleDevice(float(item["value"]), readonly=readonly),
NullUnitConv(),
)
with csv_loader(mode_dir / FAMILIES_FILENAME) as csv_reader:
for item in csv_reader:
lat[int(item["el_id"]) - 1].add_to_family(item["family"])
Expand Down
1 change: 1 addition & 0 deletions src/pytac/units.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Classes for use in unit conversion."""

import numpy
from scipy.interpolate import PchipInterpolator

Expand Down
1 change: 1 addition & 0 deletions src/pytac/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Utility functions."""

import math

import scipy.constants
Expand Down
15 changes: 15 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,18 @@ def simple_epics_lattice(simple_epics_element, mock_cs, unit_uc):
lat.add_device("x", x_device, unit_uc)
lat.add_device("y", y_device, unit_uc)
return lat


@pytest.fixture
def mode_dir():
return CURRENT_DIR_PATH / "data/dummy"


@pytest.fixture
def polyconv_file(mode_dir):
return mode_dir / load_csv.POLY_FILENAME


@pytest.fixture
def pchipconv_file(mode_dir):
return mode_dir / load_csv.PCHIP_FILENAME
3 changes: 3 additions & 0 deletions tests/data/dummy/unitconv.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
el_id,field,uc_type,uc_id,phys_units,eng_units,lower_lim,upper_lim
2,b1,null,1,m^-2,A,0,200
4,b2,null,2,m^-3,A,-100,100
1 change: 1 addition & 0 deletions tests/test_cothread_cs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

See pytest_sessionstart() in conftest.py for more.
"""

import pytest
from constants import RB_PV, SP_PV
from cothread.catools import ca_nothing, caget, caput
Expand Down
Loading
Loading