class Property: """Internal class for cached properties. Should be simplified and rewritten. Currently only used as base class for ``SingleWindow``. Do not use for new code.""" def __init__(self, construct): self.construct = construct self.holds_value = Bool(False) # porcelain @classmethod def factory(cls, func): return cachedproperty( functools.wraps(func)(lambda self: cls(func.__get__(self)))) def create(self): if self._has: self._update() else: self._new() return self.val def destroy(self): if self._has: self._del() def toggle(self): if self._has: self._del() else: self._new() def _new(self): val = self.construct() self._set(val) return val def _update(self): pass @property def _has(self): return hasattr(self, '_val') def _get(self): return self._val def _set(self, val): self._val = val self.holds_value.set(True) def _del(self): del self._val self.holds_value.set(False) # use lambdas to enable overriding the _get/_set/_del methods # without having to redefine the 'val' property val = property(lambda self: self._get(), lambda self, val: self._set(val), lambda self: self._del())
def __init__(self, session): self.session = session self.backend = None self.model = session.model self.sampler = BeamSampler(self) # menu conditions self.is_connected = Bool(False) self.has_backend = Bool(False) self.can_connect = ~self.is_connected & self.has_backend self.has_sequence = self.is_connected & self.model self._config = config = session.config.online_control self._settings = config['settings'] self._on_model_changed() self.set_backend(config.backend)
def __init__(self, lib, params, model=None, offsets=None, settings=None, control=None): self._lib = lib self._params = dicti({ 'beam_energy': dict(name='beam_energy', ui_name='beam_energy', ui_hint='', ui_prec=3, unit='MeV/u', ui_unit='MeV/u', ui_conv=1), 'beam_focus': dict(name='beam_focus', ui_name='beam_focus', ui_hint='', ui_prec=3, unit='m', ui_unit='mm', ui_conv=1000), 'beam_intensity': dict(name='beam_intensity', ui_name='beam_intensity', ui_hint='', ui_prec=3, unit='', ui_unit='', ui_conv=1), 'gantry_angle': dict(name='gantry_angle', ui_name='gantry_angle', ui_hint='', ui_prec=3, unit='°', ui_unit='°', ui_conv=1), }) self._params.update(params) self._model = model self._offsets = {} if offsets is None else offsets self.connected = Bool(False) self.settings = settings self.control = control self.vAcc = -1
def set_window(self, window): self.window = window self.menu = window and window.acs_settings_menu if window is None: return from madgui.util.menu import extend, Item, Separator self.jitter = Bool(self._lib.jitter) self.auto_sd = Bool(self._lib.auto_sd) self.menu.clear() extend(window, self.menu, [ Item('&Vary readouts', None, 'Emulate continuous readouts using gaussian jitter', self._toggle_jitter, checked=self.jitter), Item('Add &magnet aberrations', None, 'Add small deltas to all magnet strengths', self._lib._aberrate_strengths), Separator, Item('Autoset readouts from model', None, 'Autoset monitor readout values from model twiss table', self._toggle_auto_sd, checked=self.auto_sd), Separator, Item('Load readouts from file', None, 'Load monitor readout values from monitor export', self._open_sd_values), Item('Load strengths from file', None, 'Load magnet strengths from strength export', self._open_float_values), Separator, Item( 'Edit backend simulation model', None, 'Edit initial conditions for the model used to simulate ' 'the backend behaviour', self._edit_model_initial_conditions.create), ])
def __init__(self, construct): self.construct = construct self.holds_value = Bool(False)
class Property: """Internal class for cached properties. Should be simplified and rewritten. Currently only used as base class for ``SingleWindow``. Do not use for new code.""" def __init__(self, construct): self.construct = construct self.holds_value = Bool(False) # porcelain @classmethod def factory(cls, func): return cachedproperty(functools.wraps(func)( lambda self: cls(func.__get__(self)))) def create(self): if self._has: self._update() else: self._new() return self.val def destroy(self): if self._has: self._del() def toggle(self): if self._has: self._del() else: self._new() def _new(self): val = self.construct() self._set(val) return val def _update(self): pass @property def _has(self): return hasattr(self, '_val') def _get(self): return self._val def _set(self, val): self._val = val self.holds_value.set(True) def _del(self): del self._val self.holds_value.set(False) # use lambdas to enable overriding the _get/_set/_del methods # without having to redefine the 'val' property val = property(lambda self: self._get(), lambda self, val: self._set(val), lambda self: self._del())
class Control: """ Plugin class for MadGUI. When connected, the plugin can be used to access parameters in the online database. This works only if the corresponding parameters were named exactly as in the database and are assigned with the ":=" operator. """ def __init__(self, session): self.session = session self.backend = None self.model = session.model self.sampler = BeamSampler(self) # menu conditions self.is_connected = Bool(False) self.has_backend = Bool(False) self.can_connect = ~self.is_connected & self.has_backend self.has_sequence = self.is_connected & self.model self._config = config = session.config.online_control self._settings = config['settings'] self._on_model_changed() self.set_backend(config.backend) def set_backend(self, qualname): self.backend_spec = qualname self.has_backend.set(bool(qualname)) # menu handlers def connect(self): qualname = self.backend_spec logging.info('Connecting online control: {}'.format(qualname)) modname, clsname = qualname.split(':') mod = import_module(modname) cls = getattr(mod, clsname) self.backend = cls(self.session, self._settings) try: self.backend.connect() self.is_connected.set(True) self.session.user_ns.acs = self.backend self.model.changed.connect(self._on_model_changed) self._on_model_changed() except RuntimeError: logging.error('No connection to backend was possible') logging.error('Try to connect again') # Implemented since there has been reports of madgui # crashing after connecting and disconnecting from # beamOptikDLL. Should we try here to disconnect again # and implement a loop to try multiple times to connect? self.disconnect() def disconnect(self): self._settings = self.export_settings() self.session.user_ns.acs = None self.backend.disconnect() self.backend = None self.is_connected.set(False) self.model.changed.disconnect(self._on_model_changed) self._on_model_changed() def _on_model_changed(self, model=None): model = model or self.model() elems = self.is_connected() and model and model.elements or () self.sampler.monitors = [ elem.name for elem in elems if elem.base_name.lower().endswith('monitor') or elem.base_name.lower() == 'instrument' ] def export_settings(self): if hasattr(self.backend, 'export_settings'): return self.backend.export_settings() return self._settings def get_knobs(self): """Get dict of lowercase name → :class:`ParamInfo`.""" if not self.model(): return {} return { knob: info for knob in self.model().export_globals() for info in [self.backend.param_info(knob)] if info } # TODO: unify export/import dialog -> "show knobs" # TODO: can we drop the read-all button in favor of automatic reads? # (SetNewValueCallback?) def on_read_all(self): """Read all parameters from the online database.""" from madgui.online.dialogs import ImportParamWidget self._show_sync_dialog(ImportParamWidget(), self.read_all) def on_write_all(self): """Write all parameters to the online database.""" from madgui.online.dialogs import ExportParamWidget self._show_sync_dialog(ExportParamWidget(), self.write_all) def _show_sync_dialog(self, widget, apply): from madgui.online.dialogs import SyncParamItem from madgui.widget.dialog import Dialog model, live = self.model(), self.backend widget.data = [ SyncParamItem(info, live.read_param(name), model.read_param(name)) for name, info in self.get_knobs().items() ] widget.data_key = 'acs_parameters' dialog = Dialog(self.session.window()) dialog.setExportWidget(widget, self.session.folder) dialog.serious.addCustomButton('Sync Model', self.on_sync_model(self, dialog)) dialog.serious.updateButtons() dialog.accepted.connect(apply) dialog.show() return dialog class on_sync_model: def __init__(self, parent, widget): self.parent = parent self.widget = widget def __call__(self): self.onSyncModel() def onSyncModel(self): model = self.parent.backend.vAcc_to_model() self.parent.session.load_model(model) self.parent._on_model_changed() self.parent.on_read_all() self.widget.close() def read_all(self, knobs=None): live = self.backend self.model().write_params([(knob, live.read_param(knob)) for knob in knobs or self.get_knobs()], "Read params from online control") def write_all(self, knobs=None): model = self.model() self.write_params([(knob, model.read_param(knob)) for knob in knobs or self.get_knobs()]) def on_read_beam(self): # TODO: add confirmation dialog self.read_beam() def read_beam(self): self.model().update_beam(self.backend.get_beam()) def read_monitor(self, name): return self.backend.read_monitor(name) @SingleWindow.factory def monitor_widget(self): """Read out SD values (beam position/envelope).""" from madgui.online.diagnostic import MonitorWidget return MonitorWidget(self.session) @SingleWindow.factory def orm_measure_widget(self): """Measure ORM for later analysis.""" from madgui.widget.dialog import Dialog from madgui.online.orm_measure import MeasureWidget widget = MeasureWidget(self.session) return Dialog(self.session.window(), widget) def on_correct_multi_grid_method(self): from madgui.widget.correct.multi_grid import CorrectorWidget from madgui.widget.dialog import Dialog self.read_all() widget = CorrectorWidget(self.session) return Dialog(self.session.window(), widget) def on_correct_optic_variation_method(self): from madgui.widget.correct.optic_variation import CorrectorWidget from madgui.widget.dialog import Dialog self.read_all() widget = CorrectorWidget(self.session) return Dialog(self.session.window(), widget) def on_correct_measured_response_method(self): from madgui.widget.correct.mor_dialog import CorrectorWidget from madgui.widget.dialog import Dialog self.read_all() widget = CorrectorWidget(self.session) return Dialog(self.session.window(), widget) # helper functions def write_params(self, params): write = self.backend.write_param for param, value in params: write(param, value) self.backend.execute() def read_param(self, name): return self.backend.read_param(name)
class Control: """ Plugin class for MadGUI. When connected, the plugin can be used to access parameters in the online database. This works only if the corresponding parameters were named exactly as in the database and are assigned with the ":=" operator. """ def __init__(self, session): self.session = session self.backend = None self.model = session.model self.sampler = BeamSampler(self) # menu conditions self.is_connected = Bool(False) self.has_backend = Bool(False) self.can_connect = ~self.is_connected & self.has_backend self.has_sequence = self.is_connected & self.model self._config = config = session.config.online_control self._settings = config['settings'] self._on_model_changed() self.set_backend(config.backend) def set_backend(self, qualname): self.backend_spec = qualname self.has_backend.set(bool(qualname)) # menu handlers def connect(self): qualname = self.backend_spec logging.info('Connecting online control: {}'.format(qualname)) modname, clsname = qualname.split(':') mod = import_module(modname) cls = getattr(mod, clsname) self.backend = cls(self.session, self._settings) self.backend.connect() self.session.user_ns.acs = self.backend self.is_connected.set(True) self.model.changed.connect(self._on_model_changed) self._on_model_changed() def disconnect(self): self._settings = self.export_settings() self.session.user_ns.acs = None self.backend.disconnect() self.backend = None self.is_connected.set(False) self.model.changed.disconnect(self._on_model_changed) self._on_model_changed() def _on_model_changed(self, model=None): model = model or self.model() elems = self.is_connected() and model and model.elements or () self.sampler.monitors = [ elem.name for elem in elems if elem.base_name.lower().endswith('monitor') or elem.base_name.lower() == 'instrument' ] def export_settings(self): if hasattr(self.backend, 'export_settings'): return self.backend.export_settings() return self._settings def get_knobs(self): """Get dict of lowercase name → :class:`ParamInfo`.""" if not self.model(): return {} return { knob: info for knob in self.model().export_globals() for info in [self.backend.param_info(knob)] if info } # TODO: unify export/import dialog -> "show knobs" # TODO: can we drop the read-all button in favor of automatic reads? # (SetNewValueCallback?) def on_read_all(self): """Read all parameters from the online database.""" from madgui.online.dialogs import ImportParamWidget self._show_sync_dialog(ImportParamWidget(), self.read_all) def on_write_all(self): """Write all parameters to the online database.""" from madgui.online.dialogs import ExportParamWidget self._show_sync_dialog(ExportParamWidget(), self.write_all) def _show_sync_dialog(self, widget, apply): from madgui.online.dialogs import SyncParamItem from madgui.widget.dialog import Dialog model, live = self.model(), self.backend widget.data = [ SyncParamItem(info, live.read_param(name), model.read_param(name)) for name, info in self.get_knobs().items() ] widget.data_key = 'acs_parameters' dialog = Dialog(self.session.window()) dialog.setExportWidget(widget, self.session.folder) dialog.serious.updateButtons() dialog.accepted.connect(apply) dialog.show() return dialog def read_all(self, knobs=None): live = self.backend self.model().write_params([ (knob, live.read_param(knob)) for knob in knobs or self.get_knobs() ], "Read params from online control") def write_all(self, knobs=None): model = self.model() self.write_params([ (knob, model.read_param(knob)) for knob in knobs or self.get_knobs() ]) def on_read_beam(self): # TODO: add confirmation dialog self.read_beam() def read_beam(self): self.model().update_beam(self.backend.get_beam()) def read_monitor(self, name): return self.backend.read_monitor(name) @SingleWindow.factory def monitor_widget(self): """Read out SD values (beam position/envelope).""" from madgui.online.diagnostic import MonitorWidget return MonitorWidget(self.session) @SingleWindow.factory def orm_measure_widget(self): """Measure ORM for later analysis.""" from madgui.widget.dialog import Dialog from madgui.online.orm_measure import MeasureWidget widget = MeasureWidget(self.session) return Dialog(self.session.window(), widget) def on_correct_multi_grid_method(self): from madgui.widget.correct.multi_grid import CorrectorWidget from madgui.widget.dialog import Dialog self.read_all() widget = CorrectorWidget(self.session) return Dialog(self.session.window(), widget) def on_correct_optic_variation_method(self): from madgui.widget.correct.optic_variation import CorrectorWidget from madgui.widget.dialog import Dialog self.read_all() widget = CorrectorWidget(self.session) return Dialog(self.session.window(), widget) def on_correct_measured_response_method(self): from madgui.widget.correct.mor_dialog import CorrectorWidget from madgui.widget.dialog import Dialog self.read_all() widget = CorrectorWidget(self.session) return Dialog(self.session.window(), widget) # helper functions def write_params(self, params): write = self.backend.write_param for param, value in params: write(param, value) self.backend.execute() def read_param(self, name): return self.backend.read_param(name)
class _HitACS(api.Backend): def __init__(self, lib, params, model=None, offsets=None, settings=None, control=None): self._lib = lib self._params = dicti({ 'beam_energy': dict(name='beam_energy', ui_name='beam_energy', ui_hint='', ui_prec=3, unit='MeV/u', ui_unit='MeV/u', ui_conv=1), 'beam_focus': dict(name='beam_focus', ui_name='beam_focus', ui_hint='', ui_prec=3, unit='m', ui_unit='mm', ui_conv=1000), 'beam_intensity': dict(name='beam_intensity', ui_name='beam_intensity', ui_hint='', ui_prec=3, unit='', ui_unit='', ui_conv=1), 'gantry_angle': dict(name='gantry_angle', ui_name='gantry_angle', ui_hint='', ui_prec=3, unit='°', ui_unit='°', ui_conv=1), }) self._params.update(params) self._model = model self._offsets = {} if offsets is None else offsets self.connected = Bool(False) self.settings = settings self.control = control self.vAcc = -1 @property def beamoptikdll(self): """Python wrapper for the BeamOptikDLL exposed methods.""" return self._lib # Backend API def connect(self): """Connect to online database (must be loaded).""" status = self._lib.GetInterfaceInstance() logging.debug('Conection status: {}'.format(status)) self.connected.set(True) def disconnect(self): """Disconnect from online database.""" (self.settings or {}).update(self.export_settings()) self._lib.FreeInterfaceInstance() self.connected.set(False) def export_settings(self): """Updates the settings yaml file for future loggins""" mefi = self._lib.GetMEFIValue()[1] settings = { 'variant': self._lib._variant, 'vacc': self._lib.GetSelectedVAcc(), 'mefi': mefi and tuple(mefi), } if hasattr(self._lib, 'export_settings'): settings.update(self._lib.export_settings()) return settings def execute(self, options=ExecOptions.CalcDif): """Execute changes (commits prior set_value operations).""" self._lib.ExecuteChanges(options) def param_info(self, knob): """Get parameter info for backend key.""" data = self._params.get(knob) return data and api.ParamInfo(**data) def read_monitor(self, name): """ Read out one monitor, return values as dict with keys posx/posy/envx/envy. """ # TODO: Handle usability of parameters individually try: GetFloatValueSD = self._lib.GetFloatValueSD posx = GetFloatValueSD('posx_' + name) posy = GetFloatValueSD('posy_' + name) envx = GetFloatValueSD('widthx_' + name) envy = GetFloatValueSD('widthy_' + name) except RuntimeError: return {} # TODO: move sanity check to later, so values will simply be # unchecked/grayed out, instead of removed completely # The magic number -9999.0 signals corrupt values. # FIXME: Sometimes width=0 is returned. ~ Meaning? if posx == -9999 or posy == -9999 or envx <= 0 or envy <= 0: return {} xoffs, yoffs = self._offsets.get(name, (0, 0)) return { 'posx': -(posx / 1000 + xoffs), 'posy': +(posy / 1000 + yoffs), 'envx': envx / 1000, 'envy': envy / 1000, } def read_params(self, param_names=None, warn=True): """Read all specified params (by default all). Return dict.""" if param_names is None: param_names = self._params warn = False return { param: value for param in param_names for value in [self.read_param(param, warn=warn)] if value is not None } def read_param(self, param, warn=True): """Read parameter. Return numeric value.""" param = param.lower() if param in MEFI_PARAMS: return self._lib.GetMEFIValue()[0][MEFI_PARAMS.index(param)] try: return self._lib.GetFloatValue(param) except RuntimeError as e: if warn: logging.warning("{} for {!r}".format(e, param)) def write_param(self, param, value): """Update parameter into control system.""" param = param.lower() if param in MEFI_PARAMS: cur_value = self.read_param(param) if value != cur_value: logging.warning( "Unable to set {}={} (is {}). This parameter " "can only be changed by selecting the MEFI combination!". format(param, value, cur_value)) return try: self._lib.SetFloatValue(param, value) except RuntimeError as e: logging.error("{} for {!r} = {}".format(e, param, value)) def get_beam(self): units = unit.units e_para = ENERGY_PARAM.get(self._model().seq_name, 'E_HEBT') z_num = self._lib.GetFloatValue('Z_POSTSTRIP') mass = self._lib.GetFloatValue('A_POSTSTRIP') * units.u charge = self._lib.GetFloatValue('Q_POSTSTRIP') * units.e e_kin = (self._lib.GetFloatValue(e_para) or 1) * units.MeV / units.u return { 'particle': PERIODIC_TABLE[round(z_num)], 'charge': unit.from_ui('charge', charge), 'mass': unit.from_ui('mass', mass), 'energy': unit.from_ui('energy', mass * (e_kin + 1 * units.c**2)), } def get_MEFI(self): mefi = self._lib.GetMEFIValue()[1] return mefi and tuple(mefi) def vAcc_to_model(self): """User defined vAcc to model""" vAcc = self.vAcc = self._lib.GetSelectedVAcc() _isStdVacc = False if vAcc in np.arange(16): _isStdVacc = True logging.info('Loading model with vAcc {}'.format(vAcc)) for bL in VACC_TABLE: beamLine = VACC_TABLE[bL] if self.vAcc in beamLine[0]: return beamLine[1], _isStdVacc if vAcc == -1.: logging.warning('Please select a vAcc.') return self.model().model_data()['sequence'], _isStdVacc logging.warning('vAcc is not standard. Load model manually.') return self.model().model_data()['sequence'], _isStdVacc
class TestACS(_HitACS): def __init__(self, session, settings): params = load_dvm_parameters() offsets = find_offsets(settings.get('runtime_path', '.')) # Don't pass `session.model()` to the stub. It should use an # independent simulation, which is cloned upon connection in # `on_model_changed`: lib = session.user_ns.beamoptikdll = BeamOptikStub( None, offsets, settings) super().__init__(lib, params, session.model, offsets, control=session.control) self.menu = None self.window = None self.set_window(session.window()) self.connected.changed.connect(self.on_connected_changed) self.str_file = settings.get('str_file') self.sd_file = settings.get('sd_file') def load_float_values(self, filename): from madgui.util.export import read_str_file self.str_file = filename = os.path.abspath(filename) self._lib.set_float_values(read_str_file(filename)) def load_sd_values(self, filename): from madgui.util.yaml import load_file self.sd_file = filename = os.path.abspath(filename) data = load_file(filename) cols = { 'envx': 'widthx', 'envy': 'widthy', 'x': 'posx', 'y': 'posy', } self.auto_sd.set(False) self._lib.set_sd_values({ cols[param] + '_' + elem: value for elem, values in data['monitor'].items() for param, value in values.items() }) def set_window(self, window): self.window = window self.menu = window and window.acs_settings_menu if window is None: return from madgui.util.menu import extend, Item, Separator self.jitter = Bool(self._lib.jitter) self.auto_sd = Bool(self._lib.auto_sd) self.menu.clear() extend(window, self.menu, [ Item('&Vary readouts', None, 'Emulate continuous readouts using gaussian jitter', self._toggle_jitter, checked=self.jitter), Item('Add &magnet aberrations', None, 'Add small deltas to all magnet strengths', self._lib._aberrate_strengths), Separator, Item('Autoset readouts from model', None, 'Autoset monitor readout values from model twiss table', self._toggle_auto_sd, checked=self.auto_sd), Separator, Item('Load readouts from file', None, 'Load monitor readout values from monitor export', self._open_sd_values), Item('Load strengths from file', None, 'Load magnet strengths from strength export', self._open_float_values), Separator, Item( 'Edit backend simulation model', None, 'Edit initial conditions for the model used to simulate ' 'the backend behaviour', self._edit_model_initial_conditions.create), ]) def export_settings(self): return { 'jitter': self.jitter(), 'shot_interval': self._lib.sd_cache.timeout, 'auto_sd': self.auto_sd(), 'str_file': self.str_file and safe_relpath(self.str_file, None), 'sd_file': self.str_file and safe_relpath(self.sd_file, None), } def _toggle_jitter(self): self.jitter.set(not self.jitter()) self._lib.jitter = self.jitter() def _toggle_auto_sd(self): self.auto_sd.set(not self.auto_sd()) self._lib.auto_sd = self.auto_sd() self._lib.update_sd_values() def _open_sd_values(self): from madgui.widget.filedialog import getOpenFileName filters = [ ("YAML files", "*.yml", "*.yaml"), ("All files", "*"), ] folder = self.window.str_folder or self.window.folder if self.sd_file: folder = os.path.dirname(self.sd_file) filename = getOpenFileName(self.window, 'Open monitor export', folder, filters) if filename: self.load_sd_values(filename) def _open_float_values(self): from madgui.widget.filedialog import getOpenFileName filters = [ ("STR files", "*.str"), ("All files", "*"), ] folder = self.window.str_folder or self.window.folder if self.str_file: folder = os.path.dirname(self.str_file) filename = getOpenFileName(self.window, 'Open strength export', folder, filters) if filename: self.load_float_values(filename) def on_connected_changed(self, connected): if connected: self.model.changed.connect(self.on_model_changed) self.on_model_changed(self.model()) else: self.model.changed.disconnect(self.on_model_changed) if self.menu: self.menu.setEnabled(connected) def on_model_changed(self, model): clone = model and model.load_file(model.filename, stdout=False) self._lib.set_model(clone) if clone: if self.str_file: self.load_float_values(self.str_file) if self.sd_file: self.load_sd_values(self.sd_file) @SingleWindow.factory def _edit_model_initial_conditions(self): from madgui.widget.params import model_params_dialog return model_params_dialog(self._lib.model, parent=self.window, folder=self.window.folder) @property def model(self): return self._model