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);
45 auto* configuration =
new QGroupBox(tr(
"Physics list"),
this);
46 auto* form =
new QFormLayout(configuration);
47 reference =
new QComboBox(configuration);
48 reference->setObjectName(
"gemcPhysicsReference");
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());
57 reference->setCurrentText(parts.empty() ?
"FTFP_BERT" : parts.first().trimmed());
58 form->addRow(tr(
"Reference list"), reference);
60 cut =
new QDoubleSpinBox(configuration);
61 cut->setObjectName(
"gemcProductionCut");
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);
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(); }
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);
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);
117 group->setHidden(!visible);
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;
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);
139 status->setText(tr(
"Changes are applied before the next run, or with Apply physics."));
182 if (!pending) {
return true; }
185 status->setText(tr(
"Physics controls require a GEMC run manager."));
189 if (!physics->select(selection().toStdString(), error) ||
190 !physics->setProductionCut(cut->value(), error)) {
191 status->setText(QString::fromStdString(error));
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));
202 options->setOptionValueFromString(
"phys_list", physics->selection());
203 options->setOptionValueFromString(
"production_cut", std::to_string(cut->value()));
204 pendingActivation.clear();
207 status->setText(tr(
"Physics applied. The next run uses these settings."));
213 processTree->clear();
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];
225 group =
new QTreeWidgetItem(processTree, {labels.at(process.category)});
226 group->setFlags(group->flags() | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable);
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."));
246 processTree->sortItems(0, Qt::AscendingOrder);
247 for (
const auto& [name, group] : groups) {
248 group->setExpanded(name ==
"Optical");
251 if (!processTree->topLevelItemCount()) {
252 new QTreeWidgetItem(processTree, {tr(
"Processes appear after physics initialization.")});