gui
Loading...
Searching...
No Matches
physicsView.cc
Go to the documentation of this file.
1#include "physicsView.h"
2
3#include <gemc/gphysics/gphysicsRuntime.h>
4#include "G4SystemOfUnits.hh"
5
6#include <QComboBox>
7#include <QCompleter>
8#include <QDoubleSpinBox>
9#include <QFormLayout>
10#include <QGroupBox>
11#include <QHeaderView>
12#include <QLabel>
13#include <QLineEdit>
14#include <QPushButton>
15#include <QTabBar>
16#include <QTabWidget>
17#include <QTreeWidget>
18#include <QVBoxLayout>
19
20#include <algorithm>
21
22namespace {
23
24void fillCombo(QComboBox* combo, const std::vector<std::string>& names) {
25 for (const auto& name : names) { combo->addItem(QString::fromStdString(name)); }
26 combo->setEditable(true);
27 combo->setInsertPolicy(QComboBox::NoInsert);
28 combo->completer()->setFilterMode(Qt::MatchContains);
29 combo->completer()->setCompletionMode(QCompleter::PopupCompletion);
30 combo->setMinimumContentsLength(28);
31 combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
32}
33
34} // namespace
35
36PhysicsView::PhysicsView(std::shared_ptr<GOptions> input, QWidget* parent)
37 : QWidget(parent), options(std::move(input)) {
38 auto* layout = new QVBoxLayout(this);
39 layout->setContentsMargins(10, 10, 10, 10);
40 layout->setSpacing(10);
41 auto* title = new QLabel(tr("Physics"), this);
42 title->setFont(QFont("Avenir", 20, QFont::Bold));
43 layout->addWidget(title);
44
45 auto* configuration = new QGroupBox(tr("Physics list"), this);
46 auto* form = new QFormLayout(configuration);
47 reference = new QComboBox(configuration);
48 reference->setObjectName("gemcPhysicsReference");
50 const auto* physics = gphysics::RuntimePhysicsList::current();
51 const auto initial = QString::fromStdString(physics ? physics->selection() :
52 options->getRequiredScalarString("phys_list"));
53 const auto parts = initial.split('+', Qt::SkipEmptyParts);
54 if (!parts.empty() && reference->findText(parts.first().trimmed()) < 0) {
55 reference->addItem(parts.first().trimmed());
56 }
57 reference->setCurrentText(parts.empty() ? "FTFP_BERT" : parts.first().trimmed());
58 form->addRow(tr("Reference list"), reference);
59
60 cut = new QDoubleSpinBox(configuration);
61 cut->setObjectName("gemcProductionCut");
62 cut->setDecimals(6);
63 cut->setRange(0.000001, 1000000);
64 cut->setSuffix(" mm");
65 cut->setValue(physics ? physics->GetDefaultCutValue() / mm : 0.7);
66 cut->setToolTip(tr("Range cut for producing secondary gamma, electron, positron, and proton tracks. "
67 "This does not stop existing tracks below an energy threshold."));
68 form->addRow(tr("Production range cut"), cut);
69 layout->addWidget(configuration);
70
71 additions = new QTabWidget(this);
72 additions->setObjectName("gemcPhysicsConstructors");
73 additions->setTabsClosable(true);
74 additions->addTab(new QWidget(additions), "+");
75 additions->tabBar()->setTabButton(0, QTabBar::RightSide, nullptr);
76 additions->setToolTip(tr("Add a physics constructor to the reference list."));
77 for (int i = 1; i < parts.size(); ++i) { addConstructor(parts[i].trimmed()); }
78 layout->addWidget(additions);
79 connect(additions, &QTabWidget::tabBarClicked, this, [this](int index) {
80 if (index == additions->count() - 1) { addConstructor(); markPending(); }
81 });
82 connect(additions, &QTabWidget::tabCloseRequested, this, [this](int index) {
83 if (index == additions->count() - 1) { return; }
84 auto* page = additions->widget(index);
85 additions->removeTab(index);
86 page->deleteLater();
87 markPending();
88 });
89
90 auto* processes = new QGroupBox(tr("Processes"), this);
91 auto* processLayout = new QVBoxLayout(processes);
92 auto* description = new QLabel(
93 tr("Check processes to enable them. Category checkboxes control the whole group. "
94 "Combined processes are controlled as a single entry."), processes);
95 description->setWordWrap(true);
96 processLayout->addWidget(description);
97 auto* filter = new QLineEdit(processes);
98 filter->setPlaceholderText(tr("Filter processes"));
99 processLayout->addWidget(filter);
100 processTree = new QTreeWidget(processes);
101 processTree->setObjectName("gemcPhysicsProcesses");
102 processTree->setHeaderLabels({tr("Process"), tr("Applies to")});
103 processTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
104 processLayout->addWidget(processTree);
105 layout->addWidget(processes, 1);
106 connect(filter, &QLineEdit::textChanged, this, [this](const QString& text) {
107 for (int i = 0; i < processTree->topLevelItemCount(); ++i) {
108 auto* group = processTree->topLevelItem(i);
109 bool visible = false;
110 for (int j = 0; j < group->childCount(); ++j) {
111 auto* item = group->child(j);
112 const bool match = item->text(0).contains(text, Qt::CaseInsensitive) ||
113 group->text(0).contains(text, Qt::CaseInsensitive);
114 item->setHidden(!match);
115 visible |= match;
116 }
117 group->setHidden(!visible);
118 }
119 });
120 connect(processTree, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem* item, int column) {
121 if (refreshing || column != 0 || !item->parent() ||
122 !(item->flags() & Qt::ItemIsUserCheckable)) { return; }
123 pendingActivation[item->data(0, Qt::UserRole).toString().toStdString()] =
124 item->checkState(0) == Qt::Checked;
125 markPending();
126 });
127 status = new QLabel(this);
128 status->setObjectName("gemcPhysicsStatus");
129 status->setWordWrap(true);
130 layout->addWidget(status);
131 auto* apply = new QPushButton(tr("Apply physics"), this);
132 apply->setObjectName("gemcApplyPhysics");
133 connect(apply, &QPushButton::clicked, this, [this]() { applyPending(); });
134 layout->addWidget(apply, 0, Qt::AlignRight);
135 connect(reference, &QComboBox::currentTextChanged, this, &PhysicsView::markPending);
136 connect(cut, &QDoubleSpinBox::valueChanged, this, &PhysicsView::markPending);
138 pending = false;
139 status->setText(tr("Changes are applied before the next run, or with Apply physics."));
140}
141
142void PhysicsView::addConstructor(const QString& requested) {
143 auto* page = new QWidget(additions);
144 auto* layout = new QFormLayout(page);
145 auto* combo = new QComboBox(page);
146 combo->setObjectName("gemcPhysicsConstructor");
148 QString name = requested;
149 if (name.isEmpty()) {
150 name = "G4OpticalPhysics";
151 if (selection().split('+').contains(name)) {
152 for (int i = 0; i < combo->count(); ++i) {
153 if (!selection().split('+').contains(combo->itemText(i))) { name = combo->itemText(i); break; }
154 }
155 }
156 }
157 if (combo->findText(name) < 0) { combo->addItem(name); }
158 combo->setCurrentText(name);
159 layout->addRow(tr("Physics constructor"), combo);
160 const int index = additions->insertTab(additions->count() - 1, page, name + " ");
161 additions->setCurrentIndex(index);
162 connect(combo, &QComboBox::currentTextChanged, this, [this, page](const QString& text) {
163 additions->setTabText(additions->indexOf(page), text + " ");
164 markPending();
165 });
166}
167
168QString PhysicsView::selection() const {
169 QStringList parts{reference->currentText().trimmed()};
170 for (int i = 0; i < additions->count() - 1; ++i) {
171 parts.push_back(additions->widget(i)->findChild<QComboBox*>()->currentText().trimmed());
172 }
173 return parts.join('+');
174}
175
176void PhysicsView::markPending() {
177 pending = true;
178 status->setText(tr("Physics changes pending. They will be applied before the next run."));
179}
180
182 if (!pending) { return true; }
184 if (!physics) {
185 status->setText(tr("Physics controls require a GEMC run manager."));
186 return false;
187 }
188 std::string error;
189 if (!physics->select(selection().toStdString(), error) ||
190 !physics->setProductionCut(cut->value(), error)) {
191 status->setText(QString::fromStdString(error));
192 return false;
193 }
194 const auto available = physics->processes();
195 for (const auto& [name, enabled] : pendingActivation) {
196 if (std::any_of(available.begin(), available.end(), [&](const auto& item) { return item.name == name; }) &&
197 !physics->setProcessEnabled(name, enabled, error)) {
198 status->setText(QString::fromStdString(error));
199 return false;
200 }
201 }
202 options->setOptionValueFromString("phys_list", physics->selection());
203 options->setOptionValueFromString("production_cut", std::to_string(cut->value()));
204 pendingActivation.clear();
205 pending = false;
207 status->setText(tr("Physics applied. The next run uses these settings."));
208 return true;
209}
210
212 refreshing = true;
213 processTree->clear();
214 const auto* physics = gphysics::RuntimePhysicsList::current();
215 if (physics) {
216 std::map<std::string, QTreeWidgetItem*> groups;
217 const std::map<std::string, QString> labels{
218 {"Electromagnetic", tr("Electromagnetic")}, {"Hadronic", tr("Hadronic")},
219 {"Optical", tr("Optical")}, {"Decay", tr("Decay")}, {"General", tr("General")},
220 {"Transportation", tr("Transportation")}, {"Other", tr("Other")},
221 {"Parameterisation", tr("Parameterisation")}, {"Parallel worlds", tr("Parallel worlds")}};
222 for (const auto& process : physics->processes()) {
223 auto*& group = groups[process.category];
224 if (!group) {
225 group = new QTreeWidgetItem(processTree, {labels.at(process.category)});
226 group->setFlags(group->flags() | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable);
227 }
228 QStringList particles;
229 for (const auto& name : process.particles) { particles.push_back(QString::fromStdString(name)); }
230 const auto coverage = particles.size() == 1 ? particles.first() :
231 tr("%1 particle types").arg(particles.size());
232 auto* item = new QTreeWidgetItem(group, {QString::fromStdString(process.name), coverage});
233 item->setToolTip(1, particles.join(", "));
234 item->setData(0, Qt::UserRole, QString::fromStdString(process.name));
235 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
236 const auto pendingChoice = pendingActivation.find(process.name);
237 item->setCheckState(0, pendingChoice != pendingActivation.end() ?
238 (pendingChoice->second ? Qt::Checked : Qt::Unchecked) :
239 process.mixed ? Qt::PartiallyChecked : process.active ? Qt::Checked : Qt::Unchecked);
240 if (!process.editable) {
241 item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable);
242 group->setFlags(group->flags() & ~Qt::ItemIsUserCheckable);
243 item->setToolTip(0, tr("Transportation is required for tracking."));
244 }
245 }
246 processTree->sortItems(0, Qt::AscendingOrder);
247 for (const auto& [name, group] : groups) {
248 group->setExpanded(name == "Optical");
249 }
250 }
251 if (!processTree->topLevelItemCount()) {
252 new QTreeWidgetItem(processTree, {tr("Processes appear after physics initialization.")});
253 }
254 refreshing = false;
255}
void refreshProcesses()
bool applyPending()
PhysicsView(std::shared_ptr< GOptions > options, QWidget *parent=nullptr)
static std::vector< std::string > referenceLists()
static std::vector< std::string > constructors()
static RuntimePhysicsList * current()