From 0603e9fd4c129b3a8bfd0c87c98ff46f3f7212c4 Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 6 Nov 2025 12:32:19 +0100 Subject: [PATCH 1/7] Implemented sliced arrays --- pyaml/arrays/bpm_array.py | 13 +++---- pyaml/arrays/cfm_magnet_array.py | 14 +++----- pyaml/arrays/element_array.py | 59 +++++++++++++++++++++++--------- pyaml/arrays/magnet_array.py | 15 +++----- 4 files changed, 57 insertions(+), 44 deletions(-) diff --git a/pyaml/arrays/bpm_array.py b/pyaml/arrays/bpm_array.py index 508b475c..70123f22 100644 --- a/pyaml/arrays/bpm_array.py +++ b/pyaml/arrays/bpm_array.py @@ -1,7 +1,7 @@ from ..common.abstract import ReadFloatArray from ..bpm.bpm import BPM from ..control.deviceaccesslist import DeviceAccessList -from .element_array import get_peer_from_array +from .element_array import ElementArray import numpy as np @@ -53,7 +53,7 @@ def set_aggregator(self,agg:DeviceAccessList): -class BPMArray(list[BPM]): +class BPMArray(ElementArray): """ Class that implements access to a BPM array """ @@ -72,23 +72,18 @@ def __init__(self,arrayName:str,bpms:list[BPM],use_aggregator = True): use_aggregator : bool Use aggregator to increase performance by using paralell access to underlying devices. """ - super().__init__(i for i in bpms) - self.__name = arrayName - holder = get_peer_from_array(self) + super().__init__(arrayName,bpms,use_aggregator) self.__hvpos = RWBPMPosition(arrayName,bpms) self.__hpos = RWBPMSinglePosition(arrayName,bpms,0) self.__vpos = RWBPMSinglePosition(arrayName,bpms,1) if use_aggregator: - aggs = holder.create_bpm_aggregators(bpms) + aggs = self.get_peer().create_bpm_aggregators(bpms) self.__hvpos.set_aggregator(aggs[0]) self.__hpos.set_aggregator(aggs[1]) self.__vpos.set_aggregator(aggs[2]) - def get_name(self) -> str: - return self.__name - @property def positions(self) -> RWBPMPosition: """ diff --git a/pyaml/arrays/cfm_magnet_array.py b/pyaml/arrays/cfm_magnet_array.py index b43eab42..57c594c2 100644 --- a/pyaml/arrays/cfm_magnet_array.py +++ b/pyaml/arrays/cfm_magnet_array.py @@ -1,6 +1,7 @@ from ..common.abstract import ReadWriteFloatArray from ..magnet.cfm_magnet import CombinedFunctionMagnet -from .element_array import get_peer_from_array +from .element_array import ElementArray +from ..common.exception import PyAMLException import numpy as np #TODO handle aggregator for CFM @@ -76,7 +77,7 @@ def unit(self) -> list[str]: return r -class CombinedFunctionMagnetArray(list[CombinedFunctionMagnet]): +class CombinedFunctionMagnetArray(ElementArray): """ Class that implements access to a magnet array """ @@ -95,18 +96,13 @@ def __init__(self,arrayName:str,magnets:list[CombinedFunctionMagnet],use_aggrega use_aggregator : bool Use aggregator to increase performance by using paralell access to underlying devices. """ - super().__init__(i for i in magnets) - self.__name = arrayName - holder = get_peer_from_array(self) + super().__init__(arrayName,magnets) self.__rwstrengths = RWMagnetStrengths(arrayName,magnets) self.__rwhardwares = RWMagnetHardwares(arrayName,magnets) if use_aggregator: - raise("Aggregator not implemented for CombinedFunctionMagnetArray") - - def get_name(self) -> str: - return self.__name + raise(PyAMLException("Aggregator not implemented for CombinedFunctionMagnetArray")) @property def strengths(self) -> RWMagnetStrengths: diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index baf9f3f5..a544c635 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -1,21 +1,16 @@ from ..common.element import Element +from ..magnet.magnet import Magnet +from ..bpm.bpm import BPM +from ..magnet.cfm_magnet import CombinedFunctionMagnet from ..common.exception import PyAMLException - -def get_peer_from_array(array): - """ - Returns the peer (Simulator or ControlSystem) of an element list - """ - peer = array[0]._peer if len(array)>0 else None - if peer is None or any([m._peer!=peer for m in array]): - raise PyAMLException(f"{array.__class__.__name__} {array.get_name()}: All elements must be attached to the same instance of either a Simulator or a ControlSystem") - return peer +import importlib class ElementArray(list[Element]): """ Class that implements access to a magnet array """ - def __init__(self,arrayName:str,elements:list[Element]): + def __init__(self,arrayName:str,elements:list[Element],use_aggregator = True): """ Construct an element array @@ -29,17 +24,49 @@ def __init__(self,arrayName:str,elements:list[Element]): """ super().__init__(i for i in elements) self.__name = arrayName - holder = get_peer_from_array(self) - self.__name = arrayName + self.__peer = self[0]._peer if len(self)>0 else None + self.__use_aggretator = use_aggregator + if self.__peer is None or any([m._peer!=self.__peer for m in self]): + raise PyAMLException(f"{self.__class__.__name__} {self.get_name()}: All elements must be attached to the same instance of either a Simulator or a ControlSystem") + + def get_peer(self): + """ + Returns the peer (Simulator or ControlSystem) of an element list + """ + return self.__peer def get_name(self) -> str: return self.__name def names(self) -> list[str]: return [e.get_name() for e in self] - - + def __getitem__(self,key): + if(isinstance(key,int)): + return super().__getitem__(key) + elif(isinstance(key,slice)): - - \ No newline at end of file + # Check type + eltType = None + r = [] + for i in range(*key.indices(len(self))): + if eltType is None: + eltType = type(self[i]) + elif not isinstance(self[i],eltType): + eltType = Element # Fall back to element + r.append(self[i]) + + if issubclass(eltType,Magnet): + m = importlib.import_module("pyaml.arrays.magnet_array") + arrayClass = getattr(m, "MagnetArray", None) + return arrayClass("",r,self.__use_aggretator) + elif issubclass(eltType,BPM): + m = importlib.import_module("pyaml.arrays.bpm_array") + arrayClass = getattr(m, "BPMArray", None) + return arrayClass("",r,self.__use_aggretator) + elif issubclass(eltType,CombinedFunctionMagnet): + m = importlib.import_module("pyaml.arrays.cfm_array") + arrayClass = getattr(m, "CombinedFunctionMagnetArray", None) + return arrayClass("",r,self.__use_aggretator) + else: + raise PyAMLException(f"Unsupported sliced array for type {str(eltType)}") diff --git a/pyaml/arrays/magnet_array.py b/pyaml/arrays/magnet_array.py index 288870b1..a67519e5 100644 --- a/pyaml/arrays/magnet_array.py +++ b/pyaml/arrays/magnet_array.py @@ -1,7 +1,7 @@ from ..common.abstract import ReadWriteFloatArray from ..magnet.magnet import Magnet from ..common.abstract_aggregator import ScalarAggregator -from .element_array import get_peer_from_array +from .element_array import ElementArray import numpy as np class RWMagnetStrength(ReadWriteFloatArray): @@ -76,7 +76,7 @@ def unit(self) -> list[str]: def set_aggregator(self,agg:ScalarAggregator): self.__aggregator = agg -class MagnetArray(list[Magnet]): +class MagnetArray(ElementArray): """ Class that implements access to a magnet array """ @@ -95,22 +95,17 @@ def __init__(self,arrayName:str,magnets:list[Magnet],use_aggregator = True): use_aggregator : bool Use aggregator to increase performance by using paralell access to underlying devices. """ - super().__init__(i for i in magnets) - self.__name = arrayName - holder = get_peer_from_array(self) + super().__init__(arrayName,magnets,use_aggregator) self.__rwstrengths = RWMagnetStrength(arrayName,magnets) self.__rwhardwares = RWMagnetHardware(arrayName,magnets) if use_aggregator: - aggs = holder.create_magnet_strength_aggregator(magnets) - aggh = holder.create_magnet_harddware_aggregator(magnets) + aggs = self.get_peer().create_magnet_strength_aggregator(magnets) + aggh = self.get_peer().create_magnet_harddware_aggregator(magnets) self.__rwstrengths.set_aggregator(aggs) self.__rwhardwares.set_aggregator(aggh) - def get_name(self) -> str: - return self.__name - @property def strengths(self) -> RWMagnetStrength: """ From 7a21edce93165d4d6f71b252767102d7b03f45cd Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 6 Nov 2025 12:41:46 +0100 Subject: [PATCH 2/7] Fix for Element and CFM --- pyaml/arrays/cfm_magnet_array.py | 2 +- pyaml/arrays/element_array.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyaml/arrays/cfm_magnet_array.py b/pyaml/arrays/cfm_magnet_array.py index 57c594c2..30b1cd93 100644 --- a/pyaml/arrays/cfm_magnet_array.py +++ b/pyaml/arrays/cfm_magnet_array.py @@ -96,7 +96,7 @@ def __init__(self,arrayName:str,magnets:list[CombinedFunctionMagnet],use_aggrega use_aggregator : bool Use aggregator to increase performance by using paralell access to underlying devices. """ - super().__init__(arrayName,magnets) + super().__init__(arrayName,magnets,use_aggregator) self.__rwstrengths = RWMagnetStrengths(arrayName,magnets) self.__rwhardwares = RWMagnetHardwares(arrayName,magnets) diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index a544c635..7e740ba5 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -68,5 +68,7 @@ def __getitem__(self,key): m = importlib.import_module("pyaml.arrays.cfm_array") arrayClass = getattr(m, "CombinedFunctionMagnetArray", None) return arrayClass("",r,self.__use_aggretator) + elif issubclass(eltType,Element): + return ElementArray("",r,self.__use_aggretator) else: raise PyAMLException(f"Unsupported sliced array for type {str(eltType)}") From 2c2d688da5f9bd1d3cb69fc9bfedcbf397db87a2 Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 6 Nov 2025 14:27:38 +0100 Subject: [PATCH 3/7] Implemented selection by name --- pyaml/arrays/element_array.py | 68 ++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index 7e740ba5..45f37986 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -3,7 +3,9 @@ from ..bpm.bpm import BPM from ..magnet.cfm_magnet import CombinedFunctionMagnet from ..common.exception import PyAMLException + import importlib +import fnmatch class ElementArray(list[Element]): """ @@ -40,13 +42,39 @@ def get_name(self) -> str: def names(self) -> list[str]: return [e.get_name() for e in self] - + + def __create_array(self,arrName:str,eltType:type,elements:list): + + if len(elements)==0: + return [] + + if issubclass(eltType,Magnet): + m = importlib.import_module("pyaml.arrays.magnet_array") + arrayClass = getattr(m, "MagnetArray", None) + return arrayClass("",elements,self.__use_aggretator) + elif issubclass(eltType,BPM): + m = importlib.import_module("pyaml.arrays.bpm_array") + arrayClass = getattr(m, "BPMArray", None) + return arrayClass("",elements,self.__use_aggretator) + elif issubclass(eltType,CombinedFunctionMagnet): + m = importlib.import_module("pyaml.arrays.cfm_array") + arrayClass = getattr(m, "CombinedFunctionMagnetArray", None) + return arrayClass("",elements,self.__use_aggretator) + elif issubclass(eltType,Element): + return ElementArray("",elements,self.__use_aggretator) + else: + raise PyAMLException(f"Unsupported sliced array for type {str(eltType)}") + def __getitem__(self,key): - if(isinstance(key,int)): + + if isinstance(key,int): + + # By index return super().__getitem__(key) - elif(isinstance(key,slice)): + + elif isinstance(key,slice): - # Check type + # Slicing eltType = None r = [] for i in range(*key.indices(len(self))): @@ -55,20 +83,18 @@ def __getitem__(self,key): elif not isinstance(self[i],eltType): eltType = Element # Fall back to element r.append(self[i]) - - if issubclass(eltType,Magnet): - m = importlib.import_module("pyaml.arrays.magnet_array") - arrayClass = getattr(m, "MagnetArray", None) - return arrayClass("",r,self.__use_aggretator) - elif issubclass(eltType,BPM): - m = importlib.import_module("pyaml.arrays.bpm_array") - arrayClass = getattr(m, "BPMArray", None) - return arrayClass("",r,self.__use_aggretator) - elif issubclass(eltType,CombinedFunctionMagnet): - m = importlib.import_module("pyaml.arrays.cfm_array") - arrayClass = getattr(m, "CombinedFunctionMagnetArray", None) - return arrayClass("",r,self.__use_aggretator) - elif issubclass(eltType,Element): - return ElementArray("",r,self.__use_aggretator) - else: - raise PyAMLException(f"Unsupported sliced array for type {str(eltType)}") + return self.__create_array("",eltType,r) + + elif isinstance(key,str): + + # Selection by name + eltType = None + r = [] + for e in self: + if fnmatch.fnmatch(e.get_name(), key): + if eltType is None: + eltType = type(e) + elif not isinstance(e,eltType): + eltType = Element # Fall back to element + r.append(e) + return self.__create_array("",eltType,r) From 2d5873c3ad57bc82f8cd1dff2eb0574c994b00b3 Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 6 Nov 2025 15:16:53 +0100 Subject: [PATCH 4/7] Implemented selection by field --- pyaml/arrays/element_array.py | 52 ++++++++++++++++++++++++----------- pyaml/magnet/cfm_magnet.py | 6 ++++ pyaml/magnet/magnet.py | 8 +++++- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index 45f37986..418a7706 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -65,14 +65,14 @@ def __create_array(self,arrName:str,eltType:type,elements:list): else: raise PyAMLException(f"Unsupported sliced array for type {str(eltType)}") - def __getitem__(self,key): + def __eval_field(self,attName:str,e:Element) -> str: + funcName = "get_" + attName + func = getattr(e,funcName, None) + return func() if func is not None else "" - if isinstance(key,int): + def __getitem__(self,key): - # By index - return super().__getitem__(key) - - elif isinstance(key,slice): + if isinstance(key,slice): # Slicing eltType = None @@ -87,14 +87,34 @@ def __getitem__(self,key): elif isinstance(key,str): - # Selection by name - eltType = None - r = [] - for e in self: - if fnmatch.fnmatch(e.get_name(), key): - if eltType is None: - eltType = type(e) - elif not isinstance(e,eltType): - eltType = Element # Fall back to element - r.append(e) + fields = key.split(':') + + if len(fields)<=1: + # Selection by name + eltType = None + r = [] + for e in self: + if fnmatch.fnmatch(e.get_name(), key): + if eltType is None: + eltType = type(e) + elif not isinstance(e,eltType): + eltType = Element # Fall back to element + r.append(e) + else: + # Selection by fields + eltType = None + r = [] + for e in self: + txt = self.__eval_field(fields[0],e) + if fnmatch.fnmatch(txt , fields[1]): + if eltType is None: + eltType = type(e) + elif not isinstance(e,eltType): + eltType = Element # Fall back to element + r.append(e) + return self.__create_array("",eltType,r) + + else: + # Default to super selection + return super().__getitem__(key) diff --git a/pyaml/magnet/cfm_magnet.py b/pyaml/magnet/cfm_magnet.py index 393cd617..362f776a 100644 --- a/pyaml/magnet/cfm_magnet.py +++ b/pyaml/magnet/cfm_magnet.py @@ -77,6 +77,12 @@ def __init__(self, cfg: ConfigModel, peer = None): # Attach self._peer = peer + def get_model_name(self) -> str: + """ + Returns the model name of this magnet + """ + return self._cfg.name + def __create_virutal_manget(self,name:str,idx:int) -> Magnet: args = {"name":name,"model":self.model} mVirtual:Magnet = _fmap[idx](MagnetConfigModel(**args)) diff --git a/pyaml/magnet/magnet.py b/pyaml/magnet/magnet.py index 2554dd81..63fef983 100644 --- a/pyaml/magnet/magnet.py +++ b/pyaml/magnet/magnet.py @@ -88,8 +88,14 @@ def set_model_name(self, name:str): """ self.__modelName = name + def get_model_name(self) -> str: + """ + Returns the model name of this magnet + """ + return self.__modelName + def __repr__(self): - return "%s(peer='%s', name='%s', model='%s', magnet_model=%s)" % ( + return "%s(peer='%s', name='%s', model_name='%s', magnet_model=%s)" % ( self.__class__.__name__, self.get_peer(), self.get_name(), From 4362af79f5faabf9bd85149d979f5558366776e9 Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 6 Nov 2025 15:39:30 +0100 Subject: [PATCH 5/7] Fix for CFM --- pyaml/arrays/element_array.py | 2 +- pyaml/common/element_holder.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index 418a7706..0f9d4187 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -57,7 +57,7 @@ def __create_array(self,arrName:str,eltType:type,elements:list): arrayClass = getattr(m, "BPMArray", None) return arrayClass("",elements,self.__use_aggretator) elif issubclass(eltType,CombinedFunctionMagnet): - m = importlib.import_module("pyaml.arrays.cfm_array") + m = importlib.import_module("pyaml.arrays.cfm_magnet_array") arrayClass = getattr(m, "CombinedFunctionMagnetArray", None) return arrayClass("",elements,self.__use_aggretator) elif issubclass(eltType,Element): diff --git a/pyaml/common/element_holder.py b/pyaml/common/element_holder.py index 81b4eaba..0287251e 100644 --- a/pyaml/common/element_holder.py +++ b/pyaml/common/element_holder.py @@ -107,7 +107,7 @@ def get_cfm_magnets(self,name:str) -> CombinedFunctionMagnetArray: return self.__get("CombinedFunctionMagnet array",name,self.__CFM_MAGNET_ARRAYS) def get_all_cfm_magnets(self) -> list[CombinedFunctionMagnet]: - return [value for key, value in self.__CFM_MAGNET_ARRAYS.items()] + return [value for key, value in self.__CFM_MAGNETS.items()] # BPMs From 66e63113c0551ca5c382446fc6db272bb6bd5db0 Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 6 Nov 2025 15:57:42 +0100 Subject: [PATCH 6/7] Added test --- tests/test_arrays.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/test_arrays.py b/tests/test_arrays.py index 58a860f5..59ff2f16 100644 --- a/tests/test_arrays.py +++ b/tests/test_arrays.py @@ -3,8 +3,8 @@ from pyaml.instrument import Instrument from pyaml.arrays.element_array import ElementArray from pyaml.arrays.magnet_array import MagnetArray +from pyaml.arrays.cfm_magnet_array import CombinedFunctionMagnetArray from pyaml.arrays.bpm_array import BPMArray -from pyaml.arrays.cfm_magnet_array import CombinedFunctionMagnet import importlib import numpy as np @@ -174,4 +174,24 @@ def test_arrays(install_test_package): Factory.clear() + # Test dynamic arrays + + ml:PyAML = pyaml("tests/config/EBSOrbit.yaml") + sr:Instrument = ml.get('sr') + ae = ElementArray("All",sr.design.get_all_elements()) + acfm = ElementArray("AllCFM",sr.design.get_all_cfm_magnets(),use_aggregator=False) + + bpmC5 = ae['BPM*'][10:20] # All BPM C5 + assert(isinstance(bpmC5,BPMArray) and len(bpmC5)==10) + bpmc10 = ae['BPM*C10*'] # All BPM C10 + assert(isinstance(bpmc10,BPMArray) and len(bpmc10)==10) + magSHV = ae['SH*-V'] # All SH vertical corrector + assert(isinstance(magSHV,MagnetArray) and len(magSHV)==96) + magSH1A = ae['model_name:SH1A-*'] # All SH1A magnets (including the CFMs) + assert(isinstance(magSH1A,ElementArray) and len(magSH1A)==129) + magSH1AC = acfm['model_name:SH1A-*'] # All SH1A magnets (CFMs only) + assert(isinstance(magSH1AC,ElementArray) and len(magSH1AC)==32) + + Factory.clear() + From 53d006a1a3690a79eb760d90e228d4e1271ced91 Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 6 Nov 2025 16:30:09 +0100 Subject: [PATCH 7/7] Typo + doc --- pyaml/arrays/element_array.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index 0f9d4187..d05cf5be 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -9,7 +9,7 @@ class ElementArray(list[Element]): """ - Class that implements access to a magnet array + Class that implements access to a element array """ def __init__(self,arrayName:str,elements:list[Element],use_aggregator = True): @@ -23,6 +23,8 @@ def __init__(self,arrayName:str,elements:list[Element],use_aggregator = True): elements: list[Element] Element list, all elements must be attached to the same instance of either a Simulator or a ControlSystem. + use_aggregator : bool + Use aggregator to increase performance by using paralell access to underlying devices. """ super().__init__(i for i in elements) self.__name = arrayName