From 735d4ebfb9f9abdfc0f549ee7395223c8d735b42 Mon Sep 17 00:00:00 2001 From: Ryan Roussel Date: Mon, 29 Jan 2024 14:52:25 -0600 Subject: [PATCH 1/7] create measurement class and io function --- lcls_tools/common/io.py | 13 +++++++++++++ lcls_tools/common/measurement/__init__.py | 0 lcls_tools/common/measurement/measurement.py | 17 +++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 lcls_tools/common/io.py create mode 100644 lcls_tools/common/measurement/__init__.py create mode 100644 lcls_tools/common/measurement/measurement.py diff --git a/lcls_tools/common/io.py b/lcls_tools/common/io.py new file mode 100644 index 00000000..56483c9d --- /dev/null +++ b/lcls_tools/common/io.py @@ -0,0 +1,13 @@ +from typing import Union + +from lcls_tools.common.devices.device import Device + + +def dump_data_to_h5py( + element: Union[Device, Measurement], + data: dict, + filename: str = None +): + """ + Dump data to + """ \ No newline at end of file diff --git a/lcls_tools/common/measurement/__init__.py b/lcls_tools/common/measurement/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lcls_tools/common/measurement/measurement.py b/lcls_tools/common/measurement/measurement.py new file mode 100644 index 00000000..d94ebe55 --- /dev/null +++ b/lcls_tools/common/measurement/measurement.py @@ -0,0 +1,17 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from pydantic import BaseModel, DirectoryPath + + +class Measurement(BaseModel, ABC): + name: str + device_list: list[str] + save_data: bool = True + save_location: Optional[DirectoryPath] = None + + @abstractmethod + def measure(self) -> dict: + """ Implements a measurement and returns a dictionary with the results""" + pass + From 1dc398ebce3620e2255cf460630b5b809dcd2ce1 Mon Sep 17 00:00:00 2001 From: Ryan Roussel Date: Mon, 29 Jan 2024 15:08:54 -0600 Subject: [PATCH 2/7] change python function to object --- lcls_tools/common/io.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lcls_tools/common/io.py b/lcls_tools/common/io.py index 56483c9d..f70d7d4b 100644 --- a/lcls_tools/common/io.py +++ b/lcls_tools/common/io.py @@ -1,13 +1,19 @@ -from typing import Union +from typing import Optional -from lcls_tools.common.devices.device import Device +from lcls_tools.common.measurement.measurement import Measurement -def dump_data_to_h5py( - element: Union[Device, Measurement], - data: dict, - filename: str = None -): - """ - Dump data to - """ \ No newline at end of file +class HDF5Dumper: + def __init__(self): + pass + + def dump_data_to_file( + self, + measurement_data: dict, + measurement_obj: Measurement, + filename: Optional[str] = None + ): + """ + Dump data to h5file + """ + pass From 70005818c8b27278226f9ba9e25d2ce7d8ce55b9 Mon Sep 17 00:00:00 2001 From: Ryan Roussel Date: Mon, 29 Jan 2024 15:20:12 -0600 Subject: [PATCH 3/7] implement basic ICT monitor --- lcls_tools/common/devices/ict.py | 35 +++++++++++++++++++ .../{measurement => measurements}/__init__.py | 0 lcls_tools/common/measurements/beam_charge.py | 11 ++++++ .../measurement.py | 1 - 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 lcls_tools/common/devices/ict.py rename lcls_tools/common/{measurement => measurements}/__init__.py (100%) create mode 100644 lcls_tools/common/measurements/beam_charge.py rename lcls_tools/common/{measurement => measurements}/measurement.py (93%) diff --git a/lcls_tools/common/devices/ict.py b/lcls_tools/common/devices/ict.py new file mode 100644 index 00000000..7efd03dd --- /dev/null +++ b/lcls_tools/common/devices/ict.py @@ -0,0 +1,35 @@ +from pydantic import field_validator, SerializeAsAny + +from lcls_tools.common.devices.device import Device, PVSet, ControlInformation, Metadata +from epics import PV + + +class ICTPVSet(PVSet): + """ + The PV interface for screens is not uniform. + We list the potential PVs below and only + use the ones that are set to be PV-typed after + initialisation. + """ + + charge_nC: PV + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + @field_validator("*", mode="before") + def validate_pv_fields(cls, v: str): + """Convert each PV string from YAML into a PV object""" + return PV(v) + + +class ICTControlInformation(ControlInformation): + PVs: SerializeAsAny[ICTPVSet] + + +class ICT(Device): + controls_information: SerializeAsAny[ICTControlInformation] + metadata: SerializeAsAny[Metadata] + + def get_charge(self) -> float: + return self.controls_information.PVs.charge_nC.get(as_numpy=True) diff --git a/lcls_tools/common/measurement/__init__.py b/lcls_tools/common/measurements/__init__.py similarity index 100% rename from lcls_tools/common/measurement/__init__.py rename to lcls_tools/common/measurements/__init__.py diff --git a/lcls_tools/common/measurements/beam_charge.py b/lcls_tools/common/measurements/beam_charge.py new file mode 100644 index 00000000..edc81964 --- /dev/null +++ b/lcls_tools/common/measurements/beam_charge.py @@ -0,0 +1,11 @@ +from lcls_tools.common.devices.ict import ICT +from lcls_tools.common.measurements.measurement import Measurement + + +class BeamChargeMeasurement(Measurement): + name = "beam_charge" + ict_monitor: ICT + + def measure(self) -> dict: + return {"bunch_charge_nC": self.ict_monitor.get_charge()} + diff --git a/lcls_tools/common/measurement/measurement.py b/lcls_tools/common/measurements/measurement.py similarity index 93% rename from lcls_tools/common/measurement/measurement.py rename to lcls_tools/common/measurements/measurement.py index d94ebe55..a42a984f 100644 --- a/lcls_tools/common/measurement/measurement.py +++ b/lcls_tools/common/measurements/measurement.py @@ -6,7 +6,6 @@ class Measurement(BaseModel, ABC): name: str - device_list: list[str] save_data: bool = True save_location: Optional[DirectoryPath] = None From f8cc57357050be731b342a559d40076ae1194b39 Mon Sep 17 00:00:00 2001 From: Ryan Roussel Date: Wed, 31 Jan 2024 09:28:34 -0600 Subject: [PATCH 4/7] rename IO object, fix ict docstring --- lcls_tools/common/devices/ict.py | 1 - lcls_tools/common/io.py | 17 +++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lcls_tools/common/devices/ict.py b/lcls_tools/common/devices/ict.py index 7efd03dd..12f96946 100644 --- a/lcls_tools/common/devices/ict.py +++ b/lcls_tools/common/devices/ict.py @@ -6,7 +6,6 @@ class ICTPVSet(PVSet): """ - The PV interface for screens is not uniform. We list the potential PVs below and only use the ones that are set to be PV-typed after initialisation. diff --git a/lcls_tools/common/io.py b/lcls_tools/common/io.py index f70d7d4b..792ece01 100644 --- a/lcls_tools/common/io.py +++ b/lcls_tools/common/io.py @@ -1,19 +1,28 @@ from typing import Optional -from lcls_tools.common.measurement.measurement import Measurement +from lcls_tools.common.measurements.measurement import Measurement -class HDF5Dumper: +class HDF5IO: def __init__(self): pass - def dump_data_to_file( + def write( self, measurement_data: dict, measurement_obj: Measurement, filename: Optional[str] = None ): """ - Dump data to h5file + Write data to h5file + """ + pass + + def read( + self, + filename: Optional[str] = None + ): + """ + Read data from h5file """ pass From 8167a54e61161753c972bc1771a7563c0869e5b4 Mon Sep 17 00:00:00 2001 From: Ryan Roussel Date: Wed, 31 Jan 2024 13:36:31 -0600 Subject: [PATCH 5/7] replace pass with NotImplementedError --- lcls_tools/common/io.py | 4 ++-- lcls_tools/common/measurements/measurement.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lcls_tools/common/io.py b/lcls_tools/common/io.py index 792ece01..73fb61d6 100644 --- a/lcls_tools/common/io.py +++ b/lcls_tools/common/io.py @@ -16,7 +16,7 @@ def write( """ Write data to h5file """ - pass + raise NotImplementedError def read( self, @@ -25,4 +25,4 @@ def read( """ Read data from h5file """ - pass + raise NotImplementedError diff --git a/lcls_tools/common/measurements/measurement.py b/lcls_tools/common/measurements/measurement.py index a42a984f..4933ff52 100644 --- a/lcls_tools/common/measurements/measurement.py +++ b/lcls_tools/common/measurements/measurement.py @@ -12,5 +12,5 @@ class Measurement(BaseModel, ABC): @abstractmethod def measure(self) -> dict: """ Implements a measurement and returns a dictionary with the results""" - pass + raise NotImplementedError From 0948fb3bf191f9ae15e9b6db68d596d396a5cbe8 Mon Sep 17 00:00:00 2001 From: Ryan Roussel Date: Wed, 14 Feb 2024 14:34:18 -0600 Subject: [PATCH 6/7] adds multi-shot capabilities and averaging to the ICT example measurement class --- lcls_tools/common/measurements/beam_charge.py | 45 ++++++++++++++++++- lcls_tools/common/measurements/measurement.py | 2 +- lcls_tools/common/measurements/utils.py | 12 +++++ 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 lcls_tools/common/measurements/utils.py diff --git a/lcls_tools/common/measurements/beam_charge.py b/lcls_tools/common/measurements/beam_charge.py index edc81964..8925adde 100644 --- a/lcls_tools/common/measurements/beam_charge.py +++ b/lcls_tools/common/measurements/beam_charge.py @@ -1,11 +1,52 @@ +import numpy as np +from pydantic import PositiveFloat + from lcls_tools.common.devices.ict import ICT from lcls_tools.common.measurements.measurement import Measurement +import time + +from lcls_tools.common.measurements.utils import calculate_statistics class BeamChargeMeasurement(Measurement): name = "beam_charge" ict_monitor: ICT + wait_time: PositiveFloat = 1.0 + + def measure(self, n_shots: int = 1) -> dict: + """ + Measure the bunch charge using an ICT monitor. + + Parameters: + - n_shots (int, optional): The number of measurements to perform. Defaults to 1. + + Returns: + dict: A dictionary containing the measured bunch charge values and additional + statistics if multiple shots are taken. + + If n_shots is 1, the function returns a dictionary with the key "bunch_charge_nC" + and the corresponding single measurement value. + + If n_shots is greater than 1, the function performs multiple measurements with + the specified wait time and returns a dictionary with the key "bunch_charge_nC" + containing a list of measured values. Additionally, statistical information + (mean, standard deviation, etc.) is included in the dictionary. + + + """ + if n_shots == 1: + return {"bunch_charge_nC": self.ict_monitor.get_charge()} + elif n_shots > 1: + bunch_charges = [] + for i in range(n_shots): + bunch_charges += [self.ict_monitor.get_charge()] + time.sleep(self.wait_time) + + # add statistics to results + results = {"bunch_charge_nC": bunch_charges} + results = results | calculate_statistics( + np.array(bunch_charges), "bunch_charge_nC" + ) - def measure(self) -> dict: - return {"bunch_charge_nC": self.ict_monitor.get_charge()} + return results diff --git a/lcls_tools/common/measurements/measurement.py b/lcls_tools/common/measurements/measurement.py index 4933ff52..c1722374 100644 --- a/lcls_tools/common/measurements/measurement.py +++ b/lcls_tools/common/measurements/measurement.py @@ -10,7 +10,7 @@ class Measurement(BaseModel, ABC): save_location: Optional[DirectoryPath] = None @abstractmethod - def measure(self) -> dict: + def measure(self, **kwargs) -> dict: """ Implements a measurement and returns a dictionary with the results""" raise NotImplementedError diff --git a/lcls_tools/common/measurements/utils.py b/lcls_tools/common/measurements/utils.py new file mode 100644 index 00000000..88915adb --- /dev/null +++ b/lcls_tools/common/measurements/utils.py @@ -0,0 +1,12 @@ +import numpy as np +from numpy import ndarray + + +def calculate_statistics(data: ndarray, name): + return { + f"{name}_mean": np.mean(data), + f"{name}_std": np.std(data), + f"{name}_q05": np.quantile(data, 0.05), + f"{name}_q95": np.quantile(data, 0.95), + } + From eb03c33c744c1d55e6c0be07d0581497ff497020 Mon Sep 17 00:00:00 2001 From: Ryan Roussel Date: Tue, 20 Feb 2024 22:15:06 -0600 Subject: [PATCH 7/7] fixes to pass flake8 --- lcls_tools/common/measurements/beam_charge.py | 1 - lcls_tools/common/measurements/measurement.py | 1 - lcls_tools/common/measurements/utils.py | 1 - 3 files changed, 3 deletions(-) diff --git a/lcls_tools/common/measurements/beam_charge.py b/lcls_tools/common/measurements/beam_charge.py index 8925adde..adfcd0f7 100644 --- a/lcls_tools/common/measurements/beam_charge.py +++ b/lcls_tools/common/measurements/beam_charge.py @@ -49,4 +49,3 @@ def measure(self, n_shots: int = 1) -> dict: ) return results - diff --git a/lcls_tools/common/measurements/measurement.py b/lcls_tools/common/measurements/measurement.py index c1722374..3917c3d1 100644 --- a/lcls_tools/common/measurements/measurement.py +++ b/lcls_tools/common/measurements/measurement.py @@ -13,4 +13,3 @@ class Measurement(BaseModel, ABC): def measure(self, **kwargs) -> dict: """ Implements a measurement and returns a dictionary with the results""" raise NotImplementedError - diff --git a/lcls_tools/common/measurements/utils.py b/lcls_tools/common/measurements/utils.py index 88915adb..d5198891 100644 --- a/lcls_tools/common/measurements/utils.py +++ b/lcls_tools/common/measurements/utils.py @@ -9,4 +9,3 @@ def calculate_statistics(data: ndarray, name): f"{name}_q05": np.quantile(data, 0.05), f"{name}_q95": np.quantile(data, 0.95), } -