From 8a3379adc63521ebf36a034e020073b0d741f1bf Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 10:15:14 +0100 Subject: [PATCH 01/10] A first rough implementation --- .gitignore | 1 + neopdf_gui/CMakeLists.txt | 77 +++++++++++ neopdf_gui/MainWindow.cpp | 284 ++++++++++++++++++++++++++++++++++++++ neopdf_gui/MainWindow.hpp | 48 +++++++ neopdf_gui/README.md | 32 +++++ neopdf_gui/main.cpp | 10 ++ 6 files changed, 452 insertions(+) create mode 100644 neopdf_gui/CMakeLists.txt create mode 100644 neopdf_gui/MainWindow.cpp create mode 100644 neopdf_gui/MainWindow.hpp create mode 100644 neopdf_gui/README.md create mode 100644 neopdf_gui/main.cpp diff --git a/.gitignore b/.gitignore index 31219be..9a78fa0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target +/neopdf_gui/build/ /neopdf_capi/docs/build/ /neopdf_capi/docs/xml/ /neopdf_capi/docs/html/ diff --git a/neopdf_gui/CMakeLists.txt b/neopdf_gui/CMakeLists.txt new file mode 100644 index 0000000..9028d6f --- /dev/null +++ b/neopdf_gui/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 3.16) +project(neopdf_gui LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Automatically run MOC, UIC, and RCC +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +# Find Qt6 +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Charts) + +# Find the neopdf_capi header directory +find_path( + NEOPDF_CAPI_INCLUDE_DIR + NAMES NeoPDF.hpp neopdf_capi.h + PATHS /usr/local/include/neopdf_capi + NO_DEFAULT_PATH +) +if(NOT NEOPDF_CAPI_INCLUDE_DIR) + message(FATAL_ERROR "Could not find NeoPDF.hpp/neopdf_capi.h") +endif() + +# Find the neopdf_capi library +find_library( + NEOPDF_CAPI_LIBRARY + NAMES neopdf_capi + PATHS /usr/local/lib + NO_DEFAULT_PATH +) +if(NOT NEOPDF_CAPI_LIBRARY) + message(FATAL_ERROR "Could not find libneopdf_capi") +endif() + +message(STATUS "Found neopdf_capi include dir: ${NEOPDF_CAPI_INCLUDE_DIR}") +message(STATUS "Found neopdf_capi library: ${NEOPDF_CAPI_LIBRARY}") + +# --- End of finding neopdf_capi --- + +# Define the executable +add_executable(neopdf_gui + main.cpp + MainWindow.cpp + MainWindow.hpp +) + +# Link against Qt6 libraries +target_link_libraries(neopdf_gui + PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Charts +) + +# Include directories +target_include_directories(neopdf_gui + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${NEOPDF_CAPI_INCLUDE_DIR} +) + +# Link against the neopdf_capi library +target_link_libraries(neopdf_gui + PRIVATE + ${NEOPDF_CAPI_LIBRARY} +) + +# Add preprocessor definition to disable std::filesystem integration in Qt +# to solve macOS SDK compatibility issues with conda-forge compilers. +target_compile_definitions(neopdf_gui PRIVATE QT_NO_FILESYSTEM) + +install(TARGETS neopdf_gui + RUNTIME DESTINATION bin +) diff --git a/neopdf_gui/MainWindow.cpp b/neopdf_gui/MainWindow.cpp new file mode 100644 index 0000000..479cbfe --- /dev/null +++ b/neopdf_gui/MainWindow.cpp @@ -0,0 +1,284 @@ +#include "MainWindow.hpp" +#include "NeoPDF.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { + setupUI(); + setWindowTitle("NeoPDF Plotter"); + resize(1200, 800); +} + +MainWindow::~MainWindow() {} + +void MainWindow::setupUI() { + centralWidget = new QWidget(this); + setCentralWidget(centralWidget); + + mainLayout = new QHBoxLayout(centralWidget); + + // --- Controls Panel --- + controlsLayout = new QVBoxLayout(); + + // PDF Set Management + setSelectionGroup = new QGroupBox("PDF Sets"); + setSelectionLayout = new QVBoxLayout(); + setListWidget = new QListWidget(); + addSetButton = new QPushButton("Add PDF Set"); + connect(addSetButton, &QPushButton::clicked, this, &MainWindow::onAddSetButtonClicked); + + setSelectionLayout->addWidget(setListWidget); + setSelectionLayout->addWidget(addSetButton); + setSelectionGroup->setLayout(setSelectionLayout); + + // Plotting Parameters + plotParamsGroup = new QGroupBox("Plot Parameters"); + plotParamsLayout = new QFormLayout(); + xAxisVarCombo = new QComboBox(); + xAxisVarCombo->addItems({"x", "Q2"}); + connect(xAxisVarCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &MainWindow::onXAxisVarChanged); + + pidCombo = new QComboBox(); + pidCombo->addItem("g (21)", 21); + pidCombo->addItem("u (2)", 2); + pidCombo->addItem("d (1)", 1); + pidCombo->addItem("s (3)", 3); + pidCombo->addItem("c (4)", 4); + pidCombo->addItem("b (5)", 5); + pidCombo->addItem("t (6)", 6); + pidCombo->addItem("ubar (-2)", -2); + pidCombo->addItem("dbar (-1)", -1); + pidCombo->addItem("sbar (-3)", -3); + pidCombo->addItem("cbar (-4)", -4); + pidCombo->addItem("bbar (-5)", -5); + pidCombo->addItem("tbar (-6)", -6); + pidCombo->setCurrentIndex(0); // Default to gluon + + q2ValueEdit = new QLineEdit("100.0"); + xValueEdit = new QLineEdit("0.1"); + + rangeMinEdit = new QLineEdit("1e-5"); + rangeMaxEdit = new QLineEdit("1.0"); + pointsEdit = new QLineEdit("100"); + + xAxisLogCheck = new QCheckBox("Logarithmic X-axis"); + yAxisLogCheck = new QCheckBox("Logarithmic Y-axis"); + + plotButton = new QPushButton("Plot"); + connect(plotButton, &QPushButton::clicked, this, &MainWindow::onPlotButtonClicked); + + plotParamsLayout->addRow("X-axis variable:", xAxisVarCombo); + plotParamsLayout->addRow("PID:", pidCombo); + plotParamsLayout->addRow("Fixed Q2 value:", q2ValueEdit); + plotParamsLayout->addRow("Fixed x value:", xValueEdit); + plotParamsLayout->addRow("Plot Range Min:", rangeMinEdit); + plotParamsLayout->addRow("Plot Range Max:", rangeMaxEdit); + plotParamsLayout->addRow("Number of Points:", pointsEdit); + plotParamsLayout->addRow(xAxisLogCheck); + plotParamsLayout->addRow(yAxisLogCheck); + plotParamsGroup->setLayout(plotParamsLayout); + + controlsLayout->addWidget(setSelectionGroup); + controlsLayout->addWidget(plotParamsGroup); + controlsLayout->addWidget(plotButton); + controlsLayout->addStretch(); + + // --- Chart View --- + chartView = new QChartView(); + chartView->setRenderHint(QPainter::Antialiasing); + + mainLayout->addLayout(controlsLayout, 1); // 1/4 of the width + mainLayout->addWidget(chartView, 3); // 3/4 of the width + + onXAxisVarChanged(xAxisVarCombo->currentIndex()); +} + +void MainWindow::onAddSetButtonClicked() { + bool ok; + QString setName = QInputDialog::getText(this, tr("Add PDF Set"), + tr("PDF set name:"), QLineEdit::Normal, + "", &ok); + if (ok && !setName.isEmpty()) { + QListWidgetItem* item = new QListWidgetItem(setName); + item->setData(Qt::UserRole, setName); + setListWidget->addItem(item); + } +} + +void MainWindow::onXAxisVarChanged(int index) { + if (index == 0) { // "x" is x-axis + xValueEdit->setEnabled(false); + q2ValueEdit->setEnabled(true); + rangeMinEdit->setText("1e-5"); + rangeMaxEdit->setText("1.0"); + } else { // "Q2" is x-axis + xValueEdit->setEnabled(true); + q2ValueEdit->setEnabled(false); + rangeMinEdit->setText("1.0"); + rangeMaxEdit->setText("10000.0"); + } +} + +void MainWindow::onPlotButtonClicked() { + if (setListWidget->selectedItems().isEmpty()) { + QMessageBox::warning(this, "No PDF Set", "Please select a PDF set to plot."); + return; + } + + // 1. Get parameters from UI + QListWidgetItem* selectedItem = setListWidget->selectedItems().first(); + QString setName = selectedItem->data(Qt::UserRole).toString(); + + bool ok; + QString xAxisVar = xAxisVarCombo->currentText(); + int pid = pidCombo->currentData().toInt(); + + double fixed_q2 = q2ValueEdit->text().toDouble(&ok); + if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid fixed Q2 value."); return; } + + double fixed_x = xValueEdit->text().toDouble(&ok); + if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid fixed x value."); return; } + + double range_min = rangeMinEdit->text().toDouble(&ok); + if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid range min value."); return; } + + double range_max = rangeMaxEdit->text().toDouble(&ok); + if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid range max value."); return; } + + int n_points = pointsEdit->text().toInt(&ok); + if (!ok || n_points <= 1) { QMessageBox::warning(this, "Invalid Input", "Number of points must be an integer greater than 1."); return; } + + bool isXLog = xAxisLogCheck->isChecked(); + if (isXLog && range_min <= 0.0) { + QMessageBox::warning(this, "Invalid Input", "Minimum range for logarithmic X-axis must be positive."); + return; + } + bool isYLog = yAxisLogCheck->isChecked(); + + // 2. Load all members of the selected PDF set using `neopdf::NeoPDFs`. + neopdf::NeoPDFs* pdfs = nullptr; + try { + pdfs = new neopdf::NeoPDFs(setName.toStdString()); + } catch (const std::exception& e) { + QMessageBox::critical(this, "Error loading PDF", e.what()); + if (pdfs) delete pdfs; + return; + } + + // Create series for plot + auto *mean_series = new QLineSeries(); + mean_series->setName("Mean"); + auto *upper_series = new QLineSeries(); + auto *lower_series = new QLineSeries(); + + // 3. Generate data points for the plot. + double step = (range_max - range_min) / (n_points - 1); + + for (int i = 0; i < n_points; ++i) { + double x_val = range_min + i * step; + + std::vector params; + if (xAxisVar == "x") { + params = {x_val, fixed_q2}; + } else { // Q2 + params = {fixed_x, x_val}; + } + + std::vector results_for_point; + results_for_point.reserve(pdfs->size()); + for (size_t j = 0; j < pdfs->size(); ++j) { + results_for_point.push_back(pdfs->at(j).xfxQ2(pid, params[0], params[1])); + } + + // 4. Calculate mean and std deviation. + double sum = std::accumulate(results_for_point.begin(), results_for_point.end(), 0.0); + double mean = sum / results_for_point.size(); + + double sq_sum = 0.0; + for (const auto& val : results_for_point) { + sq_sum += (val - mean) * (val - mean); + } + double std_dev = std::sqrt(sq_sum / results_for_point.size()); + + mean_series->append(x_val, mean); + upper_series->append(x_val, mean + std_dev); + lower_series->append(x_val, mean - std_dev); + } + + delete pdfs; + + // 5. Create QLineSeries for the mean and QAreaSeries for the error band. + auto *area_series = new QAreaSeries(upper_series, lower_series); + area_series->setName("1-sigma Error Band"); + QPen pen(0x059669); + pen.setWidth(2); + mean_series->setPen(pen); + area_series->setColor(QColor(0x6EE7B7)); + area_series->setBorderColor(QColor(0x6EE7B7)); + + // 6. Create a QChart, add the series, and set it on the chartView. + auto *chart = new QChart(); + chart->addSeries(area_series); + chart->addSeries(mean_series); + + chart->setTitle("PDF: " + setName + " (pid=" + QString::number(pid) + ")"); + + // Axis creation + // X Axis + QAbstractAxis *axisX; + if (isXLog) { + auto *logAxis = new QLogValueAxis(); + logAxis->setBase(10.0); + logAxis->setLabelFormat("%.0e"); + logAxis->setMinorTickCount(-1); + axisX = logAxis; + } else { + auto *valAxis = new QValueAxis(); + valAxis->setLabelFormat(xAxisVar == "x" ? "%.1e" : "%.1f"); + if(xAxisVar == "x") valAxis->setTickCount(10); + axisX = valAxis; + } + axisX->setTitleText(xAxisVar); + chart->addAxis(axisX, Qt::AlignBottom); + mean_series->attachAxis(axisX); + area_series->attachAxis(axisX); + + // Y Axis + QAbstractAxis *axisY; + if (isYLog) { + auto *logAxis = new QLogValueAxis(); + logAxis->setBase(10.0); + logAxis->setLabelFormat("%.0e"); + logAxis->setMinorTickCount(-1); + axisY = logAxis; + } else { + axisY = new QValueAxis(); + } + + if (xAxisVar == "x") { + axisY->setTitleText("x * f(x, Q2=" + QString::number(fixed_q2, 'g', 4) + ")"); + } else { // Q2 + axisY->setTitleText("x * f(x=" + QString::number(fixed_x, 'g', 3) + ", Q2)"); + } + chart->addAxis(axisY, Qt::AlignLeft); + mean_series->attachAxis(axisY); + area_series->attachAxis(axisY); + + chart->legend()->setVisible(true); + chart->legend()->setAlignment(Qt::AlignBottom); + + // This will take ownership of the chart and delete the old one + chartView->setChart(chart); +} diff --git a/neopdf_gui/MainWindow.hpp b/neopdf_gui/MainWindow.hpp new file mode 100644 index 0000000..c360a9f --- /dev/null +++ b/neopdf_gui/MainWindow.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +class MainWindow : public QMainWindow { + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void onPlotButtonClicked(); + void onAddSetButtonClicked(); + void onXAxisVarChanged(int index); + +private: + void setupUI(); + + // Main layout + QWidget *centralWidget; + QHBoxLayout *mainLayout; + QVBoxLayout *controlsLayout; + + // Controls + QGroupBox *setSelectionGroup; + QVBoxLayout *setSelectionLayout; + QListWidget *setListWidget; + QPushButton *addSetButton; + + QGroupBox *plotParamsGroup; + QFormLayout *plotParamsLayout; + QComboBox *xAxisVarCombo; + QComboBox *pidCombo; + QLineEdit *q2ValueEdit; + QLineEdit *xValueEdit; + QLineEdit *rangeMinEdit; + QLineEdit *rangeMaxEdit; + QLineEdit *pointsEdit; + QCheckBox *xAxisLogCheck; + QCheckBox *yAxisLogCheck; + QPushButton *plotButton; + + // Plotting + QChartView *chartView; +}; diff --git a/neopdf_gui/README.md b/neopdf_gui/README.md new file mode 100644 index 0000000..1e1a5d2 --- /dev/null +++ b/neopdf_gui/README.md @@ -0,0 +1,32 @@ +# NeoPDF GUI Plotter + +A C++/Qt6 application to plot and compare PDF sets using the `neopdf` library. + +## Dependencies + +- A C++ compiler (g++, clang, msvc) +- CMake (version 3.16 or higher) +- Qt6 (including the Charts module) +- Rust toolchain (for building the `neopdf_capi`) + +## How to Build + +1. **Configure with CMake:** + From the `neopdf_gui` directory, run: + ```bash + cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt/6.9.1/lib/cmake/Qt6 + ``` + This will configure the project and also trigger the build of the `neopdf_capi` Rust library. + +2. **Build the application:** + ```bash + cmake --build build + ``` + +## How to Run + +After a successful build, the executable will be located in the `build` directory. + +```bash +./build/neopdf_gui +``` diff --git a/neopdf_gui/main.cpp b/neopdf_gui/main.cpp new file mode 100644 index 0000000..5b2b80a --- /dev/null +++ b/neopdf_gui/main.cpp @@ -0,0 +1,10 @@ +#include +#include "MainWindow.hpp" +#include + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + MainWindow mainWindow; + mainWindow.show(); + return app.exec(); +} From 5663a4d03cbe85c946cabb9238a6485c75cbff2a Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 10:33:54 +0100 Subject: [PATCH 02/10] Support different sets --- neopdf_gui/MainWindow.cpp | 170 +++++++++++++++++++++++++------------- neopdf_gui/MainWindow.hpp | 32 +++++-- 2 files changed, 138 insertions(+), 64 deletions(-) diff --git a/neopdf_gui/MainWindow.cpp b/neopdf_gui/MainWindow.cpp index 479cbfe..9a1c55c 100644 --- a/neopdf_gui/MainWindow.cpp +++ b/neopdf_gui/MainWindow.cpp @@ -1,5 +1,6 @@ #include "MainWindow.hpp" #include "NeoPDF.hpp" + #include #include #include @@ -38,6 +39,7 @@ void MainWindow::setupUI() { setListWidget = new QListWidget(); addSetButton = new QPushButton("Add PDF Set"); connect(addSetButton, &QPushButton::clicked, this, &MainWindow::onAddSetButtonClicked); + connect(setListWidget, &QListWidget::currentItemChanged, this, &MainWindow::onCurrentSetChanged); setSelectionLayout->addWidget(setListWidget); setSelectionLayout->addWidget(addSetButton); @@ -46,8 +48,8 @@ void MainWindow::setupUI() { // Plotting Parameters plotParamsGroup = new QGroupBox("Plot Parameters"); plotParamsLayout = new QFormLayout(); + xAxisVarCombo = new QComboBox(); - xAxisVarCombo->addItems({"x", "Q2"}); connect(xAxisVarCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &MainWindow::onXAxisVarChanged); pidCombo = new QComboBox(); @@ -66,30 +68,43 @@ void MainWindow::setupUI() { pidCombo->addItem("tbar (-6)", -6); pidCombo->setCurrentIndex(0); // Default to gluon - q2ValueEdit = new QLineEdit("100.0"); - xValueEdit = new QLineEdit("0.1"); + // Initialize all possible parameters + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_NUCLEONS, "Nucleon (A)", nullptr, nullptr, false, 1.0, "1.0"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_ALPHAS, "alpha_s", nullptr, nullptr, false, 0.118, "0.118"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_XI, "xi", nullptr, nullptr, false, 0.0, "0.0"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_DELTA, "delta", nullptr, nullptr, false, 0.0, "0.0"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_KT, "kt", nullptr, nullptr, false, 0.0, "0.0"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_MOMENTUM, "x", nullptr, nullptr, false, 0.1, "0.1"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_SCALE, "Q2", nullptr, nullptr, false, 100.0, "100.0"}); + + plotParamsLayout->addRow("X-axis variable:", xAxisVarCombo); + plotParamsLayout->addRow("PID:", pidCombo); + + for (auto& info : m_paramInfos) { + info.widget = new QLineEdit(info.default_text); + info.label = new QLabel("Fixed " + info.name + " value:"); + plotParamsLayout->addRow(info.label, info.widget); + info.widget->setVisible(false); + info.label->setVisible(false); + } rangeMinEdit = new QLineEdit("1e-5"); rangeMaxEdit = new QLineEdit("1.0"); pointsEdit = new QLineEdit("100"); - xAxisLogCheck = new QCheckBox("Logarithmic X-axis"); yAxisLogCheck = new QCheckBox("Logarithmic Y-axis"); - plotButton = new QPushButton("Plot"); - connect(plotButton, &QPushButton::clicked, this, &MainWindow::onPlotButtonClicked); - - plotParamsLayout->addRow("X-axis variable:", xAxisVarCombo); - plotParamsLayout->addRow("PID:", pidCombo); - plotParamsLayout->addRow("Fixed Q2 value:", q2ValueEdit); - plotParamsLayout->addRow("Fixed x value:", xValueEdit); plotParamsLayout->addRow("Plot Range Min:", rangeMinEdit); plotParamsLayout->addRow("Plot Range Max:", rangeMaxEdit); plotParamsLayout->addRow("Number of Points:", pointsEdit); plotParamsLayout->addRow(xAxisLogCheck); plotParamsLayout->addRow(yAxisLogCheck); + plotParamsGroup->setLayout(plotParamsLayout); + plotButton = new QPushButton("Plot"); + connect(plotButton, &QPushButton::clicked, this, &MainWindow::onPlotButtonClicked); + controlsLayout->addWidget(setSelectionGroup); controlsLayout->addWidget(plotParamsGroup); controlsLayout->addWidget(plotButton); @@ -99,10 +114,8 @@ void MainWindow::setupUI() { chartView = new QChartView(); chartView->setRenderHint(QPainter::Antialiasing); - mainLayout->addLayout(controlsLayout, 1); // 1/4 of the width - mainLayout->addWidget(chartView, 3); // 3/4 of the width - - onXAxisVarChanged(xAxisVarCombo->currentIndex()); + mainLayout->addLayout(controlsLayout, 1); + mainLayout->addWidget(chartView, 3); } void MainWindow::onAddSetButtonClicked() { @@ -114,20 +127,59 @@ void MainWindow::onAddSetButtonClicked() { QListWidgetItem* item = new QListWidgetItem(setName); item->setData(Qt::UserRole, setName); setListWidget->addItem(item); + setListWidget->setCurrentItem(item); + } +} + +void MainWindow::onCurrentSetChanged(QListWidgetItem *current, QListWidgetItem *previous) { + if (!current) return; + updateParametersUI(current->data(Qt::UserRole).toString()); +} + +void MainWindow::updateParametersUI(const QString& setName) { + neopdf::NeoPDF* pdf = nullptr; + try { + pdf = new neopdf::NeoPDF(setName.toStdString(), 0); + } catch (const std::exception& e) { + QMessageBox::critical(this, "Error loading PDF", e.what()); + // Reset UI to a default state + for (auto& info : m_paramInfos) { + info.active = false; + info.widget->setVisible(false); + info.label->setVisible(false); + } + xAxisVarCombo->clear(); + return; } + + xAxisVarCombo->blockSignals(true); + xAxisVarCombo->clear(); + + for (auto& info : m_paramInfos) { + auto range = pdf->param_range(info.id); + info.active = (range[0] < range[1]); + info.widget->setVisible(info.active); + info.label->setVisible(info.active); + if (info.active) { + xAxisVarCombo->addItem(info.name, QVariant::fromValue(info.id)); + } + } + + delete pdf; + xAxisVarCombo->blockSignals(false); + onXAxisVarChanged(xAxisVarCombo->currentIndex()); } + void MainWindow::onXAxisVarChanged(int index) { - if (index == 0) { // "x" is x-axis - xValueEdit->setEnabled(false); - q2ValueEdit->setEnabled(true); - rangeMinEdit->setText("1e-5"); - rangeMaxEdit->setText("1.0"); - } else { // "Q2" is x-axis - xValueEdit->setEnabled(true); - q2ValueEdit->setEnabled(false); - rangeMinEdit->setText("1.0"); - rangeMaxEdit->setText("10000.0"); + if (index < 0) return; + + NeopdfSubgridParams selected_id = static_cast(xAxisVarCombo->itemData(index).toInt()); + + for (auto& info : m_paramInfos) { + if (info.active) { + info.widget->setEnabled(info.id != selected_id); + } } } @@ -136,20 +188,29 @@ void MainWindow::onPlotButtonClicked() { QMessageBox::warning(this, "No PDF Set", "Please select a PDF set to plot."); return; } + if (xAxisVarCombo->currentIndex() < 0) { + QMessageBox::warning(this, "No variable selected", "Please select a variable to plot."); + return; + } // 1. Get parameters from UI - QListWidgetItem* selectedItem = setListWidget->selectedItems().first(); - QString setName = selectedItem->data(Qt::UserRole).toString(); + QString setName = setListWidget->currentItem()->data(Qt::UserRole).toString(); + NeopdfSubgridParams xAxisVarId = static_cast(xAxisVarCombo->currentData().toInt()); bool ok; - QString xAxisVar = xAxisVarCombo->currentText(); int pid = pidCombo->currentData().toInt(); - double fixed_q2 = q2ValueEdit->text().toDouble(&ok); - if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid fixed Q2 value."); return; } - - double fixed_x = xValueEdit->text().toDouble(&ok); - if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid fixed x value."); return; } + QMap fixed_values; + for (const auto& info : m_paramInfos) { + if (info.active) { + double val = info.widget->text().toDouble(&ok); + if (!ok) { + QMessageBox::warning(this, "Invalid Input", "Invalid value for " + info.name); + return; + } + fixed_values[info.id] = val; + } + } double range_min = rangeMinEdit->text().toDouble(&ok); if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid range min value."); return; } @@ -167,7 +228,7 @@ void MainWindow::onPlotButtonClicked() { } bool isYLog = yAxisLogCheck->isChecked(); - // 2. Load all members of the selected PDF set using `neopdf::NeoPDFs`. + // 2. Load all members neopdf::NeoPDFs* pdfs = nullptr; try { pdfs = new neopdf::NeoPDFs(setName.toStdString()); @@ -177,32 +238,34 @@ void MainWindow::onPlotButtonClicked() { return; } - // Create series for plot auto *mean_series = new QLineSeries(); mean_series->setName("Mean"); auto *upper_series = new QLineSeries(); auto *lower_series = new QLineSeries(); - // 3. Generate data points for the plot. - double step = (range_max - range_min) / (n_points - 1); + // 3. Generate data points + double step = isXLog ? std::pow(range_max / range_min, 1.0 / (n_points - 1)) : (range_max - range_min) / (n_points - 1); for (int i = 0; i < n_points; ++i) { - double x_val = range_min + i * step; + double x_val = isXLog ? range_min * std::pow(step, i) : range_min + i * step; std::vector params; - if (xAxisVar == "x") { - params = {x_val, fixed_q2}; - } else { // Q2 - params = {fixed_x, x_val}; + for (const auto& info : m_paramInfos) { + if (info.active) { + if (info.id == xAxisVarId) { + params.push_back(x_val); + } else { + params.push_back(fixed_values[info.id]); + } + } } std::vector results_for_point; results_for_point.reserve(pdfs->size()); for (size_t j = 0; j < pdfs->size(); ++j) { - results_for_point.push_back(pdfs->at(j).xfxQ2(pid, params[0], params[1])); + results_for_point.push_back(pdfs->at(j).xfxQ2_ND(pid, params)); } - // 4. Calculate mean and std deviation. double sum = std::accumulate(results_for_point.begin(), results_for_point.end(), 0.0); double mean = sum / results_for_point.size(); @@ -219,7 +282,6 @@ void MainWindow::onPlotButtonClicked() { delete pdfs; - // 5. Create QLineSeries for the mean and QAreaSeries for the error band. auto *area_series = new QAreaSeries(upper_series, lower_series); area_series->setName("1-sigma Error Band"); QPen pen(0x059669); @@ -228,15 +290,11 @@ void MainWindow::onPlotButtonClicked() { area_series->setColor(QColor(0x6EE7B7)); area_series->setBorderColor(QColor(0x6EE7B7)); - // 6. Create a QChart, add the series, and set it on the chartView. auto *chart = new QChart(); chart->addSeries(area_series); chart->addSeries(mean_series); - chart->setTitle("PDF: " + setName + " (pid=" + QString::number(pid) + ")"); - // Axis creation - // X Axis QAbstractAxis *axisX; if (isXLog) { auto *logAxis = new QLogValueAxis(); @@ -246,16 +304,14 @@ void MainWindow::onPlotButtonClicked() { axisX = logAxis; } else { auto *valAxis = new QValueAxis(); - valAxis->setLabelFormat(xAxisVar == "x" ? "%.1e" : "%.1f"); - if(xAxisVar == "x") valAxis->setTickCount(10); + valAxis->setLabelFormat("%.1e"); axisX = valAxis; } - axisX->setTitleText(xAxisVar); + axisX->setTitleText(xAxisVarCombo->currentText()); chart->addAxis(axisX, Qt::AlignBottom); mean_series->attachAxis(axisX); area_series->attachAxis(axisX); - // Y Axis QAbstractAxis *axisY; if (isYLog) { auto *logAxis = new QLogValueAxis(); @@ -267,11 +323,8 @@ void MainWindow::onPlotButtonClicked() { axisY = new QValueAxis(); } - if (xAxisVar == "x") { - axisY->setTitleText("x * f(x, Q2=" + QString::number(fixed_q2, 'g', 4) + ")"); - } else { // Q2 - axisY->setTitleText("x * f(x=" + QString::number(fixed_x, 'g', 3) + ", Q2)"); - } + QString yTitle = "x * f(...)"; + axisY->setTitleText(yTitle); chart->addAxis(axisY, Qt::AlignLeft); mean_series->attachAxis(axisY); area_series->attachAxis(axisY); @@ -279,6 +332,5 @@ void MainWindow::onPlotButtonClicked() { chart->legend()->setVisible(true); chart->legend()->setAlignment(Qt::AlignBottom); - // This will take ownership of the chart and delete the old one chartView->setChart(chart); } diff --git a/neopdf_gui/MainWindow.hpp b/neopdf_gui/MainWindow.hpp index c360a9f..fca89eb 100644 --- a/neopdf_gui/MainWindow.hpp +++ b/neopdf_gui/MainWindow.hpp @@ -3,6 +3,25 @@ #include #include #include +#include + +#include "neopdf_capi.h" // For NeopdfSubgridParams enum + +// Forward declarations +class QFormLayout; +class QLabel; +class QListWidgetItem; +class QLineEdit; + +struct ParamInfo { + NeopdfSubgridParams id; + QString name; + QLineEdit* widget = nullptr; + QLabel* label = nullptr; + bool active = false; + double default_val = 0.0; + QString default_text; +}; class MainWindow : public QMainWindow { Q_OBJECT @@ -15,9 +34,11 @@ private slots: void onPlotButtonClicked(); void onAddSetButtonClicked(); void onXAxisVarChanged(int index); + void onCurrentSetChanged(QListWidgetItem *current, QListWidgetItem *previous); private: void setupUI(); + void updateParametersUI(const QString& setName); // Main layout QWidget *centralWidget; @@ -34,11 +55,12 @@ private slots: QFormLayout *plotParamsLayout; QComboBox *xAxisVarCombo; QComboBox *pidCombo; - QLineEdit *q2ValueEdit; - QLineEdit *xValueEdit; - QLineEdit *rangeMinEdit; - QLineEdit *rangeMaxEdit; - QLineEdit *pointsEdit; + + QVector m_paramInfos; + + QLineEdit* rangeMinEdit; + QLineEdit* rangeMaxEdit; + QLineEdit* pointsEdit; QCheckBox *xAxisLogCheck; QCheckBox *yAxisLogCheck; QPushButton *plotButton; From c4444dde209ea46cc56aa13124d6f03278dab4ec Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 11:31:14 +0100 Subject: [PATCH 03/10] Add "clear all sets" --- neopdf_gui/MainWindow.cpp | 252 +++++++++++++++++++------------------- neopdf_gui/MainWindow.hpp | 6 +- 2 files changed, 133 insertions(+), 125 deletions(-) diff --git a/neopdf_gui/MainWindow.cpp b/neopdf_gui/MainWindow.cpp index 9a1c55c..b76fe6f 100644 --- a/neopdf_gui/MainWindow.cpp +++ b/neopdf_gui/MainWindow.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -37,12 +39,16 @@ void MainWindow::setupUI() { setSelectionGroup = new QGroupBox("PDF Sets"); setSelectionLayout = new QVBoxLayout(); setListWidget = new QListWidget(); + setListWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); addSetButton = new QPushButton("Add PDF Set"); connect(addSetButton, &QPushButton::clicked, this, &MainWindow::onAddSetButtonClicked); - connect(setListWidget, &QListWidget::currentItemChanged, this, &MainWindow::onCurrentSetChanged); + connect(setListWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::onSelectionSetChanged); setSelectionLayout->addWidget(setListWidget); setSelectionLayout->addWidget(addSetButton); + clearSetsButton = new QPushButton("Clear All"); + setSelectionLayout->addWidget(clearSetsButton); + connect(clearSetsButton, &QPushButton::clicked, this, &MainWindow::onClearSetsButtonClicked); setSelectionGroup->setLayout(setSelectionLayout); // Plotting Parameters @@ -68,7 +74,6 @@ void MainWindow::setupUI() { pidCombo->addItem("tbar (-6)", -6); pidCombo->setCurrentIndex(0); // Default to gluon - // Initialize all possible parameters m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_NUCLEONS, "Nucleon (A)", nullptr, nullptr, false, 1.0, "1.0"}); m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_ALPHAS, "alpha_s", nullptr, nullptr, false, 0.118, "0.118"}); m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_XI, "xi", nullptr, nullptr, false, 0.0, "0.0"}); @@ -110,7 +115,6 @@ void MainWindow::setupUI() { controlsLayout->addWidget(plotButton); controlsLayout->addStretch(); - // --- Chart View --- chartView = new QChartView(); chartView->setRenderHint(QPainter::Antialiasing); @@ -120,9 +124,7 @@ void MainWindow::setupUI() { void MainWindow::onAddSetButtonClicked() { bool ok; - QString setName = QInputDialog::getText(this, tr("Add PDF Set"), - tr("PDF set name:"), QLineEdit::Normal, - "", &ok); + QString setName = QInputDialog::getText(this, tr("Add PDF Set"), tr("PDF set name:"), QLineEdit::Normal, "", &ok); if (ok && !setName.isEmpty()) { QListWidgetItem* item = new QListWidgetItem(setName); item->setData(Qt::UserRole, setName); @@ -131,18 +133,12 @@ void MainWindow::onAddSetButtonClicked() { } } -void MainWindow::onCurrentSetChanged(QListWidgetItem *current, QListWidgetItem *previous) { - if (!current) return; - updateParametersUI(current->data(Qt::UserRole).toString()); +void MainWindow::onSelectionSetChanged() { + updateParametersUI(setListWidget->selectedItems()); } -void MainWindow::updateParametersUI(const QString& setName) { - neopdf::NeoPDF* pdf = nullptr; - try { - pdf = new neopdf::NeoPDF(setName.toStdString(), 0); - } catch (const std::exception& e) { - QMessageBox::critical(this, "Error loading PDF", e.what()); - // Reset UI to a default state +void MainWindow::updateParametersUI(const QList& items) { + if (items.isEmpty()) { for (auto& info : m_paramInfos) { info.active = false; info.widget->setVisible(false); @@ -152,29 +148,53 @@ void MainWindow::updateParametersUI(const QString& setName) { return; } + QSet commonParams; + bool first = true; + + for (const auto& item : items) { + QSet itemParams; + try { + neopdf::NeoPDF pdf(item->data(Qt::UserRole).toString().toStdString(), 0); + for (const auto& info : m_paramInfos) { + auto range = pdf.param_range(info.id); + if (range[0] < range[1]) { + itemParams.insert(info.id); + } + } + } catch (const std::exception& e) { + QMessageBox::warning(this, "Error Loading Set", "Could not inspect " + item->text() + ":\n" + e.what()); + // Skip this set for determining common params + continue; + } + + if (first) { + commonParams = itemParams; + first = false; + } else { + commonParams.intersect(itemParams); + } + } + xAxisVarCombo->blockSignals(true); xAxisVarCombo->clear(); for (auto& info : m_paramInfos) { - auto range = pdf->param_range(info.id); - info.active = (range[0] < range[1]); + info.active = commonParams.contains(info.id); info.widget->setVisible(info.active); info.label->setVisible(info.active); if (info.active) { - xAxisVarCombo->addItem(info.name, QVariant::fromValue(info.id)); + xAxisVarCombo->addItem(info.name, static_cast(info.id)); } } - delete pdf; xAxisVarCombo->blockSignals(false); onXAxisVarChanged(xAxisVarCombo->currentIndex()); } - void MainWindow::onXAxisVarChanged(int index) { if (index < 0) return; - NeopdfSubgridParams selected_id = static_cast(xAxisVarCombo->itemData(index).toInt()); + auto selected_id = static_cast(xAxisVarCombo->itemData(index).toInt()); for (auto& info : m_paramInfos) { if (info.active) { @@ -183,154 +203,140 @@ void MainWindow::onXAxisVarChanged(int index) { } } +void MainWindow::onClearSetsButtonClicked() { + setListWidget->clear(); + updateParametersUI({}); // Call with empty list to reset UI +} + void MainWindow::onPlotButtonClicked() { - if (setListWidget->selectedItems().isEmpty()) { - QMessageBox::warning(this, "No PDF Set", "Please select a PDF set to plot."); + QList selectedItems = setListWidget->selectedItems(); + if (selectedItems.isEmpty()) { + QMessageBox::warning(this, "No PDF Set", "Please select one or more PDF sets to plot."); return; } if (xAxisVarCombo->currentIndex() < 0) { - QMessageBox::warning(this, "No variable selected", "Please select a variable to plot."); + QMessageBox::warning(this, "No variable selected", "No common variables to plot. Please select sets with compatible kinematics."); return; } - // 1. Get parameters from UI - QString setName = setListWidget->currentItem()->data(Qt::UserRole).toString(); - NeopdfSubgridParams xAxisVarId = static_cast(xAxisVarCombo->currentData().toInt()); - bool ok; + auto xAxisVarId = static_cast(xAxisVarCombo->currentData().toInt()); int pid = pidCombo->currentData().toInt(); QMap fixed_values; for (const auto& info : m_paramInfos) { - if (info.active) { + if (info.active && info.id != xAxisVarId) { double val = info.widget->text().toDouble(&ok); - if (!ok) { - QMessageBox::warning(this, "Invalid Input", "Invalid value for " + info.name); - return; - } + if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid value for " + info.name); return; } fixed_values[info.id] = val; } } double range_min = rangeMinEdit->text().toDouble(&ok); if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid range min value."); return; } - double range_max = rangeMaxEdit->text().toDouble(&ok); if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid range max value."); return; } - int n_points = pointsEdit->text().toInt(&ok); - if (!ok || n_points <= 1) { QMessageBox::warning(this, "Invalid Input", "Number of points must be an integer greater than 1."); return; } + if (!ok || n_points <= 1) { QMessageBox::warning(this, "Invalid Input", "Number of points must be an integer > 1."); return; } bool isXLog = xAxisLogCheck->isChecked(); if (isXLog && range_min <= 0.0) { - QMessageBox::warning(this, "Invalid Input", "Minimum range for logarithmic X-axis must be positive."); - return; + QMessageBox::warning(this, "Invalid Input", "Minimum range for logarithmic X-axis must be positive."); return; } bool isYLog = yAxisLogCheck->isChecked(); - // 2. Load all members - neopdf::NeoPDFs* pdfs = nullptr; - try { - pdfs = new neopdf::NeoPDFs(setName.toStdString()); - } catch (const std::exception& e) { - QMessageBox::critical(this, "Error loading PDF", e.what()); - if (pdfs) delete pdfs; - return; - } + auto *chart = new QChart(); + auto *y_axis = isYLog ? static_cast(new QLogValueAxis()) : static_cast(new QValueAxis()); + chart->addAxis(y_axis, Qt::AlignLeft); + + QList colors = {Qt::blue, Qt::red, Qt::green, Qt::cyan, Qt::magenta, Qt::yellow, Qt::darkGray}; + int color_idx = 0; + + for (auto* item : selectedItems) { + QString setName = item->data(Qt::UserRole).toString(); + neopdf::NeoPDFs* pdfs = nullptr; + try { + pdfs = new neopdf::NeoPDFs(setName.toStdString()); + } catch (const std::exception& e) { + QMessageBox::warning(this, "Plotting Error", "Could not load " + setName + ":\n" + e.what()); + continue; + } - auto *mean_series = new QLineSeries(); - mean_series->setName("Mean"); - auto *upper_series = new QLineSeries(); - auto *lower_series = new QLineSeries(); + auto *mean_series = new QLineSeries(); + mean_series->setName(setName + "+1std"); + auto *upper_series = new QLineSeries(); + auto *lower_series = new QLineSeries(); - // 3. Generate data points - double step = isXLog ? std::pow(range_max / range_min, 1.0 / (n_points - 1)) : (range_max - range_min) / (n_points - 1); + double step = isXLog ? std::pow(range_max / range_min, 1.0 / (n_points - 1)) : (range_max - range_min) / (n_points - 1); - for (int i = 0; i < n_points; ++i) { - double x_val = isXLog ? range_min * std::pow(step, i) : range_min + i * step; + for (int i = 0; i < n_points; ++i) { + double x_val = isXLog ? range_min * std::pow(step, i) : range_min + i * step; - std::vector params; - for (const auto& info : m_paramInfos) { - if (info.active) { - if (info.id == xAxisVarId) { - params.push_back(x_val); - } else { - params.push_back(fixed_values[info.id]); + std::vector params; + for (const auto& info : m_paramInfos) { + if (info.active) { + params.push_back(info.id == xAxisVarId ? x_val : fixed_values[info.id]); } } - } - std::vector results_for_point; - results_for_point.reserve(pdfs->size()); - for (size_t j = 0; j < pdfs->size(); ++j) { - results_for_point.push_back(pdfs->at(j).xfxQ2_ND(pid, params)); - } + std::vector results; + results.reserve(pdfs->size()); + for (size_t j = 0; j < pdfs->size(); ++j) { + results.push_back(pdfs->at(j).xfxQ2_ND(pid, params)); + } - double sum = std::accumulate(results_for_point.begin(), results_for_point.end(), 0.0); - double mean = sum / results_for_point.size(); + double sum = std::accumulate(results.begin(), results.end(), 0.0); + double mean = sum / results.size(); + double sq_sum = std::accumulate(results.begin(), results.end(), 0.0, [mean](double acc, double val) { return acc + (val - mean) * (val - mean); }); + double std_dev = std::sqrt(sq_sum / results.size()); - double sq_sum = 0.0; - for (const auto& val : results_for_point) { - sq_sum += (val - mean) * (val - mean); + mean_series->append(x_val, mean); + upper_series->append(x_val, mean + std_dev); + lower_series->append(x_val, mean - std_dev); } - double std_dev = std::sqrt(sq_sum / results_for_point.size()); + delete pdfs; - mean_series->append(x_val, mean); - upper_series->append(x_val, mean + std_dev); - lower_series->append(x_val, mean - std_dev); - } + auto *area_series = new QAreaSeries(upper_series, lower_series); - delete pdfs; + QColor color = colors[color_idx % colors.size()]; + QPen pen(color); + pen.setWidth(2); + mean_series->setPen(pen); - auto *area_series = new QAreaSeries(upper_series, lower_series); - area_series->setName("1-sigma Error Band"); - QPen pen(0x059669); - pen.setWidth(2); - mean_series->setPen(pen); - area_series->setColor(QColor(0x6EE7B7)); - area_series->setBorderColor(QColor(0x6EE7B7)); + QColor areaColor = color; + areaColor.setAlphaF(0.15); + area_series->setColor(areaColor); + area_series->setBorderColor(areaColor); + area_series->setName(""); - auto *chart = new QChart(); - chart->addSeries(area_series); - chart->addSeries(mean_series); - chart->setTitle("PDF: " + setName + " (pid=" + QString::number(pid) + ")"); - - QAbstractAxis *axisX; - if (isXLog) { - auto *logAxis = new QLogValueAxis(); - logAxis->setBase(10.0); - logAxis->setLabelFormat("%.0e"); - logAxis->setMinorTickCount(-1); - axisX = logAxis; - } else { - auto *valAxis = new QValueAxis(); - valAxis->setLabelFormat("%.1e"); - axisX = valAxis; - } - axisX->setTitleText(xAxisVarCombo->currentText()); - chart->addAxis(axisX, Qt::AlignBottom); - mean_series->attachAxis(axisX); - area_series->attachAxis(axisX); - - QAbstractAxis *axisY; - if (isYLog) { - auto *logAxis = new QLogValueAxis(); - logAxis->setBase(10.0); - logAxis->setLabelFormat("%.0e"); - logAxis->setMinorTickCount(-1); - axisY = logAxis; - } else { - axisY = new QValueAxis(); + chart->addSeries(area_series); + chart->addSeries(mean_series); + + mean_series->attachAxis(y_axis); + area_series->attachAxis(y_axis); + + color_idx++; } - QString yTitle = "x * f(...)"; - axisY->setTitleText(yTitle); - chart->addAxis(axisY, Qt::AlignLeft); - mean_series->attachAxis(axisY); - area_series->attachAxis(axisY); + auto *x_axis = isXLog ? static_cast(new QLogValueAxis()) : static_cast(new QValueAxis()); + x_axis->setTitleText(xAxisVarCombo->currentText()); + chart->addAxis(x_axis, Qt::AlignBottom); + + for(auto* s : chart->series()) { + s->attachAxis(x_axis); + } + y_axis->setTitleText("x * f(...)"); chart->legend()->setVisible(true); chart->legend()->setAlignment(Qt::AlignBottom); + for (auto* series : chart->series()) { + if (auto* areaSeries = qobject_cast(series)) { + for (auto* marker : chart->legend()->markers(areaSeries)) { + marker->setVisible(false); + } + } + } + chartView->setChart(chart); } diff --git a/neopdf_gui/MainWindow.hpp b/neopdf_gui/MainWindow.hpp index fca89eb..e973bad 100644 --- a/neopdf_gui/MainWindow.hpp +++ b/neopdf_gui/MainWindow.hpp @@ -34,11 +34,12 @@ private slots: void onPlotButtonClicked(); void onAddSetButtonClicked(); void onXAxisVarChanged(int index); - void onCurrentSetChanged(QListWidgetItem *current, QListWidgetItem *previous); + void onSelectionSetChanged(); + void onClearSetsButtonClicked(); private: void setupUI(); - void updateParametersUI(const QString& setName); + void updateParametersUI(const QList& items); // Main layout QWidget *centralWidget; @@ -50,6 +51,7 @@ private slots: QVBoxLayout *setSelectionLayout; QListWidget *setListWidget; QPushButton *addSetButton; + QPushButton *clearSetsButton; QGroupBox *plotParamsGroup; QFormLayout *plotParamsLayout; From 275e9dbeeea55cb936242fea2fadca392a148d71 Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 12:13:06 +0100 Subject: [PATCH 04/10] Improve CMake configuration a bit --- neopdf_gui/CMakeLists.txt | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/neopdf_gui/CMakeLists.txt b/neopdf_gui/CMakeLists.txt index 9028d6f..df1fbf1 100644 --- a/neopdf_gui/CMakeLists.txt +++ b/neopdf_gui/CMakeLists.txt @@ -4,49 +4,49 @@ project(neopdf_gui LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Automatically run MOC, UIC, and RCC set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) -# Find Qt6 find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Charts) -# Find the neopdf_capi header directory +set(CARGO_C_INSTALL_PREFIX "$ENV{CARGO_C_INSTALL_PREFIX}") +if(DEFINED CARGO_C_INSTALL_PREFIX AND NOT CARGO_C_INSTALL_PREFIX STREQUAL "") + list(APPEND NEOPDF_CAPI_INCLUDE_SEARCH_PATHS "${CARGO_C_INSTALL_PREFIX}/include/neopdf_capi") + list(APPEND NEOPDF_CAPI_LIBRARY_SEARCH_PATHS "${CARGO_C_INSTALL_PREFIX}/lib") +else() + message(FATAL_ERROR "CARGO_C_INSTALL_PREFIX must be specified.") +endif() + find_path( NEOPDF_CAPI_INCLUDE_DIR NAMES NeoPDF.hpp neopdf_capi.h - PATHS /usr/local/include/neopdf_capi + PATHS ${NEOPDF_CAPI_INCLUDE_SEARCH_PATHS} NO_DEFAULT_PATH ) if(NOT NEOPDF_CAPI_INCLUDE_DIR) - message(FATAL_ERROR "Could not find NeoPDF.hpp/neopdf_capi.h") + message(FATAL_ERROR "Could not find NeoPDF.hpp/neopdf_capi.h.") endif() -# Find the neopdf_capi library find_library( NEOPDF_CAPI_LIBRARY NAMES neopdf_capi - PATHS /usr/local/lib + PATHS ${NEOPDF_CAPI_LIBRARY_SEARCH_PATHS} NO_DEFAULT_PATH ) if(NOT NEOPDF_CAPI_LIBRARY) - message(FATAL_ERROR "Could not find libneopdf_capi") + message(FATAL_ERROR "Could not find libneopdf_capi.") endif() -message(STATUS "Found neopdf_capi include dir: ${NEOPDF_CAPI_INCLUDE_DIR}") message(STATUS "Found neopdf_capi library: ${NEOPDF_CAPI_LIBRARY}") +message(STATUS "Found neopdf_capi include dir: ${NEOPDF_CAPI_INCLUDE_DIR}") -# --- End of finding neopdf_capi --- - -# Define the executable add_executable(neopdf_gui main.cpp MainWindow.cpp MainWindow.hpp ) -# Link against Qt6 libraries target_link_libraries(neopdf_gui PRIVATE Qt6::Core @@ -55,21 +55,17 @@ target_link_libraries(neopdf_gui Qt6::Charts ) -# Include directories target_include_directories(neopdf_gui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${NEOPDF_CAPI_INCLUDE_DIR} ) -# Link against the neopdf_capi library target_link_libraries(neopdf_gui PRIVATE ${NEOPDF_CAPI_LIBRARY} ) -# Add preprocessor definition to disable std::filesystem integration in Qt -# to solve macOS SDK compatibility issues with conda-forge compilers. target_compile_definitions(neopdf_gui PRIVATE QT_NO_FILESYSTEM) install(TARGETS neopdf_gui From 3dec1bdc8d032e4067d1c2be5ce9879a4affbdda Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 12:18:09 +0100 Subject: [PATCH 05/10] Update README --- neopdf_gui/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neopdf_gui/README.md b/neopdf_gui/README.md index 1e1a5d2..c9886c4 100644 --- a/neopdf_gui/README.md +++ b/neopdf_gui/README.md @@ -7,12 +7,12 @@ A C++/Qt6 application to plot and compare PDF sets using the `neopdf` library. - A C++ compiler (g++, clang, msvc) - CMake (version 3.16 or higher) - Qt6 (including the Charts module) -- Rust toolchain (for building the `neopdf_capi`) ## How to Build 1. **Configure with CMake:** - From the `neopdf_gui` directory, run: + First, specify the path where the NeoPDf C/C++-API is installed with the variable `CARGO_C_INSTALL_PREFIX`. + Then from the `neopdf_gui` directory, run: ```bash cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt/6.9.1/lib/cmake/Qt6 ``` From 9facdfa9cab4cf73894e794c86e5e9f06554e2ab Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 13:56:39 +0100 Subject: [PATCH 06/10] Format C++ in NeoPDF GUI --- neopdf_gui/MainWindow.cpp | 181 ++++++++++++++++++++++++-------------- neopdf_gui/MainWindow.hpp | 22 ++--- 2 files changed, 128 insertions(+), 75 deletions(-) diff --git a/neopdf_gui/MainWindow.cpp b/neopdf_gui/MainWindow.cpp index b76fe6f..44cb83e 100644 --- a/neopdf_gui/MainWindow.cpp +++ b/neopdf_gui/MainWindow.cpp @@ -1,22 +1,22 @@ -#include "MainWindow.hpp" -#include "NeoPDF.hpp" - -#include #include #include #include -#include +#include +#include +#include #include +#include +#include #include -#include -#include #include -#include -#include +#include -#include -#include #include +#include +#include + +#include "MainWindow.hpp" +#include "NeoPDF.hpp" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setupUI(); @@ -41,14 +41,18 @@ void MainWindow::setupUI() { setListWidget = new QListWidget(); setListWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); addSetButton = new QPushButton("Add PDF Set"); - connect(addSetButton, &QPushButton::clicked, this, &MainWindow::onAddSetButtonClicked); - connect(setListWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::onSelectionSetChanged); + connect(addSetButton, &QPushButton::clicked, this, + &MainWindow::onAddSetButtonClicked); + connect(setListWidget->selectionModel(), + &QItemSelectionModel::selectionChanged, this, + &MainWindow::onSelectionSetChanged); setSelectionLayout->addWidget(setListWidget); setSelectionLayout->addWidget(addSetButton); clearSetsButton = new QPushButton("Clear All"); setSelectionLayout->addWidget(clearSetsButton); - connect(clearSetsButton, &QPushButton::clicked, this, &MainWindow::onClearSetsButtonClicked); + connect(clearSetsButton, &QPushButton::clicked, this, + &MainWindow::onClearSetsButtonClicked); setSelectionGroup->setLayout(setSelectionLayout); // Plotting Parameters @@ -56,7 +60,8 @@ void MainWindow::setupUI() { plotParamsLayout = new QFormLayout(); xAxisVarCombo = new QComboBox(); - connect(xAxisVarCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &MainWindow::onXAxisVarChanged); + connect(xAxisVarCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &MainWindow::onXAxisVarChanged); pidCombo = new QComboBox(); pidCombo->addItem("g (21)", 21); @@ -74,18 +79,25 @@ void MainWindow::setupUI() { pidCombo->addItem("tbar (-6)", -6); pidCombo->setCurrentIndex(0); // Default to gluon - m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_NUCLEONS, "Nucleon (A)", nullptr, nullptr, false, 1.0, "1.0"}); - m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_ALPHAS, "alpha_s", nullptr, nullptr, false, 0.118, "0.118"}); - m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_XI, "xi", nullptr, nullptr, false, 0.0, "0.0"}); - m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_DELTA, "delta", nullptr, nullptr, false, 0.0, "0.0"}); - m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_KT, "kt", nullptr, nullptr, false, 0.0, "0.0"}); - m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_MOMENTUM, "x", nullptr, nullptr, false, 0.1, "0.1"}); - m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_SCALE, "Q2", nullptr, nullptr, false, 100.0, "100.0"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_NUCLEONS, "Nucleon (A)", nullptr, + nullptr, false, 1.0, "1.0"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_ALPHAS, "alpha_s", nullptr, + nullptr, false, 0.118, "0.118"}); + m_paramInfos.append( + {NEOPDF_SUBGRID_PARAMS_XI, "xi", nullptr, nullptr, false, 0.0, "0.0"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_DELTA, "delta", nullptr, nullptr, + false, 0.0, "0.0"}); + m_paramInfos.append( + {NEOPDF_SUBGRID_PARAMS_KT, "kt", nullptr, nullptr, false, 0.0, "0.0"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_MOMENTUM, "x", nullptr, nullptr, + false, 0.1, "0.1"}); + m_paramInfos.append({NEOPDF_SUBGRID_PARAMS_SCALE, "Q2", nullptr, nullptr, + false, 100.0, "100.0"}); plotParamsLayout->addRow("X-axis variable:", xAxisVarCombo); plotParamsLayout->addRow("PID:", pidCombo); - for (auto& info : m_paramInfos) { + for (auto &info : m_paramInfos) { info.widget = new QLineEdit(info.default_text); info.label = new QLabel("Fixed " + info.name + " value:"); plotParamsLayout->addRow(info.label, info.widget); @@ -108,7 +120,8 @@ void MainWindow::setupUI() { plotParamsGroup->setLayout(plotParamsLayout); plotButton = new QPushButton("Plot"); - connect(plotButton, &QPushButton::clicked, this, &MainWindow::onPlotButtonClicked); + connect(plotButton, &QPushButton::clicked, this, + &MainWindow::onPlotButtonClicked); controlsLayout->addWidget(setSelectionGroup); controlsLayout->addWidget(plotParamsGroup); @@ -124,9 +137,11 @@ void MainWindow::setupUI() { void MainWindow::onAddSetButtonClicked() { bool ok; - QString setName = QInputDialog::getText(this, tr("Add PDF Set"), tr("PDF set name:"), QLineEdit::Normal, "", &ok); + QString setName = + QInputDialog::getText(this, tr("Add PDF Set"), tr("PDF set name:"), + QLineEdit::Normal, "", &ok); if (ok && !setName.isEmpty()) { - QListWidgetItem* item = new QListWidgetItem(setName); + QListWidgetItem *item = new QListWidgetItem(setName); item->setData(Qt::UserRole, setName); setListWidget->addItem(item); setListWidget->setCurrentItem(item); @@ -137,9 +152,9 @@ void MainWindow::onSelectionSetChanged() { updateParametersUI(setListWidget->selectedItems()); } -void MainWindow::updateParametersUI(const QList& items) { +void MainWindow::updateParametersUI(const QList &items) { if (items.isEmpty()) { - for (auto& info : m_paramInfos) { + for (auto &info : m_paramInfos) { info.active = false; info.widget->setVisible(false); info.label->setVisible(false); @@ -151,19 +166,21 @@ void MainWindow::updateParametersUI(const QList& items) { QSet commonParams; bool first = true; - for (const auto& item : items) { + for (const auto &item : items) { QSet itemParams; try { - neopdf::NeoPDF pdf(item->data(Qt::UserRole).toString().toStdString(), 0); - for (const auto& info : m_paramInfos) { + neopdf::NeoPDF pdf( + item->data(Qt::UserRole).toString().toStdString(), 0); + for (const auto &info : m_paramInfos) { auto range = pdf.param_range(info.id); if (range[0] < range[1]) { itemParams.insert(info.id); } } - } catch (const std::exception& e) { - QMessageBox::warning(this, "Error Loading Set", "Could not inspect " + item->text() + ":\n" + e.what()); - // Skip this set for determining common params + } catch (const std::exception &e) { + QMessageBox::warning(this, "Error Loading Set", + "Could not inspect " + item->text() + ":\n" + + e.what()); continue; } @@ -178,7 +195,7 @@ void MainWindow::updateParametersUI(const QList& items) { xAxisVarCombo->blockSignals(true); xAxisVarCombo->clear(); - for (auto& info : m_paramInfos) { + for (auto &info : m_paramInfos) { info.active = commonParams.contains(info.id); info.widget->setVisible(info.active); info.label->setVisible(info.active); @@ -192,11 +209,13 @@ void MainWindow::updateParametersUI(const QList& items) { } void MainWindow::onXAxisVarChanged(int index) { - if (index < 0) return; + if (index < 0) + return; - auto selected_id = static_cast(xAxisVarCombo->itemData(index).toInt()); + auto selected_id = static_cast( + xAxisVarCombo->itemData(index).toInt()); - for (auto& info : m_paramInfos) { + for (auto &info : m_paramInfos) { if (info.active) { info.widget->setEnabled(info.id != selected_id); } @@ -209,56 +228,81 @@ void MainWindow::onClearSetsButtonClicked() { } void MainWindow::onPlotButtonClicked() { - QList selectedItems = setListWidget->selectedItems(); + QList selectedItems = setListWidget->selectedItems(); if (selectedItems.isEmpty()) { - QMessageBox::warning(this, "No PDF Set", "Please select one or more PDF sets to plot."); + QMessageBox::warning(this, "No PDF Set", + "Please select one or more PDF sets to plot."); return; } if (xAxisVarCombo->currentIndex() < 0) { - QMessageBox::warning(this, "No variable selected", "No common variables to plot. Please select sets with compatible kinematics."); + QMessageBox::warning(this, "No variable selected", + "No common variables to plot. Please select " + "sets with compatible kinematics."); return; } bool ok; - auto xAxisVarId = static_cast(xAxisVarCombo->currentData().toInt()); + auto xAxisVarId = + static_cast(xAxisVarCombo->currentData().toInt()); int pid = pidCombo->currentData().toInt(); QMap fixed_values; - for (const auto& info : m_paramInfos) { + for (const auto &info : m_paramInfos) { if (info.active && info.id != xAxisVarId) { double val = info.widget->text().toDouble(&ok); - if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid value for " + info.name); return; } + if (!ok) { + QMessageBox::warning(this, "Invalid Input", + "Invalid value for " + info.name); + return; + } fixed_values[info.id] = val; } } double range_min = rangeMinEdit->text().toDouble(&ok); - if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid range min value."); return; } + if (!ok) { + QMessageBox::warning(this, "Invalid Input", "Invalid range min value."); + return; + } double range_max = rangeMaxEdit->text().toDouble(&ok); - if (!ok) { QMessageBox::warning(this, "Invalid Input", "Invalid range max value."); return; } + if (!ok) { + QMessageBox::warning(this, "Invalid Input", "Invalid range max value."); + return; + } int n_points = pointsEdit->text().toInt(&ok); - if (!ok || n_points <= 1) { QMessageBox::warning(this, "Invalid Input", "Number of points must be an integer > 1."); return; } + if (!ok || n_points <= 1) { + QMessageBox::warning(this, "Invalid Input", + "Number of points must be an integer > 1."); + return; + } bool isXLog = xAxisLogCheck->isChecked(); if (isXLog && range_min <= 0.0) { - QMessageBox::warning(this, "Invalid Input", "Minimum range for logarithmic X-axis must be positive."); return; + QMessageBox::warning( + this, "Invalid Input", + "Minimum range for logarithmic X-axis must be positive."); + return; } bool isYLog = yAxisLogCheck->isChecked(); auto *chart = new QChart(); - auto *y_axis = isYLog ? static_cast(new QLogValueAxis()) : static_cast(new QValueAxis()); + auto *y_axis = isYLog ? static_cast(new QLogValueAxis()) + : static_cast(new QValueAxis()); chart->addAxis(y_axis, Qt::AlignLeft); - QList colors = {Qt::blue, Qt::red, Qt::green, Qt::cyan, Qt::magenta, Qt::yellow, Qt::darkGray}; + QList colors = {Qt::blue, Qt::red, Qt::green, Qt::cyan, + Qt::magenta, Qt::yellow, Qt::darkGray}; int color_idx = 0; - for (auto* item : selectedItems) { + for (auto *item : selectedItems) { QString setName = item->data(Qt::UserRole).toString(); - neopdf::NeoPDFs* pdfs = nullptr; + neopdf::NeoPDFs *pdfs = nullptr; try { pdfs = new neopdf::NeoPDFs(setName.toStdString()); - } catch (const std::exception& e) { - QMessageBox::warning(this, "Plotting Error", "Could not load " + setName + ":\n" + e.what()); + } catch (const std::exception &e) { + QMessageBox::warning(this, "Plotting Error", + "Could not load " + setName + ":\n" + + e.what()); continue; } @@ -267,15 +311,19 @@ void MainWindow::onPlotButtonClicked() { auto *upper_series = new QLineSeries(); auto *lower_series = new QLineSeries(); - double step = isXLog ? std::pow(range_max / range_min, 1.0 / (n_points - 1)) : (range_max - range_min) / (n_points - 1); + double step = + isXLog ? std::pow(range_max / range_min, 1.0 / (n_points - 1)) + : (range_max - range_min) / (n_points - 1); for (int i = 0; i < n_points; ++i) { - double x_val = isXLog ? range_min * std::pow(step, i) : range_min + i * step; + double x_val = + isXLog ? range_min * std::pow(step, i) : range_min + i * step; std::vector params; - for (const auto& info : m_paramInfos) { + for (const auto &info : m_paramInfos) { if (info.active) { - params.push_back(info.id == xAxisVarId ? x_val : fixed_values[info.id]); + params.push_back( + info.id == xAxisVarId ? x_val : fixed_values[info.id]); } } @@ -287,7 +335,11 @@ void MainWindow::onPlotButtonClicked() { double sum = std::accumulate(results.begin(), results.end(), 0.0); double mean = sum / results.size(); - double sq_sum = std::accumulate(results.begin(), results.end(), 0.0, [mean](double acc, double val) { return acc + (val - mean) * (val - mean); }); + double sq_sum = + std::accumulate(results.begin(), results.end(), 0.0, + [mean](double acc, double val) { + return acc + (val - mean) * (val - mean); + }); double std_dev = std::sqrt(sq_sum / results.size()); mean_series->append(x_val, mean); @@ -318,11 +370,12 @@ void MainWindow::onPlotButtonClicked() { color_idx++; } - auto *x_axis = isXLog ? static_cast(new QLogValueAxis()) : static_cast(new QValueAxis()); + auto *x_axis = isXLog ? static_cast(new QLogValueAxis()) + : static_cast(new QValueAxis()); x_axis->setTitleText(xAxisVarCombo->currentText()); chart->addAxis(x_axis, Qt::AlignBottom); - for(auto* s : chart->series()) { + for (auto *s : chart->series()) { s->attachAxis(x_axis); } @@ -330,9 +383,9 @@ void MainWindow::onPlotButtonClicked() { chart->legend()->setVisible(true); chart->legend()->setAlignment(Qt::AlignBottom); - for (auto* series : chart->series()) { - if (auto* areaSeries = qobject_cast(series)) { - for (auto* marker : chart->legend()->markers(areaSeries)) { + for (auto *series : chart->series()) { + if (auto *areaSeries = qobject_cast(series)) { + for (auto *marker : chart->legend()->markers(areaSeries)) { marker->setVisible(false); } } diff --git a/neopdf_gui/MainWindow.hpp b/neopdf_gui/MainWindow.hpp index e973bad..6654021 100644 --- a/neopdf_gui/MainWindow.hpp +++ b/neopdf_gui/MainWindow.hpp @@ -1,9 +1,9 @@ #pragma once #include -#include -#include #include +#include +#include #include "neopdf_capi.h" // For NeopdfSubgridParams enum @@ -16,8 +16,8 @@ class QLineEdit; struct ParamInfo { NeopdfSubgridParams id; QString name; - QLineEdit* widget = nullptr; - QLabel* label = nullptr; + QLineEdit *widget = nullptr; + QLabel *label = nullptr; bool active = false; double default_val = 0.0; QString default_text; @@ -26,20 +26,20 @@ struct ParamInfo { class MainWindow : public QMainWindow { Q_OBJECT -public: + public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); -private slots: + private slots: void onPlotButtonClicked(); void onAddSetButtonClicked(); void onXAxisVarChanged(int index); void onSelectionSetChanged(); void onClearSetsButtonClicked(); -private: + private: void setupUI(); - void updateParametersUI(const QList& items); + void updateParametersUI(const QList &items); // Main layout QWidget *centralWidget; @@ -60,9 +60,9 @@ private slots: QVector m_paramInfos; - QLineEdit* rangeMinEdit; - QLineEdit* rangeMaxEdit; - QLineEdit* pointsEdit; + QLineEdit *rangeMinEdit; + QLineEdit *rangeMaxEdit; + QLineEdit *pointsEdit; QCheckBox *xAxisLogCheck; QCheckBox *yAxisLogCheck; QPushButton *plotButton; From 088a481ef6858732bfb4f04bc9d943afaac65dab Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 13:57:39 +0100 Subject: [PATCH 07/10] Tiny cosmetic update --- neopdf_gui/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neopdf_gui/main.cpp b/neopdf_gui/main.cpp index 5b2b80a..c1dafc9 100644 --- a/neopdf_gui/main.cpp +++ b/neopdf_gui/main.cpp @@ -1,7 +1,8 @@ #include -#include "MainWindow.hpp" #include +#include "MainWindow.hpp" + int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow mainWindow; From aca2b0c48414e48873f6d43b33ac31d1db48e6cd Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 17:41:01 +0100 Subject: [PATCH 08/10] Minor clean up --- neopdf_gui/MainWindow.cpp | 6 +++--- neopdf_gui/MainWindow.hpp | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/neopdf_gui/MainWindow.cpp b/neopdf_gui/MainWindow.cpp index 44cb83e..a6ee2fb 100644 --- a/neopdf_gui/MainWindow.cpp +++ b/neopdf_gui/MainWindow.cpp @@ -20,7 +20,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setupUI(); - setWindowTitle("NeoPDF Plotter"); + setWindowTitle("NeoPDF"); resize(1200, 800); } @@ -40,7 +40,7 @@ void MainWindow::setupUI() { setSelectionLayout = new QVBoxLayout(); setListWidget = new QListWidget(); setListWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); - addSetButton = new QPushButton("Add PDF Set"); + addSetButton = new QPushButton("Add Set"); connect(addSetButton, &QPushButton::clicked, this, &MainWindow::onAddSetButtonClicked); connect(setListWidget->selectionModel(), @@ -379,7 +379,7 @@ void MainWindow::onPlotButtonClicked() { s->attachAxis(x_axis); } - y_axis->setTitleText("x * f(...)"); + y_axis->setTitleText("xf"); chart->legend()->setVisible(true); chart->legend()->setAlignment(Qt::AlignBottom); diff --git a/neopdf_gui/MainWindow.hpp b/neopdf_gui/MainWindow.hpp index 6654021..c953c2d 100644 --- a/neopdf_gui/MainWindow.hpp +++ b/neopdf_gui/MainWindow.hpp @@ -5,9 +5,8 @@ #include #include -#include "neopdf_capi.h" // For NeopdfSubgridParams enum +#include "neopdf_capi.h" -// Forward declarations class QFormLayout; class QLabel; class QListWidgetItem; @@ -41,12 +40,10 @@ class MainWindow : public QMainWindow { void setupUI(); void updateParametersUI(const QList &items); - // Main layout QWidget *centralWidget; QHBoxLayout *mainLayout; QVBoxLayout *controlsLayout; - // Controls QGroupBox *setSelectionGroup; QVBoxLayout *setSelectionLayout; QListWidget *setListWidget; @@ -67,6 +64,5 @@ class MainWindow : public QMainWindow { QCheckBox *yAxisLogCheck; QPushButton *plotButton; - // Plotting QChartView *chartView; }; From 4353c1d30cfb198138b0cf23a1158faf4e3f877e Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 18:58:25 +0100 Subject: [PATCH 09/10] Package GUI application --- .github/workflows/release.yml | 73 ++++++++++++++++++++++++++++++++++- maintainer/make-release.sh | 4 ++ neopdf_gui/CMakeLists.txt | 35 ++++++++++++++++- 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 04eff81..f36805e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -151,7 +151,7 @@ jobs: path: neopdf_capi-${{ matrix.target }}.tar.gz publish-release: - needs: [capi-macos, cli-macos, capi-linux, cli-linux] + needs: [capi-macos, cli-macos, capi-linux, cli-linux, gui-macos, gui-linux] runs-on: ubuntu-latest if: "startsWith(github.ref, 'refs/tags/')" steps: @@ -166,6 +166,77 @@ jobs: find artifacts -name 'neopdf_*' ! -name '*.whl' -type f -exec gh release upload v${version} {} + gh release edit v${version} --draft=false + gui-macos: + needs: capi-macos + strategy: + matrix: + include: + - os: macos-13 + target: x86_64-apple-darwin + - os: macos-14 + target: aarch64-apple-darwin + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Install Qt + run: brew install qt6 + - name: Set Qt path + run: echo "CMAKE_PREFIX_PATH=$(brew --prefix qt6)" >> $GITHUB_ENV + - name: Download capi artifact + uses: actions/download-artifact@v4 + with: + name: neopdf_capi-${{ matrix.target }} + path: neopdf_capi_artifact + - name: Build and package + run: | + mkdir capi_install + tar -xzf neopdf_capi_artifact/neopdf_capi-${{ matrix.target }}.tar.gz -C capi_install + export CARGO_C_INSTALL_PREFIX=$(pwd)/capi_install + cmake -S neopdf_gui -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$(pwd)/install -DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH + cmake --build build --config Release + cmake --install build + cd install + cpack + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: neopdf_gui-${{ matrix.target }} + path: install/*.dmg + + gui-linux: + needs: capi-linux + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64-unknown-linux-gnu] + steps: + - uses: actions/checkout@v4 + - name: Install Qt and build essentials + run: | + sudo apt-get update + sudo apt-get install -y build-essential qt6-base-dev qt6-charts-dev + - name: Download capi artifact + uses: actions/download-artifact@v4 + with: + name: neopdf_capi-${{ matrix.target }} + path: neopdf_capi_artifact + - name: Build and package + run: | + mkdir capi_install + tar -xzf neopdf_capi_artifact/neopdf_capi-${{ matrix.target }}.tar.gz -C capi_install + export CARGO_C_INSTALL_PREFIX=$(pwd)/capi_install + cmake -S neopdf_gui -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$(pwd)/install + cmake --build build --config Release + cmake --install build + cd install + cpack + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: neopdf_gui-${{ matrix.target }} + path: install/*.tar.gz + + publish-crates: runs-on: ubuntu-latest container: ghcr.io/qcdlab/neopdf-container:latest diff --git a/maintainer/make-release.sh b/maintainer/make-release.sh index aa3227b..21e162f 100755 --- a/maintainer/make-release.sh +++ b/maintainer/make-release.sh @@ -61,6 +61,10 @@ sed -i \ Cargo.toml git add Cargo.toml +# update GUI version +sed -i "s/^project(neopdf_gui VERSION .*)/project(neopdf_gui VERSION ${version})/" neopdf_gui/CMakeLists.txt +git add neopdf_gui/CMakeLists.txt + echo ">>> Updating Cargo.lock ..." # update explicit version for `neopdf_tmdlib` in `neopdf_cli` diff --git a/neopdf_gui/CMakeLists.txt b/neopdf_gui/CMakeLists.txt index df1fbf1..d24d60b 100644 --- a/neopdf_gui/CMakeLists.txt +++ b/neopdf_gui/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(neopdf_gui LANGUAGES CXX) +project(neopdf_gui VERSION 0.1.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -47,6 +47,12 @@ add_executable(neopdf_gui MainWindow.hpp ) +if(APPLE) + set_target_properties(neopdf_gui PROPERTIES + MACOSX_BUNDLE TRUE + ) +endif() + target_link_libraries(neopdf_gui PRIVATE Qt6::Core @@ -69,5 +75,32 @@ target_link_libraries(neopdf_gui target_compile_definitions(neopdf_gui PRIVATE QT_NO_FILESYSTEM) install(TARGETS neopdf_gui + BUNDLE DESTINATION . RUNTIME DESTINATION bin ) + +if(WIN32) + set(QT_DEPLOY_TOOL windeployqt) +elseif(APPLE) + set(QT_DEPLOY_TOOL macdeployqt) +endif() + +if(QT_DEPLOY_TOOL) + find_program(QT_DEPLOY_TOOL_PATH ${QT_DEPLOY_TOOL} HINTS ${Qt6_BIN_DIR}) + if(QT_DEPLOY_TOOL_PATH) + if(WIN32) + set(APP_PATH "\"${CMAKE_INSTALL_PREFIX}/bin/neopdf_gui.exe\"") + elseif(APPLE) + set(APP_PATH "\"${CMAKE_INSTALL_PREFIX}/neopdf_gui.app\"") + endif() + install(CODE "execute_process(COMMAND ${QT_DEPLOY_TOOL_PATH} ${APP_PATH})") + endif() +endif() + +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) +set(CPACK_GENERATOR "TGZ") +if(APPLE) + set(CPACK_GENERATOR "DragNDrop") +endif() + +include(CPack) From debbeb13daed59486eaeefc94363bc4e37f92b7a Mon Sep 17 00:00:00 2001 From: Tanjona Rabemananjara Date: Fri, 19 Dec 2025 20:29:25 +0100 Subject: [PATCH 10/10] Fix README in GUI --- neopdf_gui/README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/neopdf_gui/README.md b/neopdf_gui/README.md index c9886c4..f9abf73 100644 --- a/neopdf_gui/README.md +++ b/neopdf_gui/README.md @@ -7,26 +7,22 @@ A C++/Qt6 application to plot and compare PDF sets using the `neopdf` library. - A C++ compiler (g++, clang, msvc) - CMake (version 3.16 or higher) - Qt6 (including the Charts module) +- C/C++ NeoPDF APIS (see documentation for installation) ## How to Build 1. **Configure with CMake:** - First, specify the path where the NeoPDf C/C++-API is installed with the variable `CARGO_C_INSTALL_PREFIX`. - Then from the `neopdf_gui` directory, run: + First, specify the path where the NeoPDf C/C++-APIs are installed with the + variable `CARGO_C_INSTALL_PREFIX`. Then from the `neopdf_gui` directory, run: + ```bash + cmake -B build -DCMAKE_BUILD_TYPE=Release + ``` + On macOS, you might need to manually specify the path to Qt as follows: ```bash cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt/6.9.1/lib/cmake/Qt6 ``` - This will configure the project and also trigger the build of the `neopdf_capi` Rust library. 2. **Build the application:** ```bash cmake --build build ``` - -## How to Run - -After a successful build, the executable will be located in the `build` directory. - -```bash -./build/neopdf_gui -```