Skip to content

Commit 2560985

Browse files
committed
HLA-825 Make BPM records from csv
Created new func in create_csv which adds BBA pvs to the new bba.csv file, previously some of these PVs were defined in feedback.csv and some at runtime in atip_server/_create_feedback_records. Removed BBA special case record creation from atip_server/_create_feedback_records and instead load them from csv in _create_bba_records. Reran creation of csv files for DIAD and I04
1 parent 56a5cb8 commit 2560985

File tree

11 files changed

+5515
-5189
lines changed

11 files changed

+5515
-5189
lines changed

src/virtac/atip_ioc_entry.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def main():
6262
server = atip_server.ATIPServer(
6363
ring_mode,
6464
DATADIR / ring_mode / "limits.csv",
65+
DATADIR / ring_mode / "bba.csv",
6566
DATADIR / ring_mode / "feedback.csv",
6667
DATADIR / ring_mode / "mirrored.csv",
6768
DATADIR / ring_mode / "tunefb.csv",

src/virtac/atip_server.py

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import ast
21
import csv
32
from warnings import warn
43

@@ -59,6 +58,7 @@ def __init__(
5958
self,
6059
ring_mode,
6160
limits_csv=None,
61+
bba_csv=None,
6262
feedback_csv=None,
6363
mirror_csv=None,
6464
tune_csv=None,
@@ -70,7 +70,10 @@ def __init__(
7070
limits_csv (str): The filepath to the .csv file from which to
7171
load the pv limits, for more information
7272
see create_csv.py.
73-
feedback_csv (str): The filepath to the .csv file from which to
73+
bba_csv (str): The filepath to the .csv file from which to
74+
load the bba records, for more
75+
information see create_csv.py.
76+
feedback_csv (string): The filepath to the .csv file from which to
7477
load the feedback records, for more
7578
information see create_csv.py.
7679
mirror_csv (str): The filepath to the .csv file from which to
@@ -88,12 +91,15 @@ def __init__(
8891
self._in_records = {}
8992
self._out_records = {}
9093
self._rb_only_records = []
94+
self._bba_records = {}
9195
self._feedback_records = {}
9296
self._mirrored_records = {}
9397
self._monitored_pvs = {}
9498
self._offset_pvs = {}
9599
print("Starting record creation.")
96100
self._create_records(limits_csv, disable_emittance)
101+
if bba_csv is not None:
102+
self._create_bba_records(bba_csv)
97103
if feedback_csv is not None:
98104
self._create_feedback_records(feedback_csv, disable_emittance)
99105
if mirror_csv is not None:
@@ -114,6 +120,7 @@ def all_record_names(self):
114120
list(self._in_records.keys())
115121
+ list(self._out_records.keys())
116122
+ list(self._feedback_records.values())
123+
+ list(self._bba_records.values())
117124
+ mirrored
118125
)
119126
return {record.name: record for record in all_records}
@@ -291,10 +298,47 @@ def _on_update(self, value, name):
291298
field, value, units=pytac.ENG, data_source=pytac.SIM
292299
)
293300

301+
def _create_bba_records(self, bba_csv):
302+
"""Create all the beam-based-alignment records from the .csv file at the
303+
location passed, see create_csv.py for more information.
304+
305+
Args:
306+
bba_csv (str): The filepath to the .csv file to load the
307+
records in accordance with.
308+
"""
309+
# We don't set limits or precision but this shouldn't be an issue as these
310+
# records aren't really intended to be set to by a user.
311+
csv_reader = csv.DictReader(open(bba_csv))
312+
for line in csv_reader:
313+
prefix, suffix = line["pv"].split(":", 1)
314+
builder.SetDeviceName(prefix)
315+
if line["record_type"] == "ai":
316+
record = builder.aIn(
317+
suffix, initial_value=int(line["value"]), MDEL="-1"
318+
)
319+
self._bba_records[(int(line["index"]), line["field"])] = record
320+
elif line["record_type"] == "ao":
321+
record = builder.aOut(
322+
suffix, initial_value=int(line["value"]), always_update=True
323+
)
324+
self._bba_records[(int(line["index"]), line["field"])] = record
325+
elif line["record_type"] == "wfm":
326+
record = builder.WaveformOut(
327+
suffix,
328+
# We remove the [] around the string
329+
initial_value=numpy.fromstring(
330+
(line["value"])[1:-1], dtype=int, sep=" "
331+
),
332+
always_update=True,
333+
)
334+
self._bba_records[(int(line["index"]), line["field"])] = record
335+
else:
336+
raise ValueError
337+
294338
def _create_feedback_records(self, feedback_csv, disable_emittance):
295339
"""Create all the feedback records from the .csv file at the location
296-
passed, see create_csv.py for more information; records for two edge
297-
cases are also created.
340+
passed, see create_csv.py for more information; records for one edge
341+
case are also created.
298342
299343
Args:
300344
feedback_csv (str): The filepath to the .csv file to load the
@@ -306,25 +350,25 @@ def _create_feedback_records(self, feedback_csv, disable_emittance):
306350
# records aren't really intended to be set to by a user.
307351
csv_reader = csv.DictReader(open(feedback_csv))
308352
for line in csv_reader:
309-
try:
310-
readonly = ast.literal_eval(line["read-only"])
311-
assert isinstance(readonly, bool)
312-
except (ValueError, AssertionError) as exc:
313-
raise ValueError(
314-
f"Unable to evaluate {line['read-only']} as a boolean."
315-
) from exc
316353
prefix, suffix = line["pv"].split(":", 1)
317354
builder.SetDeviceName(prefix)
318-
if readonly:
319-
in_record = builder.aIn(
355+
if line["record_type"] == "ai":
356+
record = builder.aIn(
320357
suffix, initial_value=int(line["value"]), MDEL="-1"
321358
)
322-
self._feedback_records[(int(line["index"]), line["field"])] = in_record
323-
else:
324-
out_record = builder.aOut(
359+
self._feedback_records[(int(line["index"]), line["field"])] = record
360+
elif line["record_type"] == "ao":
361+
record = builder.aOut(
362+
suffix, initial_value=int(line["value"]), always_update=True
363+
)
364+
self._feedback_records[(int(line["index"]), line["field"])] = record
365+
elif line["record_type"] == "wfm":
366+
record = builder.WaveformOut(
325367
suffix, initial_value=int(line["value"]), always_update=True
326368
)
327-
self._feedback_records[(int(line["index"]), line["field"])] = out_record
369+
self._feedback_records[(int(line["index"]), line["field"])] = record
370+
else:
371+
raise ValueError
328372
# Special case: BPM ID for the x axis of beam position plot, since we
329373
# cannot currently create Waveform records via CSV.
330374
bpm_ids = [
@@ -336,28 +380,7 @@ def _create_feedback_records(self, feedback_csv, disable_emittance):
336380
"BPMID", NELM=len(bpm_ids), initial_value=bpm_ids
337381
)
338382
self._feedback_records[(0, "bpm_id")] = bpm_id_record
339-
# Special case: Fast BBA oscillation PVs - should also be moved to CSV in future
340-
for cell in range(1, self.lattice.symmetry + 1):
341-
cell = str(cell).zfill(2)
342-
builder.SetDeviceName(f"SR{cell}A-CS-FOFB-01")
343-
start_times = builder.WaveformOut(
344-
"EXCITE:START_TIMES", initial_value=numpy.zeros(18)
345-
)
346-
self._feedback_records[(0, f"cell_{cell}_excite_start_times")] = start_times
347-
excite_amps = builder.WaveformOut(
348-
"EXCITE:AMPS", initial_value=numpy.zeros(18)
349-
)
350-
self._feedback_records[(0, f"cell_{cell}_excite_amps")] = excite_amps
351-
excite_deltas = builder.WaveformOut(
352-
"EXCITE:DELTAS", initial_value=numpy.zeros(18)
353-
)
354-
self._feedback_records[(0, f"cell_{cell}_excite_deltas")] = excite_deltas
355-
excite_ticks = builder.WaveformOut(
356-
"EXCITE:TICKS", initial_value=numpy.zeros(18)
357-
)
358-
self._feedback_records[(0, f"cell_{cell}_excite_ticks")] = excite_ticks
359-
excite_prime = builder.aOut("EXCITE:PRIME", initial_value=1)
360-
self._feedback_records[(0, f"cell_{cell}_excite_prime")] = excite_prime
383+
361384
# We can choose to not calculate emittance as it is not always required,
362385
# which decreases computation time.
363386
if not disable_emittance:
@@ -546,13 +569,14 @@ def set_feedback_record(self, index, field, value):
546569
"""
547570
try:
548571
self._feedback_records[(index, field)].set(value)
572+
self._bba_records[(index, field)].set(value)
549573
except KeyError as exc:
550574
if index == 0:
551575
raise FieldException(
552576
f"Simulated lattice {self.lattice} does not have field {field}."
553577
) from exc
554578
else:
555579
raise FieldException(
556-
f"Simulated element {self.lattice[index]} does not have "
557-
f"field {field}."
580+
f"Simulated element {self.lattice[index]} does not have \
581+
field {field}."
558582
) from exc

src/virtac/create_csv.py

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import csv
77
import os
88

9+
import numpy as np
910
import pytac
1011
from cothread.catools import FORMAT_CTRL, caget
1112

@@ -23,54 +24,104 @@ def generate_feedback_pvs(all_elements):
2324
+ all_elements.q1b
2425
)
2526
# Data to be written is stored as a list of tuples each with structure:
26-
# element index (int), field (str), pv (str), value (int).
27+
# element index (int), field (str), pv (str), value (int), record_type (str).
2728
# We have special cases for four lattice fields that feedback systems read from.
2829
data = [
29-
("index", "field", "pv", "value", "read-only"),
30-
(0, "beam_current", "SR-DI-DCCT-01:SIGNAL", 300, True),
31-
(0, "feedback_status", "CS-CS-MSTAT-01:FBSTAT", 2, True),
32-
(0, "fofb_status", "SR01A-CS-FOFB-01:RUN", 0, False),
33-
(0, "feedback_heart", "CS-CS-MSTAT-01:FBHEART", 10, False),
30+
("index", "field", "pv", "value", "record_type"),
31+
(0, "beam_current", "SR-DI-DCCT-01:SIGNAL", 300, "ai"),
32+
(0, "feedback_status", "CS-CS-MSTAT-01:FBSTAT", 2, "ai"),
33+
(0, "fofb_status", "SR01A-CS-FOFB-01:RUN", 0, "ao"),
34+
(0, "feedback_heart", "CS-CS-MSTAT-01:FBHEART", 10, "ao"),
3435
]
3536
# Iterate over our elements to get the PV names.
3637
for elem in all_elements.hstr:
3738
pv_stem = elem.get_device("x_kick").name
38-
data.append((elem.index, "error_sum", pv_stem + ":ERCSUM", 0, True))
39-
data.append((elem.index, "state", pv_stem + ":STATE", 2, True))
39+
data.append((elem.index, "error_sum", pv_stem + ":ERCSUM", 0, "ai"))
40+
data.append((elem.index, "state", pv_stem + ":STATE", 2, "ai"))
4041
for elem in all_elements.vstr:
4142
pv_stem = elem.get_device("y_kick").name
42-
data.append((elem.index, "error_sum", pv_stem + ":ERCSUM", 0, True))
43-
data.append((elem.index, "state", pv_stem + ":STATE", 2, True))
43+
data.append((elem.index, "error_sum", pv_stem + ":ERCSUM", 0, "ai"))
44+
data.append((elem.index, "state", pv_stem + ":STATE", 2, "ai"))
4445
for elem in all_elements.bpm:
4546
data.append(
46-
(elem.index, "enabled", elem.get_pv_name("enabled", pytac.RB), 1, True)
47+
(elem.index, "enabled", elem.get_pv_name("enabled", pytac.RB), 1, "ai")
4748
)
4849
# Add elements for Tune Feedback
4950
for elem in tune_quad_elements:
5051
data.append(
51-
(elem.index, "offset", elem.get_device("b1").name + ":OFFSET1", 0, True)
52+
(elem.index, "offset", elem.get_device("b1").name + ":OFFSET1", 0, "ai")
5253
)
5354
return data
5455

5556

56-
def generate_bba_pvs(all_elements):
57+
def generate_bba_pvs(all_elements, symmetry):
5758
"""Data to be written is stored as a list of tuples each with structure:
58-
element index (int), field (str), pv (str), value (int).
59+
element index (int), field (str), pv (str), value (int), record_type (str).
5960
"""
60-
data = [("index", "field", "pv", "value", "read-only")]
61+
data = [("index", "field", "pv", "value", "record_type")]
6162
# Iterate over the BPMs to construct the PV names.
6263
for elem in all_elements.bpm:
6364
pv_stem = elem.get_device("enabled").name
6465
data.append(
65-
(elem.index, "golden_offset_x", pv_stem + ":CF:GOLDEN_X_S", 0, False)
66+
(elem.index, "golden_offset_x", pv_stem + ":CF:GOLDEN_X_S", 0, "ao")
6667
)
6768
data.append(
68-
(elem.index, "golden_offset_y", pv_stem + ":CF:GOLDEN_Y_S", 0, False)
69+
(elem.index, "golden_offset_y", pv_stem + ":CF:GOLDEN_Y_S", 0, "ao")
70+
)
71+
data.append((elem.index, "bcd_offset_x", pv_stem + ":CF:BCD_X_S", 0, "ao"))
72+
data.append((elem.index, "bcd_offset_y", pv_stem + ":CF:BCD_Y_S", 0, "ao"))
73+
data.append((elem.index, "bba_offset_x", pv_stem + ":CF:BBA_X_S", 0, "ao"))
74+
data.append((elem.index, "bba_offset_y", pv_stem + ":CF:BBA_Y_S", 0, "ao"))
75+
for cell in range(1, symmetry + 1):
76+
cell = str(cell).zfill(2)
77+
pv_stem = f"SR{cell}A-CS-FOFB-01"
78+
# Waveform records
79+
data.append(
80+
(
81+
cell,
82+
f"cell_{cell}_excite_start_times",
83+
f"{pv_stem}:EXCITE:START_TIMES",
84+
np.zeros(18),
85+
"wfm",
86+
)
87+
)
88+
data.append(
89+
(
90+
cell,
91+
f"cell_{cell}_excite_amps",
92+
f"{pv_stem}:EXCITE:AMPS",
93+
np.zeros(18),
94+
"wfm",
95+
)
96+
)
97+
data.append(
98+
(
99+
cell,
100+
f"cell_{cell}_excite_deltas",
101+
f"{pv_stem}:EXCITE:DELTAS",
102+
np.zeros(18),
103+
"wfm",
104+
)
105+
)
106+
data.append(
107+
(
108+
cell,
109+
f"cell_{cell}_excite_ticks",
110+
f"{pv_stem}:EXCITE:TICKS",
111+
np.zeros(18),
112+
"wfm",
113+
)
114+
)
115+
# ao record
116+
data.append(
117+
(
118+
cell,
119+
f"cell_{cell}_excite_prime",
120+
f"{pv_stem}:EXCITE:PRIME",
121+
0,
122+
"ao",
123+
)
69124
)
70-
data.append((elem.index, "bcd_offset_x", pv_stem + ":CF:BCD_X_S", 0, False))
71-
data.append((elem.index, "bcd_offset_y", pv_stem + ":CF:BCD_Y_S", 0, False))
72-
data.append((elem.index, "bba_offset_x", pv_stem + ":CF:BBA_X_S", 0, False))
73-
data.append((elem.index, "bba_offset_y", pv_stem + ":CF:BBA_Y_S", 0, False))
74125
return data
75126

76127

@@ -136,7 +187,9 @@ def generate_mirrored_pvs(lattice):
136187
value:
137188
The inital value of the output record.
138189
"""
139-
data = [("output type", "mirror type", "in", "out", "value")]
190+
data: list[tuple[str, str, str, str, int]] = [
191+
("output type", "mirror type", "in", "out", "value")
192+
]
140193
# Tune PV aliases.
141194
tune = [
142195
lattice.get_value("tune_x", pytac.RB, data_source=pytac.SIM),
@@ -308,6 +361,11 @@ def parse_arguments():
308361
help="Filename for output tune feedback offset PVs CSV file",
309362
default="tunefb.csv",
310363
)
364+
parser.add_argument(
365+
"--bba",
366+
help="Filename for output beam-based-alignment PVs CSV file",
367+
default="bba.csv",
368+
)
311369
return parser.parse_args()
312370

313371

@@ -316,8 +374,9 @@ def parse_arguments():
316374
lattice = atip.utils.loader(args.ring_mode)
317375
all_elements = atip.utils.preload(lattice)
318376
data = generate_feedback_pvs(all_elements)
319-
data.extend(generate_bba_pvs(all_elements)[1:])
320377
write_data_to_file(data, args.feedback, args.ring_mode)
378+
data = generate_bba_pvs(all_elements, lattice.symmetry)
379+
write_data_to_file(data, args.bba, args.ring_mode)
321380
data = generate_pv_limits(lattice)
322381
write_data_to_file(data, args.limits, args.ring_mode)
323382
data = generate_mirrored_pvs(lattice)

0 commit comments

Comments
 (0)