From cd8d02b7524c0b446ff25bc64c8529ebeb1e7a3d Mon Sep 17 00:00:00 2001 From: Phil Smith Date: Thu, 25 Sep 2025 17:29:22 +0000 Subject: [PATCH 1/3] Allow toggling of additional simulator behaviour Allow selecting linopt function, disabling chromaticity and disabling_radiation. Also update the Virtac print data. --- src/virtac/__main__.py | 53 +++++++++++++++++++++++++++++++++++-- src/virtac/virtac_server.py | 43 +++++++++++++++++++++++------- 2 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/virtac/__main__.py b/src/virtac/__main__.py index 0a30ddb..2eb825e 100644 --- a/src/virtac/__main__.py +++ b/src/virtac/__main__.py @@ -1,7 +1,7 @@ -import argparse import logging import os import socket +from argparse import ArgumentError, ArgumentParser from pathlib import Path from typing import cast from warnings import warn @@ -21,7 +21,7 @@ def parse_arguments(): """Parse command line arguments sent to virtac""" - parser = argparse.ArgumentParser() + parser = ArgumentParser() parser.add_argument( "ring_mode", nargs="?", @@ -35,6 +35,28 @@ def parse_arguments(): action="store_true", default=False, ) + parser.add_argument( + "-c", + "--disable-chromaticity", + help="Disable chromaticity calculations", + action="store_true", + default=False, + ) + parser.add_argument( + "-r", + "--disable-radiation", + help="Disable radiation calculations in the simulation", + action="store_true", + default=False, + ) + parser.add_argument( + "-l", + "--linopt-function", + help="Which pyAT linear optics function to use: linopt2, linopt4, linopt6. " + "Default is linopt6", + default="linopt6", + type=str, + ) parser.add_argument( "-t", "--disable-tfb", @@ -57,6 +79,29 @@ def parse_arguments(): return parser.parse_args() +def check_sim_params(args): + """Check that we have a valid combination of simulation parameters.""" + if args.disable_radiation: + if args.linopt_function == "linopt6": + raise ArgumentError( + None, + f"Cannot disable radiation when using linopt function: " + f"{args.linopt_function}", + ) + if not args.disable_emittance: + raise ArgumentError( + None, + "You cannot calculate emittance with radiation disabled", + ) + else: + if args.linopt_function == "linopt2" or args.linopt_function == "linopt4": + raise ArgumentError( + None, + "You must disable radiation to use linopt function: " + f"{args.linopt_function}", + ) + + def configure_ca(): """Setup channel access settings for our CA server and for accessing PVs from other IOCs. We will be creating a python softioc IOC which automatically @@ -110,6 +155,7 @@ def main() -> None: logging.basicConfig(level=log_level, format=LOG_FORMAT) configure_ca() + check_sim_params(args) # Determine the ring mode if args.ring_mode is not None: @@ -138,7 +184,10 @@ def main() -> None: DATADIR / ring_mode / "feedback.csv", DATADIR / ring_mode / "mirrored.csv", DATADIR / ring_mode / "tunefb.csv", + args.linopt_function, args.disable_emittance, + args.disable_chromaticity, + args.disable_radiation, args.disable_tfb, ) diff --git a/src/virtac/virtac_server.py b/src/virtac/virtac_server.py index 9915e0a..0807b9d 100644 --- a/src/virtac/virtac_server.py +++ b/src/virtac/virtac_server.py @@ -57,12 +57,15 @@ class VirtacServer: def __init__( self, ring_mode: str, - limits_csv: Path | None = None, - bba_csv: Path | None = None, - feedback_csv: Path | None = None, - mirror_csv: Path | None = None, - tune_csv: Path | None = None, + limits_csv: str, + bba_csv: str | None = None, + feedback_csv: str | None = None, + mirror_csv: str | None = None, + tune_csv: str | None = None, + linopt_function: str = "linopt6", disable_emittance: bool = False, + disable_chromaticity: bool = False, + disable_radiation: bool = False, disable_tunefb: bool = False, ) -> None: """ @@ -81,11 +84,20 @@ def __init__( disable_emittance: Whether emittance should be disabled. disable_tunefb: Whether tune feedback should be disabled. """ + self._linopt_function: str = linopt_function self._disable_emittance: bool = disable_emittance + self._disable_chromaticity: bool = disable_chromaticity + self._disable_radiation: bool = disable_radiation + self._disable_tunefb: bool = disable_tunefb self._pv_monitoring: bool = True self.lattice: pytac.lattice.EpicsLattice = atip.utils.loader( - ring_mode, self.update_pvs, self._disable_emittance + ring_mode, + self._linopt_function, + self._disable_emittance, + self._disable_chromaticity, + self._disable_radiation, + self.update_pvs, ) self.lattice.set_default_data_source(pytac.SIM) # Holding dictionary for all PVs @@ -515,19 +527,32 @@ def print_virtac_stats(self, verbosity: int = 0) -> None: "\t Tune feedbacks is " f"{('disabled' if self._disable_tunefb else 'enabled')}" ) + print(f"\t Linear optics function is {self._linopt_function}") print( "\t Emittance calculations are " f"{('disabled' if self._disable_emittance else 'enabled')}" ) + print( + "\t Chromaticity calculations are " + f"{('disabled' if self._disable_chromaticity else 'enabled')}" + ) + print( + "\t Radiation calculations are " + f"{('disabled' if self._disable_chromaticity else 'enabled')}" + ) print( f"\t PV monitoring is {('enabled' if self._pv_monitoring else 'disabled')}" ) - print(f"\t Total pvs: {len(self._pv_dict)}") + print(f"\t Total pvs: {len(self._pv_dict)}, consisting of:") for pv_type, count in pv_type_count.items(): print(f"\t\t {pv_type.__name__} pvs: {count}") + print( + "\t Number of PVs to update after simulation recalculation: " + f"{len(self._readback_pvs_dict)}" + ) if verbosity >= 1: - print("\tAvailable PVs") + print("\t Available PVs") for pv in self._pv_dict.values(): - print(f"\t\t{pv.name}, {type(pv)}") + print(f"\t\t {pv.name}, {type(pv)}") From 6b9e42e9e85606c9d51840577e9b23d666c80c26 Mon Sep 17 00:00:00 2001 From: Phil Smith Date: Thu, 25 Sep 2025 17:29:22 +0000 Subject: [PATCH 2/3] Rework toggling of additional simulator behaviour We now use the SimParams dataclass from atip instead of storing the boolean args supplied from the CLI as seperate variables. --- src/virtac/__main__.py | 39 ++++++++----------------------- src/virtac/virtac_server.py | 46 +++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 54 deletions(-) diff --git a/src/virtac/__main__.py b/src/virtac/__main__.py index 2eb825e..7e6a0af 100644 --- a/src/virtac/__main__.py +++ b/src/virtac/__main__.py @@ -1,11 +1,12 @@ import logging import os import socket -from argparse import ArgumentError, ArgumentParser +from argparse import ArgumentParser from pathlib import Path from typing import cast from warnings import warn +from atip.simulator import SimParams from cothread.catools import ca_nothing, caget from softioc import builder, softioc @@ -79,29 +80,6 @@ def parse_arguments(): return parser.parse_args() -def check_sim_params(args): - """Check that we have a valid combination of simulation parameters.""" - if args.disable_radiation: - if args.linopt_function == "linopt6": - raise ArgumentError( - None, - f"Cannot disable radiation when using linopt function: " - f"{args.linopt_function}", - ) - if not args.disable_emittance: - raise ArgumentError( - None, - "You cannot calculate emittance with radiation disabled", - ) - else: - if args.linopt_function == "linopt2" or args.linopt_function == "linopt4": - raise ArgumentError( - None, - "You must disable radiation to use linopt function: " - f"{args.linopt_function}", - ) - - def configure_ca(): """Setup channel access settings for our CA server and for accessing PVs from other IOCs. We will be creating a python softioc IOC which automatically @@ -155,7 +133,13 @@ def main() -> None: logging.basicConfig(level=log_level, format=LOG_FORMAT) configure_ca() - check_sim_params(args) + + sim_params = SimParams( + args.linopt_function, + not args.disable_emittance, + not args.disable_chromaticity, + not args.disable_radiation, + ) # Determine the ring mode if args.ring_mode is not None: @@ -184,10 +168,7 @@ def main() -> None: DATADIR / ring_mode / "feedback.csv", DATADIR / ring_mode / "mirrored.csv", DATADIR / ring_mode / "tunefb.csv", - args.linopt_function, - args.disable_emittance, - args.disable_chromaticity, - args.disable_radiation, + sim_params, args.disable_tfb, ) diff --git a/src/virtac/virtac_server.py b/src/virtac/virtac_server.py index 0807b9d..1f30c38 100644 --- a/src/virtac/virtac_server.py +++ b/src/virtac/virtac_server.py @@ -57,15 +57,12 @@ class VirtacServer: def __init__( self, ring_mode: str, - limits_csv: str, - bba_csv: str | None = None, - feedback_csv: str | None = None, - mirror_csv: str | None = None, - tune_csv: str | None = None, - linopt_function: str = "linopt6", - disable_emittance: bool = False, - disable_chromaticity: bool = False, - disable_radiation: bool = False, + limits_csv: Path | None = None, + bba_csv: Path | None = None, + feedback_csv: Path | None = None, + mirror_csv: Path | None = None, + tune_csv: Path | None = None, + sim_params: atip.simulator.SimParams | None = None, disable_tunefb: bool = False, ) -> None: """ @@ -81,28 +78,27 @@ def __init__( mirror records, for more information see create_csv.py. tune_csv: The filepath to the .csv file from which to load the tune feedback records, for more information see create_csv.py. - disable_emittance: Whether emittance should be disabled. + sim_params (SimParams | None): An optional dataclass containing the pyAT + simulation parameters to use. disable_tunefb: Whether tune feedback should be disabled. """ - self._linopt_function: str = linopt_function - self._disable_emittance: bool = disable_emittance - self._disable_chromaticity: bool = disable_chromaticity - self._disable_radiation: bool = disable_radiation + if sim_params is None: + sim_params = atip.simulator.SimParams() + self._sim_params: atip.simulator.SimParams = sim_params self._disable_tunefb: bool = disable_tunefb self._pv_monitoring: bool = True + self.lattice: pytac.lattice.EpicsLattice = atip.utils.loader( ring_mode, - self._linopt_function, - self._disable_emittance, - self._disable_chromaticity, - self._disable_radiation, + sim_params, self.update_pvs, ) self.lattice.set_default_data_source(pytac.SIM) + # Holding dictionary for all PVs self._pv_dict: dict[str, BasePV] = {} - # Dictionary for the PVs which should be automatically updated when the + # Dictionary for the PVs which need to be automatically updated when the # simulation data is recalculated self._readback_pvs_dict: dict[str, ReadSimPV] = {} @@ -274,7 +270,7 @@ def _create_lattice_pvs(self, limits_dict: LimitsDictType) -> None: """ lat_field_dict = cast(dict[str, list[str]], self.lattice.get_fields()) lat_field_set = set(lat_field_dict[pytac.LIVE]) & set(lat_field_dict[pytac.SIM]) - if self._disable_emittance: + if not self._sim_params.emittance: lat_field_set -= {"emittance_x", "emittance_y"} for field in lat_field_set: # Ignore basic devices as they do not have PVs. @@ -325,7 +321,7 @@ def _create_feedback_records(self, feedback_csv: Path) -> None: # We can choose to not calculate emittance as it is not always required, # which decreases computation time. - if not self._disable_emittance: + if self._sim_params.emittance: name = "SR-DI-EMIT-01:STATUS" record_data = RecordData(RecordTypes.MBBI, zrvl="0", zrst="Successful") emit_status_pv = BasePV(name, record_data) @@ -527,18 +523,18 @@ def print_virtac_stats(self, verbosity: int = 0) -> None: "\t Tune feedbacks is " f"{('disabled' if self._disable_tunefb else 'enabled')}" ) - print(f"\t Linear optics function is {self._linopt_function}") + print(f"\t Linear optics function is {self._sim_params.linopt}") print( "\t Emittance calculations are " - f"{('disabled' if self._disable_emittance else 'enabled')}" + f"{('disabled' if not self._sim_params.emittance else 'enabled')}" ) print( "\t Chromaticity calculations are " - f"{('disabled' if self._disable_chromaticity else 'enabled')}" + f"{('disabled' if not self._sim_params.chromaticity else 'enabled')}" ) print( "\t Radiation calculations are " - f"{('disabled' if self._disable_chromaticity else 'enabled')}" + f"{('disabled' if not self._sim_params.radiation else 'enabled')}" ) print( f"\t PV monitoring is {('enabled' if self._pv_monitoring else 'disabled')}" From 7c9bc69d8fbb1d5c8665b668d0b14e9c59d8b377 Mon Sep 17 00:00:00 2001 From: Phil Smith Date: Tue, 22 Sep 2026 10:27:29 +0000 Subject: [PATCH 3/3] Bump atip and pytac versions --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b056e7a..5b4edc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,10 +15,10 @@ description = "Diamond virtual accelerator" dependencies = [ "numpy", "scipy", - "pytac==0.6.0", + "pytac>=1.1.0", "cothread", "softioc", - "atip>=0.2.0", + "atip>=0.3.0", "epicscorelibs", ]