def __init__(self, parent=None): QtGui.QWidget.__init__(self) self.ui = Ui_ELE() self.ui.setupUi(self) self.parent = parent self.fileExtension = '.ele' self._setup_widgets() self.update_widget_state() self.process = QtCore.QProcess(self) self.process.readyReadStandardOutput.connect(self._process_stdout) self.process.readyReadStandardError.connect(self._process_stderr) self.process.started.connect(self._process_started) self.process.finished.connect(self._process_finished) # states: 'summary' or 'full' self.status_mode = 'summary' self.summary_html = '' self.container = self
class RbEle(QtGui.QWidget): acceptsFileTypes = [] defaultTitle = 'Elegant' task = 'Run an Elegant simulation' category = 'simulations' ERROR_FILE_NAME = 'elegant_errors.txt' OUTPUT_FILE_NAME = 'elegant_output.txt' ELEGANT_BASE_NAME = 'elegantSimulation' ELEGANT_TEMPLATE = ''' &run_setup lattice = "{latticeFileName}", use_beamline = {beamlineName}, default_order = 2, p_central_mev = {momentum}, output = %s.out, centroid = %s.cen, sigma = %s.sig, parameters = %s.param, random_number_seed = 987654321, combine_bunch_statistics = 0, concat_order = 2, tracking_updates = 1, echo_lattice = 0, print_statistics = 1, &end &run_control n_steps = 1, reset_rf_for_each_step = 1, &end &twiss_output matched = 0, concat_order = 3, beta_x = 5, alpha_x = 0, beta_y = 5, alpha_y = 0, output_at_each_step = 1, statistics = 1, concat_order = 3, filename = %s.twi, &end &sdds_beam input = "{bunchFileName}", &end &track &end &stop &end ''' def __init__(self, parent=None): QtGui.QWidget.__init__(self) self.ui = Ui_ELE() self.ui.setupUi(self) self.parent = parent self.fileExtension = '.ele' self._setup_widgets() self.update_widget_state() self.process = QtCore.QProcess(self) self.process.readyReadStandardOutput.connect(self._process_stdout) self.process.readyReadStandardError.connect(self._process_stderr) self.process.started.connect(self._process_started) self.process.finished.connect(self._process_finished) # states: 'summary' or 'full' self.status_mode = 'summary' self.summary_html = '' self.progressIndex = 0 self.beamlineNames = [] self.container = self # This tab only pulls together data from other sources. # It has no information to save. exportToFile() creates # an empty file to signal to RadTrack to create a new # tab when a project is opened that had this tab. def exportToFile(self, fileName): with open(fileName, 'w'): pass def importFile(self, fileName): pass def append_status(self, line): """Formats and appends the line to the status field""" status = self.ui.simulationStatusTextEdit line += '\n' if self._is_error_text(line): html = '<strong>{}</strong>'.format(cgi.escape(line)) else: html = cgi.escape(line) html = re.sub(r'\n', '<br>', html) status.moveCursor(QtGui.QTextCursor.End) status.insertHtml(html) scroll_bar = status.verticalScrollBar() scroll_bar.setValue(scroll_bar.maximum()) self.summary_html += html def clear_beam_lines(self): self.ui.beamLineComboBox.clear() def get_tab_by_name(self, tab_name): """Returns an app tab widget by text value""" tab = self.parent.tabWidget for i in range(tab.count()): if tab.tabText(i) == tab_name: return getRealWidget(tab.widget(i)) return None def get_tab_names_for_type(self, tab_type): """Returns a list of app tab names with the specified type""" tab_names = [] for i in range(self.parent.tabWidget.count()): name = self.parent.tabWidget.tabText(i) if type(self.get_tab_by_name(name)) == tab_type: tab_names.append(name) return tab_names def session_file(self, file_name=None, suffix=None): """Creates a file path in the sessionDirectory.""" if not file_name: file_name = '{}.{}'.format(self.ELEGANT_BASE_NAME, suffix) return os.path.join(self.parent.sessionDirectory, file_name) def set_beam_lines(self, items): """Load the Beam Line combo with items""" self.clear_beam_lines() self.ui.beamLineComboBox.addItems(items) self.ui.beamLineComboBox.setCurrentIndex( self.ui.beamLineComboBox.count() - 1) def show_warning_box(self, message): QtGui.QMessageBox.warning(self, 'RadTrack', message) def update_sources_from_tabs(self): """Called from RbGlobal when the tab state changes. Resync the bunch source and beam line source combo items.""" self.bunch_source_manager.update_sources_from_tabs() self.beam_line_source_manager.update_sources_from_tabs() self.beam_line_source_manager._beam_line_source_changed() def update_widget_state(self): """Set the enabled status on widgets based on current selections""" has_beam_line_source = self.beam_line_source_manager.has_selection() self.ui.beamLineLabel.setEnabled(has_beam_line_source) self.ui.beamLineComboBox.setEnabled(has_beam_line_source) show_momentum = self.bunch_source_manager.is_momentum_required() self.ui.momentumLabel.setVisible(show_momentum) self.ui.momentumLineEdit.setVisible(show_momentum) enable_button = self.bunch_source_manager.has_selection() \ and self.beam_line_source_manager.has_selection() if show_momentum and not self.ui.momentumLineEdit.text(): enable_button = False self.ui.simulateButton.setEnabled(enable_button) def validate_momentum(self): """Ensure the momentum value is valid""" if self.bunch_source_manager.is_momentum_required(): try: momentum = float(self.ui.momentumLineEdit.text()) except ValueError: try: momentum = convertUnitsStringToNumber( self.ui.momentumLineEdit.text(), 'MeV') except ValueError: self.show_warning_box('Unable to parse momentum') self.ui.momentumLineEdit.setFocus() return None else: bunchTab = self.bunch_source_manager.get_tab_widget() bunchTab.generateBunch() # make sure any new settings get read try: momentum = convertUnitsNumber( bunchTab.myBunch.getDesignMomentumEV(), 'eV', 'MeV') except ValueError: self.show_warning_box( 'Invalid momentum value on Bunch Tab') return None return momentum def _abort_simulation(self): self.process.kill() def _add_menu_actions(self, menu, file_name, tab_type, name): """Adds the context menu actions for the specified tab_type""" for tab_name in self.get_tab_names_for_type(tab_type): menu.addAction( 'Open in {} tab'.format(tab_name), lambda: self._load_tab(tab_name, file_name)) menu.addAction( 'Open in new {} tab'.format(name), lambda: self._new_tab(tab_type, file_name)) def _add_result_file(self, text, file_name): """Adds the file entry to the simulation results list""" if not os.path.isfile(file_name): cpr('missing result file: {}', file_name) return results = self.ui.simulationResultsListWidget icon = results.style().standardIcon(QtGui.QStyle.SP_FileIcon) item = QtGui.QListWidgetItem(icon, text) item.setData(QtCore.Qt.UserRole, file_name) results.addItem(item) def _add_result_files(self): """Adds output files from lattice elements and elegant template""" loader = self.beam_line_source_manager.get_lattice_element_loader() for element in loader.elementDictionary.values(): if element.isBeamline(): continue for output_parameter in element.outputFileParameters: i = element.parameterNames.index(output_parameter) if element.data[i]: self._add_result_file( '{}: {}'.format(element.name, type(element).__name__), self._autocomplete_file_name(element.data[i])) matches = re.findall(r'\%s\.\w+', self.ELEGANT_TEMPLATE) for match in matches: file_name = self._autocomplete_file_name(match) if os.path.isfile(file_name): text = self._sdds_description(file_name) if not text: text = os.path.basename(file_name) self._add_result_file(text, file_name) def _autocomplete_file_name(self, file_name): """Removes leading/trailing quotes and autocompletes %s.xxx files""" file_name = re.sub(r'\'|"', '', file_name) file_name = re.sub(r'\%s', self.ELEGANT_BASE_NAME, file_name) if not os.path.isfile(file_name): file_name = self.session_file(file_name=file_name) return file_name def _enable_parameters(self, is_enabled): """Enable or disable all the parameter fields""" for w in (self.ui.bunchSourceLabel, self.ui.bunchSourceComboBox, self.ui.beamLineSourceLabel, self.ui.beamLineSourceComboBox, self.ui.beamLineLabel, self.ui.beamLineComboBox, self.ui.momentumLabel, self.ui.momentumLineEdit, self.ui.simulateButton): w.setEnabled(is_enabled) def _enable_status_and_results(self): """Enable the simulation status and results""" self.ui.simulationStatusTextEdit.clear() self.status_mode = 'summary' self.summary_html = '' self.ui.simulationResultsListWidget.clear() for w in (self.ui.simulationStatusLabel, self.ui.simulationStatusTextEdit, self.ui.simulationResultsLabel, self.ui.simulationResultsListWidget): w.setEnabled(True) self._enable_parameters(False) def _is_bunch_file(self, file_name): if re.search('\.csv$', file_name, re.IGNORECASE): return True if not self._is_sdds_file(file_name): return False """Scans the SDDS header for the 6D field names""" search_columns = ['x', 'xp', 'y', 'yp', 't', 'p'] for line in self._read_sdds_header(file_name): match = re.search(r'^\&column\s.*?name\=(\w+)', line) if match: name = match.group(1).lower() if name in search_columns: search_columns.remove(name) if len(search_columns): return False return True def _is_error_text(self, text): return re.search(r'^warn|^error|wrong units', text, re.IGNORECASE) def _is_sdds_file(self, file_name): """Returns True if the file has the SDDS header""" with open(file_name, 'r') as input_file: if re.search(r'^SDDS', input_file.readline()): return True return False def _load_tab(self, tab_name, file_name): """Load a parent tab with data from the specified file""" target = self.get_tab_by_name(tab_name) target.importFile(file_name) self.parent.tabWidget.setCurrentWidget(target) def _new_tab(self, tab_type, file_name): """Create a new parent tab and load the data from the specified file""" self.parent.newTab(tab_type) getRealWidget(self.parent.tabWidget.currentWidget()).importFile(file_name) def _process_finished(self, code, status): """Callback when simulation process has finished""" self._enable_parameters(True) self.ui.abortButton.setEnabled(False) self.ui.progressBar.setValue(self.ui.progressBar.maximum()) self.output_file.close() self.error_file.close() self.append_status('') # always add the bunch source and beam line source files # so they can be opened in the editor tabs to verify them if not self.bunch_source_manager.is_tab_choice(): file_name = self.bunch_source_manager.get_file_name() self._add_result_file(os.path.basename(file_name), file_name) if not self.beam_line_source_manager.is_tab_choice(): file_name = self.beam_line_source_manager.get_file_name() self._add_result_file(os.path.basename(file_name), file_name) if status == 0: if code == 0: self.append_status('Simulation completed successfully') self._add_result_files() else: self.append_status('Simulation failed with errors') else: self.append_status('Simulation was terminated') def _process_started(self): """Callback when simulation process has started""" self.ui.abortButton.setEnabled(True) beamlineName=self.ui.beamLineComboBox.currentText() self.beamlineNames = self.beam_line_source_manager.get_lattice_element_loader().elementDictionary[beamlineName].fullElementNameList() self.ui.progressBar.setMaximum(len(self.beamlineNames)) self.ui.progressBar.reset() self.progressIndex = 0 def _process_stderr(self): """Callback with simulation stderr text""" out = str(self.process.readAllStandardError()) for line in out.split("\n"): self.append_status(line) self.error_file.write(out) def _process_stdout(self): """Callback with simulation stdout text""" out = str(self.process.readAllStandardOutput()) for line in out.split("\n"): if self._is_error_text(line): self.append_status(line) # print_statistics lists each element traversed by the beam. # Use this to track progress of simulation. if line.startswith('tracking through'): outputElementName = line.split()[-1] if re.match('M\d+', outputElementName): # skips generated names of combined magnets continue elementDictionary = self.beam_line_source_manager.get_lattice_element_loader().elementDictionary if outputElementName in elementDictionary: while outputElementName != self.beamlineNames[self.progressIndex]: self.progressIndex += 1 self.ui.progressBar.setValue(self.progressIndex + 1) self.progressIndex += 1 self.output_file.write(out) def _read_sdds_header(self, file_name): """Returns the header lines from a sdds file.""" # doesn't use sdds module, avoids reading in entire file lines = [] with open(file_name, 'r') as input_file: line = input_file.readline() while line != '': if re.search(r'^\&data ', line): break lines.append(line) if len(lines) > 300: break line = input_file.readline() return lines def _results_context_menu(self, position): """Show the context menu for a result item.""" results = self.ui.simulationResultsListWidget if results.currentItem(): file_name = results.currentItem().data( QtCore.Qt.UserRole).toString() menu = QtGui.QMenu() ext = os.path.splitext(file_name)[1].strip('.') for tabType in self.parent.availableTabTypes: if ext in tabType.acceptsFileTypes: self._add_menu_actions( menu, file_name, tabType, tabType.defaultTitle) menu.exec_(results.mapToGlobal(position)) def _run_simulation(self): """Generate the input files and start the Elegant process.""" momentum = self.validate_momentum() if not momentum: return if not os.getenv('RPN_DEFNS', None): os.environ['RPN_DEFNS'] = resource.filename('defns.rpn') self.error_file = open( self.session_file(file_name=self.ERROR_FILE_NAME), 'w') self.output_file = open( self.session_file(file_name=self.OUTPUT_FILE_NAME), 'w') self._enable_status_and_results() # The replace() command escapes backslashes # since Elegant interprets \x as a special character. elegant_input_file = self._write_simulation_input_files(momentum).replace('\\', '\\\\') self.append_status('Running simulation ...\n') self.process.start('elegant', [elegant_input_file]) def _sdds_description(self, file_name): """Return the sdds file description if present""" for line in self._read_sdds_header(file_name): match = re.search(r'^\&description .*?text="(.*?)"', line) if match: description = match.group(1) description = re.sub(r'\-\-input.*', '', description) description = re.sub(r'(\\|\/).*', '', description) return description return None def _setup_widgets(self): """Setup initial widget state""" self.ui.simulateButton.setIcon( self.ui.simulateButton.style().standardIcon( QtGui.QStyle.SP_MediaPlay)) self.ui.simulateButton.clicked.connect(self._run_simulation) self.ui.abortButton.setIcon( self.ui.abortButton.style().standardIcon( QtGui.QStyle.SP_MediaStop)) self.ui.abortButton.clicked.connect(self._abort_simulation) self.ui.momentumLineEdit.textChanged.connect(self.update_widget_state) self.ui.abortButton.setEnabled(False) self.ui.simulationStatusTextEdit.customContextMenuRequested.connect( self._status_context_menu) self.ui.simulationResultsListWidget.customContextMenuRequested.connect( self._results_context_menu) self.bunch_source_manager = BunchSourceManager( self, self.ui.bunchSourceComboBox) self.beam_line_source_manager = BeamLineSourceManager( self, self.ui.beamLineSourceComboBox) def _show_full_status(self): """Show the full process stdout and stderr text""" self.status_mode = 'full' status = self.ui.simulationStatusTextEdit status.clear() text = '' for name in (self.OUTPUT_FILE_NAME, self.ERROR_FILE_NAME): with open(self.session_file(file_name=name), 'r') as input_file: text += input_file.read() text += '\n\n' status.moveCursor(QtGui.QTextCursor.End) status.insertPlainText(text) scroll_bar = status.verticalScrollBar() scroll_bar.setValue(scroll_bar.maximum()) def _show_summary_status(self): """Show the summary status text""" self.status_mode = 'summary' self.ui.simulationStatusTextEdit.clear() self.ui.simulationStatusTextEdit.insertHtml(self.summary_html) def _status_context_menu(self, position): """Show the context menu for the status panel.""" status = self.ui.simulationStatusTextEdit if status.isEnabled() and self.ui.simulateButton.isEnabled(): menu = QtGui.QMenu() full = menu.addAction('Full Output', self._show_full_status) summary = menu.addAction( 'Summary Output', self._show_summary_status) selected = full if self.status_mode == 'full' else summary selected.setCheckable(True) selected.setChecked(True) menu.exec_(status.mapToGlobal(position)) def _write_simulation_input_files(self, momentum): """Generates and writes simulation input files""" self.append_status('Writing Elegant simulation file ...') elegant_file_name = self.session_file(suffix='ele') with open(elegant_file_name, 'w') as output_file: output_file.write(self.ELEGANT_TEMPLATE.format( latticeFileName=os.path.basename(self.beam_line_source_manager.get_lattice_file_name()), beamlineName=self.ui.beamLineComboBox.currentText(), momentum=str(momentum), bunchFileName=os.path.basename(self.bunch_source_manager.get_bunch_file_name()) )) for fileName in [self.beam_line_source_manager.get_lattice_file_name(), self.bunch_source_manager.get_bunch_file_name()]: if os.path.dirname(fileName) != self.parent.sessionDirectory: shutil.copy2(fileName, self.parent.sessionDirectory) return elegant_file_name