Пример #1
0
 def __init__(self, session):
     super().__init__()
     load_ui(self, __package__, self.ui_file)
     self.corrector = Corrector(session, direct=not self.multi_step)
     self.corrector.start()
     self.init_controls()
     self.set_initial_values()
     self.connect_signals()
Пример #2
0
    def __init__(self, session):
        super().__init__()
        load_ui(self, __package__, self.ui_file)
        self.control = session.control
        self.model = session.model()
        self.corrector = Corrector(session, False)
        self.corrector.setup({
            'monitors': [],
        })
        self.corrector.start()
        self.bot = ProcBot(self, self.corrector)

        self.init_controls()
        self.set_initial_values()
        self.connect_signals()
Пример #3
0
    def __init__(self, corrector):
        super().__init__()
        load_ui(self, __package__, self.ui_file)
        self.corrector = Corrector(corrector.session, False)
        self.corrector.setup({
            'monitors': corrector.monitors,
            'optics': corrector.variables,
        })
        self.corrector.start()
        self.bot = ProcBot(self, self.corrector)

        elem_by_knob = {}
        for elem in corrector.model.elements:
            for knob in corrector.model.get_elem_knobs(elem):
                elem_by_knob.setdefault(knob.lower(), elem)

        self.steerers = [
            elem_by_knob[v.lower()]
            for v in corrector.variables
        ]

        self.corrector.add_record = self.add_record
        self.raw_records = []

        self.init_controls()
        self.set_initial_values()
        self.connect_signals()
Пример #4
0
def test_simple_procedure(session):
    session.load_model('sample_model/sample')
    session.control.set_backend('hit_acs.plugin:TestACS')
    session.control.connect()

    corrector = Corrector(session)
    assert corrector is not None        # for lack of a better test;) for now
Пример #5
0
def corrector(session):
    session.load_model('sample_model/sample')
    session.control.set_backend('hit_acs.plugin:TestACS')
    session.control.connect()
    corrector = Corrector(session)
    corrector.setup({
        'monitors': [
            'monitor1',
            'monitor2',
        ],
        'optics':   [
            'kL_q31',
            'kL_q32',
            'ax_K1',
            'ay_K1',
        ],
    })
    return corrector
Пример #6
0
def corrector(session):
    session.load_model('hit_models/hht3')
    session.control.set_backend('hit_acs.plugin:TestACS')
    session.control.connect()
    corrector = Corrector(session)
    corrector.setup({
        'monitors': [
            't3dg2g',
            't3dg1g',
            't3df1',
        ],
        'optics':   [
            'ax_g3mw2',
            'ax_g3ms2',
            'ay_g3mw1',
            'ay_g3ms1',
        ],
    })
    return corrector
Пример #7
0
    def __init__(self, corrector):
        super().__init__()
        load_ui(self, __package__, self.ui_file)
        self.corrector = Corrector(corrector.session, False)
        self.corrector.setup({
            'monitors': corrector.monitors,
            'optics': corrector.variables,
        })
        self.corrector.start()
        self.bot = ProcBot(self, self.corrector)

        elem_by_knob = {}
        for elem in corrector.model.elements:
            for knob in corrector.model.get_elem_knobs(elem):
                elem_by_knob.setdefault(knob.lower(), elem)

        self.steerers = [elem_by_knob[v.lower()] for v in corrector.variables]

        self.corrector.add_record = self.add_record
        self.raw_records = []

        self.init_controls()
        self.set_initial_values()
        self.connect_signals()
Пример #8
0
    def __init__(self, session):
        super().__init__()
        load_ui(self, __package__, self.ui_file)
        self.control = session.control
        self.model = session.model()
        self.corrector = Corrector(session, False)
        self.corrector.setup({
            'monitors': [],
        })
        self.corrector.start()
        self.bot = ProcBot(self, self.corrector)

        self.init_controls()
        self.set_initial_values()
        self.connect_signals()
Пример #9
0
class MeasureWidget(QWidget):

    ui_file = 'orm_measure.ui'
    extension = '.orm_measurement.yml'

    def __init__(self, corrector):
        super().__init__()
        load_ui(self, __package__, self.ui_file)
        self.corrector = Corrector(corrector.session, False)
        self.corrector.setup({
            'monitors': corrector.monitors,
            'optics': corrector.variables,
        })
        self.corrector.start()
        self.bot = ProcBot(self, self.corrector)

        elem_by_knob = {}
        for elem in corrector.model.elements:
            for knob in corrector.model.get_elem_knobs(elem):
                elem_by_knob.setdefault(knob.lower(), elem)

        self.steerers = [
            elem_by_knob[v.lower()]
            for v in corrector.variables
        ]

        self.corrector.add_record = self.add_record
        self.raw_records = []

        self.init_controls()
        self.set_initial_values()
        self.connect_signals()

    def add_record(self, step, shot):
        if shot == 0:
            self.raw_records.append([])
        records = {r.name: r.data for r in self.corrector.readouts}
        self.raw_records[-1].append(records)
        self.corrector.write_shot(step, shot, {
            monitor: [data['posx'], data['posy'],
                      data['envx'], data['envy']]
            for monitor, data in records.items()
        })

    def sizeHint(self):
        return QSize(600, 400)

    def init_controls(self):
        self.opticsTable.set_viewmodel(
            self.get_corrector_row, unit=(None, 'kick'))

    def set_initial_values(self):
        self.d_phi = {}
        self.default_dphi = 2e-4
        self.opticsTable.rows[:] = self.steerers
        self.fileEdit.setText(
            "{date}_{time}_{sequence}_{monitor}"+self.extension)
        self.update_ui()

    def connect_signals(self):
        self.startButton.clicked.connect(self.start_bot)
        self.cancelButton.clicked.connect(self.cancel)

    def get_corrector_row(self, i, c) -> ("Kicker", "ΔΦ"):
        return [
            TableItem(c.name),
            TableItem(self.d_phi.get(c.name.lower(), self.default_dphi),
                      name='kick', set_value=self.set_kick,
                      delegate=delegates[float]),
        ]

    def set_kick(self, i, c, value):
        self.d_phi[c.name.lower()] = value

    @property
    def running(self):
        return bool(self.bot) and self.bot.running

    def closeEvent(self, event):
        self.bot.cancel()
        super().closeEvent(event)

    def update_ui(self):
        running = self.running
        valid = bool(self.corrector.optic_params)
        self.cancelButton.setEnabled(running)
        self.startButton.setEnabled(not running and valid)
        self.numIgnoredSpinBox.setEnabled(not running)
        self.numUsedSpinBox.setEnabled(not running)
        self.opticsTable.setEnabled(not running)
        self.progressBar.setEnabled(running)
        self.progressBar.setRange(0, self.bot.totalops)
        self.progressBar.setValue(self.bot.progress)

    def set_progress(self, progress):
        self.progressBar.setValue(progress)

    def update_fit(self):
        """Called when procedure finishes succesfully."""
        full_data = np.array([
            [
                np.mean([
                    [shot[monitor]['posx'], shot[monitor]['posy']]
                    for shot in series
                ], axis=0)
                for monitor in self.corrector.monitors
            ]
            for series in self.raw_records
        ])
        differences = full_data[1:] - full_data[[0]]
        deltas = [
            self.d_phi.get(v.lower(), self.default_dphi)
            for v in self.corrector.variables
        ]
        self.final_orm = [
            ORM_Entry(mon, var, *differences[i_var, i_mon] / deltas[i_var])
            for i_var, var in enumerate(self.corrector.variables)
            for i_mon, mon in enumerate(self.corrector.monitors)
        ]

        self.window().accept()

    def start_bot(self):
        self.corrector.set_optics_delta(self.d_phi, self.default_dphi)
        self.bot.start(
            self.numIgnoredSpinBox.value(),
            self.numUsedSpinBox.value())

        now = time.localtime(time.time())
        fname = os.path.join(
            '.',
            self.fileEdit.text().format(
                date=time.strftime("%Y-%m-%d", now),
                time=time.strftime("%H-%M-%S", now),
                sequence=self.corrector.model.seq_name,
                monitor=self.corrector.monitors[-1],
            ))

        self.corrector.open_export(fname)

    def cancel(self):
        self.bot.cancel()
        self.window().reject()

    def log(self, text, *args, **kwargs):
        formatted = text.format(*args, **kwargs)
        self.logEdit.appendPlainText(formatted)
Пример #10
0
class CorrectorWidget(QWidget):

    ui_file = 'multi_grid.ui'
    data_key = 'multi_grid'
    multi_step = False

    def __init__(self, session):
        super().__init__()
        load_ui(self, __package__, self.ui_file)
        self.corrector = Corrector(session, direct=not self.multi_step)
        self.corrector.start()
        self.init_controls()
        self.set_initial_values()
        self.connect_signals()

    def closeEvent(self, event):
        self.corrector.stop()
        self.view.hide_monitor_readouts()
        super().closeEvent(event)

    def on_execute_corrections(self):
        """Apply calculated corrections."""
        self.corrector.apply()
        self.update_status()

    def init_controls(self):
        self.configSelect.set_corrector(self.corrector, self.data_key)
        self.fitSettingsWidget.set_corrector(self.corrector)
        self.monitorTable.set_corrector(self.corrector)
        self.targetsTable.set_corrector(self.corrector)
        self.resultsTable.set_corrector(self.corrector)
        self.view = self.corrector.session.window().open_graph('orbit')

    def set_initial_values(self):
        self.fitButton.setFocus()
        self.update_status()

    def connect_signals(self):
        self.corrector.setup_changed.connect(self.update_status)
        self.corrector.saved_optics.changed.connect(self.update_ui)
        self.corrector.strategy.changed.connect(self.update_fit)
        self.corrector.use_backtracking.changed.connect(self.update_fit)
        self.fitButton.clicked.connect(self.update_fit)
        self.applyButton.clicked.connect(self.on_execute_corrections)
        self.prevButton.setDefaultAction(
            self.corrector.saved_optics.create_undo_action(self))
        self.nextButton.setDefaultAction(
            self.corrector.saved_optics.create_redo_action(self))

    def update_status(self):
        self.corrector.update_vars()
        self.corrector.update_records()
        self.update_ui()

    def update_fit(self, *_):
        """Calculate initial positions / corrections."""
        self.corrector.update_vars()
        self.corrector.update_records()
        self.corrector.update_fit()
        self.update_ui()

    @Queued.method
    def update_ui(self):
        saved_optics = self.corrector.saved_optics
        self.applyButton.setEnabled(
            self.corrector.online_optic != saved_optics())
        if saved_optics() is not None:
            self.corrector.variables.touch()
        self.view.show_monitor_readouts(self.corrector.monitors[:])
Пример #11
0
class MeasureWidget(QWidget):

    ui_file = 'orm_measure.ui'
    extension = '.orm_measurement.yml'

    def __init__(self, corrector):
        super().__init__()
        load_ui(self, __package__, self.ui_file)
        self.corrector = Corrector(corrector.session, False)
        self.corrector.setup({
            'monitors': corrector.monitors,
            'optics': corrector.variables,
        })
        self.corrector.start()
        self.bot = ProcBot(self, self.corrector)

        elem_by_knob = {}
        for elem in corrector.model.elements:
            for knob in corrector.model.get_elem_knobs(elem):
                elem_by_knob.setdefault(knob.lower(), elem)

        self.steerers = [elem_by_knob[v.lower()] for v in corrector.variables]

        self.corrector.add_record = self.add_record
        self.raw_records = []

        self.init_controls()
        self.set_initial_values()
        self.connect_signals()

    def add_record(self, step, shot):
        if shot == 0:
            self.raw_records.append([])
        records = {r.name: r.data for r in self.corrector.readouts}
        self.raw_records[-1].append(records)
        self.corrector.write_shot(
            step, shot, {
                monitor:
                [data['posx'], data['posy'], data['envx'], data['envy']]
                for monitor, data in records.items()
            })

    def sizeHint(self):
        return QSize(600, 400)

    def init_controls(self):
        self.opticsTable.set_viewmodel(self.get_corrector_row,
                                       unit=(None, 'kick'))

    def set_initial_values(self):
        self.d_phi = {}
        self.default_dphi = 2e-4
        self.opticsTable.rows[:] = self.steerers
        self.fileEdit.setText("{date}_{time}_{sequence}_{monitor}" +
                              self.extension)
        self.update_ui()

    def connect_signals(self):
        self.startButton.clicked.connect(self.start_bot)
        self.cancelButton.clicked.connect(self.cancel)

    def get_corrector_row(self, i, c) -> ("Kicker", "ΔΦ"):
        return [
            TableItem(c.name),
            TableItem(self.d_phi.get(c.name.lower(), self.default_dphi),
                      name='kick',
                      set_value=self.set_kick,
                      delegate=delegates[float]),
        ]

    def set_kick(self, i, c, value):
        self.d_phi[c.name.lower()] = value

    @property
    def running(self):
        return bool(self.bot) and self.bot.running

    def closeEvent(self, event):
        self.bot.cancel()
        super().closeEvent(event)

    def update_ui(self):
        running = self.running
        valid = bool(self.corrector.optic_params)
        self.cancelButton.setEnabled(running)
        self.startButton.setEnabled(not running and valid)
        self.numIgnoredSpinBox.setEnabled(not running)
        self.numUsedSpinBox.setEnabled(not running)
        self.opticsTable.setEnabled(not running)
        self.progressBar.setEnabled(running)
        self.progressBar.setRange(0, self.bot.totalops)
        self.progressBar.setValue(self.bot.progress)

    def set_progress(self, progress):
        self.progressBar.setValue(progress)

    def update_fit(self):
        """Called when procedure finishes succesfully."""
        full_data = np.array([[
            np.mean([[shot[monitor]['posx'], shot[monitor]['posy']]
                     for shot in series],
                    axis=0) for monitor in self.corrector.monitors
        ] for series in self.raw_records])
        differences = full_data[1:] - full_data[[0]]
        deltas = [
            self.d_phi.get(v.lower(), self.default_dphi)
            for v in self.corrector.variables
        ]
        self.final_orm = [
            ORM_Entry(mon, var, *differences[i_var, i_mon] / deltas[i_var])
            for i_var, var in enumerate(self.corrector.variables)
            for i_mon, mon in enumerate(self.corrector.monitors)
        ]

        self.window().accept()

    def start_bot(self):
        self.corrector.set_optics_delta(self.d_phi, self.default_dphi)
        self.bot.start(self.numIgnoredSpinBox.value(),
                       self.numUsedSpinBox.value())

        now = time.localtime(time.time())
        fname = os.path.join(
            '.',
            self.fileEdit.text().format(
                date=time.strftime("%Y-%m-%d", now),
                time=time.strftime("%H-%M-%S", now),
                sequence=self.corrector.model.seq_name,
                monitor=self.corrector.monitors[-1],
            ))

        self.corrector.open_export(fname)

    def cancel(self):
        self.bot.cancel()
        self.window().reject()

    def log(self, text, *args, **kwargs):
        formatted = text.format(*args, **kwargs)
        self.logEdit.appendPlainText(formatted)
Пример #12
0
class MeasureWidget(QWidget):

    ui_file = 'orm_measure.ui'
    extension = '.orm_measurement.yml'

    def __init__(self, session):
        super().__init__()
        load_ui(self, __package__, self.ui_file)
        self.control = session.control
        self.model = session.model()
        self.corrector = Corrector(session, False)
        self.corrector.setup({
            'monitors': [],
        })
        self.corrector.start()
        self.bot = ProcBot(self, self.corrector)

        self.init_controls()
        self.set_initial_values()
        self.connect_signals()

    def sizeHint(self):
        return QSize(600, 400)

    def init_controls(self):
        self.opticsTable.set_viewmodel(self.get_corrector_row)
        self.monitorTable.set_viewmodel(self.get_monitor_row)
        self.view = self.corrector.session.window().open_graph('orbit')

    def set_initial_values(self):
        self.set_folder('.')    # FIXME
        self.fileEdit.setText(
            "{date}_{time}_{sequence}_{monitor}"+self.extension)
        self.d_phi = {}
        self.opticsTable.rows[:] = []
        self.monitorTable.rows[:] = self.corrector.all_monitors
        self.update_ui()

    def connect_signals(self):
        self.folderButton.clicked.connect(self.change_output_file)
        self.startButton.clicked.connect(self.start_bot)
        self.cancelButton.clicked.connect(self.bot.cancel)
        self.monitorTable.selectionModel().selectionChanged.connect(
            self.monitor_selection_changed)
        self.filterEdit.textChanged.connect(lambda _: self._update_knobs())
        self.defaultSpinBox.valueChanged.connect(
            lambda _: self.opticsTable.rows.touch())

    def get_monitor_row(self, i, m) -> ("Monitor",):
        return [
            TableItem(m),
        ]

    def get_corrector_row(self, i, c) -> ("Param", "Δ"):
        default = self.defaultSpinBox.value() or None
        return [
            TableItem(c.name),
            TableItem(
                self.d_phi.get(c.name.lower(), default),
                set_value=self.set_delta,
                delegate=delegates[float]),
        ]

    def set_delta(self, i, c, value):
        self.d_phi[c.name.lower()] = value

    def monitor_selection_changed(self, selected, deselected):
        self.corrector.setup({
            'monitors': [
                self.corrector.all_monitors[idx.row()]
                for idx in self.monitorTable.selectedIndexes()
            ],
        })
        self._update_knobs()
        self.update_ui()

    def _update_knobs(self):
        match = self._get_filter()
        if not match:
            return
        elements = self.model.elements
        last_mon = elements.index(self.corrector.monitors[-1])
        self.opticsTable.rows = [
            self.corrector._knobs[knob.lower()]
            for elem in elements
            if elem.index < last_mon
            for knob in self.model.get_elem_knobs(elem)
            if knob.lower() in self.corrector._knobs
            and match.search(knob.lower())
        ]

    def _get_filter(self):
        text = self.filterEdit.text()
        text = text.replace(' ', '')
        try:
            return re.compile(text, re.ASCII | re.IGNORECASE)
        except re.error:
            return None

    def change_output_file(self):
        from madgui.widget.filedialog import getSaveFolderName
        folder = getSaveFolderName(
            self.window(), 'Output folder', self.folder)
        if folder:
            self.set_folder(folder)

    def set_folder(self, folder):
        self.folder = os.path.abspath(folder)
        self.folderEdit.setText(self.folder)

    @property
    def running(self):
        return bool(self.bot) and self.bot.running

    def closeEvent(self, event):
        self.bot.cancel()
        super().closeEvent(event)

    def update_ui(self):
        running = self.running
        valid = bool(self.opticsTable.rows and self._get_filter())
        self.cancelButton.setEnabled(running)
        self.startButton.setEnabled(not running and valid)
        self.folderButton.setEnabled(not running)
        self.numIgnoredSpinBox.setEnabled(not running)
        self.numUsedSpinBox.setEnabled(not running)
        self.monitorTable.setEnabled(not running)
        self.opticsTable.setEnabled(not running)
        self.progressBar.setEnabled(running)
        self.progressBar.setRange(0, self.bot.totalops)
        self.progressBar.setValue(self.bot.progress)

    def set_progress(self, progress):
        self.progressBar.setValue(progress)

    def update_fit(self):
        """Called when procedure finishes succesfully."""
        pass

    def start_bot(self):
        self.corrector.set_optics_delta(self.d_phi, self.defaultSpinBox.value())

        now = time.localtime(time.time())
        fname = os.path.join(
            self.folderEdit.text(),
            self.fileEdit.text().format(
                date=time.strftime("%Y-%m-%d", now),
                time=time.strftime("%H-%M-%S", now),
                sequence=self.model.seq_name,
                monitor=self.corrector.monitors[-1],
            ))

        self.corrector.open_export(fname)

        self.bot.start(
            self.numIgnoredSpinBox.value(),
            self.numUsedSpinBox.value())

    def log(self, text, *args, **kwargs):
        formatted = text.format(*args, **kwargs)
        logging.info(formatted)
        self.logEdit.appendPlainText(formatted)
Пример #13
0
def main(model_file, spec_file, record_file):
    """
    Usage:
        analysis MODEL PARAMS RECORDS

    MODEL must be the path of the model/sequence file to initialize MAD-X.

    PARAMS is a YAML file with arguments for the "measurement" procedure, it
        must contain at least a list of `monitors` and `optics` and should
        contain keys for errors to be inserted: `knobs`, `ealign`, `efcomp`

    RECORDS is the name of the YAML output file where
    """
    init_app([], gui=False)

    config = load_config(isolated=True)
    with ExitStack() as stack:
        setup_args = yaml.load_file(spec_file)['procedure']
        session = stack.enter_context(Session(config))
        session.control._settings.update({
            'shot_interval':
            0.001,
            'jitter':
            setup_args.get('jitter', True),
            'auto_params':
            False,
            'auto_sd':
            True,
        })
        session.load_model(model_file,
                           stdout=False,
                           command_log=lambda text: print("X:>", text))
        session.control.set_backend('hit_acs.plugin:TestACS')
        session.control.connect()
        session.control.write_all()
        corrector = Corrector(session)
        corrector.setup({
            'monitors': setup_args['monitors'],
            'optics': setup_args['optics'],
        })

        # FIXME: this is not yet compatible with general parameter errors. In
        # order to fix this, the hit_acs test backend will have to use an
        # independent model!
        model = session.model()
        import_errors(model, setup_args['errors'])
        model.twiss.invalidate()

        corrector.set_optics_delta(setup_args.get('optics_deltas', {}),
                                   setup_args.get('default_delta', 1e-4))
        corrector.open_export(record_file)

        widget = mock.Mock()
        procbot = ProcBot(widget, corrector)

        num_mons = len(setup_args['monitors'])
        num_optics = len(setup_args['optics']) + 1
        if setup_args.get('jitter', True):
            num_ignore = setup_args.get('num_ignore', 1)
            num_shots = setup_args.get('num_shots', 5)
        else:
            num_ignore = 0
            num_shots = 1

        procbot.start(num_ignore, num_shots, gui=False)

        total_steps = num_mons * (num_optics + 1) * (num_ignore + num_shots)

        i = 0
        while procbot.running and i < 2 * total_steps:
            procbot._feed(None, None)
            time.sleep(0.010)
            i += 1

        assert not procbot.running
Пример #14
0
class MeasureWidget(QWidget):

    ui_file = 'orm_measure.ui'
    extension = '.orm_measurement.yml'

    def __init__(self, session):
        super().__init__()
        load_ui(self, __package__, self.ui_file)
        self.control = session.control
        self.model = session.model()
        self.corrector = Corrector(session, False)
        self.corrector.setup({
            'monitors': [],
        })
        self.corrector.start()
        self.bot = ProcBot(self, self.corrector)

        self.init_controls()
        self.set_initial_values()
        self.connect_signals()

    def sizeHint(self):
        return QSize(600, 600)

    def init_controls(self):
        self.opticsTable.set_viewmodel(self.get_corrector_row)
        self.monitorTable.set_viewmodel(self.get_monitor_row)
        self.view = self.corrector.session.window().open_graph('orbit')

    def set_initial_values(self):
        self.set_folder('.')  # FIXME
        self.fileEdit.setText("{date}_{time}_{sequence}_{monitor}" +
                              self.extension)
        self.d_phi = {}
        self.opticsTable.rows[:] = []
        self.monitorTable.rows[:] = self.corrector.all_monitors
        self.update_ui()

    def connect_signals(self):
        self.folderButton.clicked.connect(self.change_output_file)
        self.startButton.clicked.connect(self.start_bot)
        self.cancelButton.clicked.connect(self.bot.cancel)
        self.monitorTable.selectionModel().selectionChanged.connect(
            self.monitor_selection_changed)
        self.filterEdit.textChanged.connect(lambda _: self._update_knobs())
        self.defaultSpinBox.valueChanged.connect(
            lambda _: self.opticsTable.rows.touch())

    def get_monitor_row(self, i, m) -> ("Monitor", ):
        return [
            TableItem(m),
        ]

    def get_corrector_row(self, i, c) -> ("Param", "Δ [rad]"):
        default = self.defaultSpinBox.value() or None
        return [
            TableItem(c.name),
            TableItem(self.d_phi.get(c.name.lower(), default),
                      set_value=self.set_delta,
                      delegate=delegates[float]),
        ]

    def set_delta(self, i, c, value):
        self.d_phi[c.name.lower()] = value

    def monitor_selection_changed(self, selected, deselected):
        self.corrector.setup({
            'monitors': [
                self.corrector.all_monitors[idx.row()]
                for idx in self.monitorTable.selectedIndexes()
            ],
        })
        self._update_knobs()
        self.update_ui()

    def _update_knobs(self):
        match = self._get_filter()
        if not match:
            return
        elements = self.model.elements
        last_mon = elements.index(self.corrector.monitors[-1])
        self.opticsTable.rows = [
            self.corrector._knobs[knob.lower()] for elem in elements
            if elem.index < last_mon
            for knob in self.model.get_elem_knobs(elem)
            if knob.lower() in self.corrector._knobs
            and match.search(knob.lower())
        ]

    def _get_filter(self):
        text = self.filterEdit.text()
        text = text.replace(' ', '')
        try:
            return re.compile(text, re.ASCII | re.IGNORECASE)
        except re.error:
            return None

    def change_output_file(self):
        from madgui.widget.filedialog import getSaveFolderName
        folder = getSaveFolderName(self.window(), 'Output folder', self.folder)
        if folder:
            self.set_folder(folder)

    def set_folder(self, folder):
        self.folder = os.path.abspath(folder)
        self.folderEdit.setText(self.folder)

    @property
    def running(self):
        return bool(self.bot) and self.bot.running

    def closeEvent(self, event):
        self.bot.cancel()
        super().closeEvent(event)

    def update_ui(self):
        running = self.running
        valid = bool(self.opticsTable.rows and self._get_filter())
        self.cancelButton.setEnabled(running)
        self.startButton.setEnabled(not running and valid)
        self.folderButton.setEnabled(not running)
        self.numIgnoredSpinBox.setEnabled(not running)
        self.numUsedSpinBox.setEnabled(not running)
        self.monitorTable.setEnabled(not running)
        self.opticsTable.setEnabled(not running)
        self.progressBar.setEnabled(running)
        self.progressBar.setRange(0, self.bot.totalops)
        self.progressBar.setValue(self.bot.progress)

    def set_progress(self, progress):
        self.progressBar.setValue(progress)

    def update_fit(self):
        """Called when procedure finishes succesfully."""
        pass

    def start_bot(self):
        self.corrector.set_optics_delta(self.d_phi,
                                        self.defaultSpinBox.value())

        now = time.localtime(time.time())
        fname = os.path.join(
            self.folderEdit.text(),
            self.fileEdit.text().format(
                date=time.strftime("%Y-%m-%d", now),
                time=time.strftime("%H-%M-%S", now),
                sequence=self.model.seq_name,
                monitor=self.corrector.monitors[-1],
            ))

        self.corrector.open_export(fname)

        self.bot.start(self.numIgnoredSpinBox.value(),
                       self.numUsedSpinBox.value())

    def log(self, text, *args, **kwargs):
        formatted = text.format(*args, **kwargs)
        logging.info(formatted)
        self.logEdit.appendPlainText(formatted)