Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
shell: bash -el {0}
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
sudo apt install xvfb herbstluftwm libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils
sudo apt install libegl1 xvfb herbstluftwm libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils
sudo /sbin/start-stop-daemon --start --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset
sleep 3
sudo /sbin/start-stop-daemon --start --pidfile /tmp/custom_herbstluftwm_99.pid --make-pidfile --background --exec /usr/bin/herbstluftwm
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

Badger is a PyQt5 GUI/CLI optimization tool for particle accelerators. It wraps the [Xopt](https://github.com/xopt-org/Xopt) library and uses a plugin system for environments and interfaces. The package name on PyPI/conda is `badger-opt`, but the Python import is `badger`.
Badger is a qtpy GUI/CLI optimization tool for particle accelerators. It wraps the [Xopt](https://github.com/xopt-org/Xopt) library and uses a plugin system for environments and interfaces. The package name on PyPI/conda is `badger-opt`, but the Python import is `badger`.

## Development Setup

Expand All @@ -11,7 +11,7 @@ pip install -e ".[dev]"
pre-commit install
```

Python 3.11–3.13 only. PyQt5 is a hard dependency even for non-GUI code paths (`badger.utils` imports `PyQt5.QtWidgets` at the top level).
Python 3.11–3.13 only. qtpy is a hard dependency even for non-GUI code paths (`badger.utils` imports `qtpy.QtWidgets` at the top level).

## Running Tests

Expand Down Expand Up @@ -144,7 +144,7 @@ Temporary runs (pre-archive) go to `BADGER_ARCHIVE_ROOT/.tmp/` with `.tmp-Badger

## GUI structure

The GUI is a single-window PyQt5 app (`BadgerMainWindow`) with:
The GUI is a single-window qtpy app (`BadgerMainWindow`) with:
- One page: `home_page.py` (contains routine editor + run monitor)
- Components in `gui/components/` — the naming doesn't always match the visual element (see `GUI_GUIDE.md` for the mapping)
- Dialogs in `gui/windows/`
Expand All @@ -170,4 +170,4 @@ A pre-commit hook (`check-module-docstrings`) enforces presence. Empty `__init__

4. **The `db.py` module is semi-deprecated** — it requires `BADGER_DB_ROOT` config which is not in the default `BadgerConfig` model. The `x-test_db.py` and `x-test_routine_id.py` files test this functionality but are excluded from normal test runs.

5. **`utils.py` has Qt dependencies** — `BlockSignalsContext` and related utilities import from `PyQt5.QtWidgets` at the module level, so `badger.utils` cannot be imported without PyQt5 installed.
5. **`utils.py` has Qt dependencies** — `BlockSignalsContext` and related utilities import from `qtpy.QtWidgets` at the module level, so `badger.utils` cannot be imported without PyQt5 installed.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ classifiers = [
requires-python = ">=3.11, <3.14"
dependencies = [
"coolname",
"pyqt5",
"qtpy",
"pyqtgraph",
"qdarkstyle>=3.0",
"pillow",
Expand All @@ -37,6 +37,7 @@ version_file = "src/badger/_version.py"

[project.optional-dependencies]
dev = [
"pyside6",
"pytest",
"pytest-cov",
"pytest-qt",
Expand Down
2 changes: 1 addition & 1 deletion src/badger/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
with the traceback when raised inside the GUI. Subclasses cover config issues,
database errors, plugin failures, and optimization stop signals."""

from PyQt5.QtWidgets import QMessageBox
from qtpy.QtWidgets import QMessageBox
import traceback
import sys

Expand Down
6 changes: 3 additions & 3 deletions src/badger/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import signal
import sys
import time
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QFont, QIcon
from PyQt5 import QtCore
from qtpy.QtWidgets import QApplication
from qtpy.QtGui import QFont, QIcon
from qtpy import QtCore
from qdarkstyle import load_stylesheet, LightPalette, DarkPalette

from badger.settings import init_settings
Expand Down
38 changes: 19 additions & 19 deletions src/badger/gui/components/action_bar.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Toolbar with run-control buttons (start, pause, stop), logbook submission,
docs access, and the extensions palette launcher."""

from PyQt5.QtWidgets import QWidget, QHBoxLayout
from PyQt5.QtWidgets import QToolButton, QMenu, QAction
from PyQt5.QtGui import QIcon, QFont
from PyQt5.QtCore import pyqtSignal, QSize
from qtpy.QtWidgets import QWidget, QHBoxLayout
from qtpy.QtWidgets import QToolButton, QMenu
from qtpy.QtGui import QAction, QIcon, QFont
from qtpy.QtCore import Signal, QSize
from importlib import resources
from badger.gui.utils import create_button
from badger.gui.windows.docs_window import BadgerDocsWindow
Expand Down Expand Up @@ -88,21 +88,21 @@


class BadgerActionBar(QWidget):
sig_start = pyqtSignal()
sig_start_until = pyqtSignal()
sig_stop = pyqtSignal()

sig_delete_run = pyqtSignal()
sig_logbook = pyqtSignal()
sig_reset_env = pyqtSignal()
sig_jump_to_optimal = pyqtSignal()
sig_dial_in = pyqtSignal()
sig_ctrl = pyqtSignal(bool)
sig_open_extensions_palette = pyqtSignal()

sig_save_checkpoint = pyqtSignal()
sig_edit_checkpoint = pyqtSignal()
sig_load_checkpoint = pyqtSignal()
sig_start = Signal()
sig_start_until = Signal()
sig_stop = Signal()

sig_delete_run = Signal()
sig_logbook = Signal()
sig_reset_env = Signal()
sig_jump_to_optimal = Signal()
sig_dial_in = Signal()
sig_ctrl = Signal(bool)
sig_open_extensions_palette = Signal()

sig_save_checkpoint = Signal()
sig_edit_checkpoint = Signal()
sig_load_checkpoint = Signal()

def __init__(self, parent=None):
super().__init__(parent)
Expand Down
8 changes: 4 additions & 4 deletions src/badger/gui/components/analysis_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from typing import Optional, cast
import logging

from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QDialog, QVBoxLayout
from PyQt5.QtGui import QCloseEvent
from qtpy.QtCore import Signal
from qtpy.QtWidgets import QDialog, QVBoxLayout
from qtpy.QtGui import QCloseEvent
from badger.gui.components.analysis_widget import AnalysisWidget
from badger.gui.components.bo_visualizer.bo_widget import BOPlotWidget
from badger.gui.components.pf_viewer.pf_widget import ParetoFrontWidget
Expand All @@ -20,7 +20,7 @@


class AnalysisExtension(QDialog):
window_closed = pyqtSignal(object)
window_closed = Signal(object)
generator_type = Generator
widget = AnalysisWidget

Expand Down
2 changes: 1 addition & 1 deletion src/badger/gui/components/analysis_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from collections.abc import Callable
from typing import Any, Optional

from PyQt5.QtWidgets import QDialog
from qtpy.QtWidgets import QDialog
from badger.gui.components.extension_utilities import HandledException
from badger.routine import Routine
from xopt import Generator
Expand Down
21 changes: 10 additions & 11 deletions src/badger/gui/components/archive_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@
when building a routine."""

import logging
from typing import List
from PyQt5.QtGui import QDrag, QKeyEvent
from PyQt5.QtCore import (
from typing import List, Any
from qtpy.QtGui import QDrag, QKeyEvent
from qtpy.QtCore import (
QAbstractTableModel,
QMimeData,
QModelIndex,
QObject,
Qt,
QVariant,
pyqtSignal,
Signal,
)
from PyQt5.QtWidgets import (
from qtpy.QtWidgets import (
QAbstractItemView,
QHBoxLayout,
QHeaderView,
Expand Down Expand Up @@ -58,17 +57,17 @@ def columnCount(self, parent: QObject) -> int:
return 0
return len(self.column_names)

def data(self, index: QModelIndex, role: int) -> QVariant:
def data(self, index: QModelIndex, role: int) -> str | None:
"""Return the data for the associated role. Currently only supporting DisplayRole."""
if not index.isValid():
return QVariant()
return None

if role != Qt.DisplayRole:
return QVariant()
return None

return self.results_list[index.row()]

def headerData(self, section, orientation, role=Qt.DisplayRole) -> QVariant:
def headerData(self, section, orientation, role=Qt.DisplayRole) -> Any:
"""Return data associated with the header"""
if role != Qt.DisplayRole:
return super().headerData(section, orientation, role)
Expand Down Expand Up @@ -121,7 +120,7 @@ class ArchiveSearchWidget(QWidget):
The parent item of this widget
"""

append_variables_requested = pyqtSignal(str)
append_variables_requested = Signal(str)

def __init__(self, environment, parent: QObject = None) -> None:
super().__init__(parent=parent)
Expand Down
6 changes: 3 additions & 3 deletions src/badger/gui/components/bo_visualizer/bo_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
"""

from typing import Optional, cast
from PyQt5.QtWidgets import (
from qtpy.QtWidgets import (
QHBoxLayout,
QWidget,
QVBoxLayout,
QMessageBox,
QTableWidgetItem,
)
from PyQt5.QtWidgets import QSizePolicy
from qtpy.QtWidgets import QSizePolicy

from badger.gui.components.bo_visualizer.types import ConfigurableOptions
from badger.gui.components.extension_utilities import (
Expand All @@ -29,7 +29,7 @@
from xopt.generator import Generator
from badger.gui.components.bo_visualizer.ui_components import UIComponents
from badger.gui.components.bo_visualizer.plotting_area import PlottingArea
from PyQt5.QtCore import Qt
from qtpy.QtCore import Qt
from xopt.generators.bayesian.bayesian_generator import BayesianGenerator
from xopt.vocs import select_best
from badger.gui.components.analysis_widget import AnalysisWidget
Expand Down
2 changes: 1 addition & 1 deletion src/badger/gui/components/bo_visualizer/plotting_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
)
from matplotlib.figure import Figure
from matplotlib.axes import Axes
from PyQt5.QtWidgets import QVBoxLayout, QWidget
from qtpy.QtWidgets import QVBoxLayout, QWidget
from badger.utils import BlockSignalsContext
from xopt.generators.bayesian.visualize import (
visualize_generator_model,
Expand Down
4 changes: 2 additions & 2 deletions src/badger/gui/components/bo_visualizer/ui_components.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Control panel for the BO visualizer — variable selectors, reference
point table, grid resolution, and plot option checkboxes."""

from PyQt5.QtWidgets import (
from qtpy.QtWidgets import (
QVBoxLayout,
QHBoxLayout,
QComboBox,
Expand All @@ -14,7 +14,7 @@
QCheckBox,
QHeaderView,
)
from PyQt5.QtCore import Qt
from qtpy.QtCore import Qt

from gest_api.vocs import BaseVariable
from badger.gui.components.bo_visualizer.types import ConfigurableOptions
Expand Down
6 changes: 3 additions & 3 deletions src/badger/gui/components/bounds_preview.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Widget for visualizing hard bounds, preview scan bounds, and current value."""

from PyQt5.QtCore import Qt, QRectF
from PyQt5.QtGui import QColor, QPainter, QPen, QBrush, QPaintEvent
from PyQt5.QtWidgets import QWidget
from qtpy.QtCore import Qt, QRectF
from qtpy.QtGui import QColor, QPainter, QPen, QBrush, QPaintEvent
from qtpy.QtWidgets import QWidget


class BoundsPreviewBar(QWidget):
Expand Down
10 changes: 5 additions & 5 deletions src/badger/gui/components/collapsible_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
group environment config, generator parameters, etc. without taking
up permanent screen space."""

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QLayout
from qtpy import QtCore, QtGui, QtWidgets
from qtpy.QtWidgets import QLayout


stylesheet_toolbutton = """
Expand All @@ -16,7 +16,7 @@

# https://stackoverflow.com/a/56293688
class ScrollArea(QtWidgets.QScrollArea):
resized = QtCore.pyqtSignal()
resized = QtCore.Signal()

def resizeEvent(self, e):
self.resized.emit()
Expand Down Expand Up @@ -76,7 +76,7 @@ def __init__(self, parent=None, title="", duration=100, tooltip=None):
QtCore.QPropertyAnimation(self.content_area, b"maximumHeight")
)

@QtCore.pyqtSlot()
@QtCore.Slot()
def start_animation(self):
checked = self.toggle_button.isChecked()
arrow_type = QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow
Expand All @@ -97,7 +97,7 @@ def expand(self):
def updateContentLayout(self):
if (
self.toggle_button.isChecked()
and self.toggle_animation.state() != self.toggle_animation.Running
and self.toggle_animation.state() != QtCore.QAbstractAnimation.State.Running
):
content_height = self.content_area.layout().sizeHint().height()
self.setMinimumHeight(self.collapsed_height + content_height)
Expand Down
2 changes: 1 addition & 1 deletion src/badger/gui/components/con_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
reordering."""

from typing import Any
from PyQt5.QtWidgets import (
from qtpy.QtWidgets import (
QCheckBox,
QComboBox,
QStyledItemDelegate,
Expand Down
6 changes: 3 additions & 3 deletions src/badger/gui/components/constraint_item.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
"""Creates a single constraint row widget: observable selector, relation
combo (>, <, =), threshold spinbox, criticality checkbox, and remove button."""

from PyQt5.QtWidgets import (
from qtpy.QtWidgets import (
QHBoxLayout,
QPushButton,
QWidget,
QDoubleSpinBox,
QAbstractSpinBox,
)
from PyQt5.QtWidgets import QComboBox, QCheckBox, QStyledItemDelegate
from PyQt5.QtCore import Qt
from qtpy.QtWidgets import QComboBox, QCheckBox, QStyledItemDelegate
from qtpy.QtCore import Qt
from badger.gui.utils import (
MouseWheelWidgetAdjustmentGuard,
NoHoverFocusComboBox,
Expand Down
6 changes: 3 additions & 3 deletions src/badger/gui/components/create_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from multiprocessing import Event, Pipe, Process, Queue

from PyQt5.QtCore import pyqtSignal, QObject
from qtpy.QtCore import Signal, QObject

from badger.settings import init_settings
from badger.core_subprocess import run_routine_subprocess
Expand All @@ -22,8 +22,8 @@ class CreateProcess(QObject):
The new process will be started, but will be holding until the wait_event is set.
"""

finished = pyqtSignal()
subprocess_prepared = pyqtSignal(object)
finished = Signal()
subprocess_prepared = Signal(object)

def create_subprocess(self) -> None:
"""
Expand Down
6 changes: 3 additions & 3 deletions src/badger/gui/components/data_panel.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""Panel for viewing and managing pre-loaded optimization data. Lets
users load data from archived runs or clear the buffer before starting."""

from PyQt5.QtWidgets import (
from qtpy.QtWidgets import (
QVBoxLayout,
QHBoxLayout,
QPushButton,
QWidget,
QMessageBox,
)
import pandas as pd
from PyQt5.QtWidgets import QGroupBox, QCheckBox, QLabel
from PyQt5.QtCore import Qt
from qtpy.QtWidgets import QGroupBox, QCheckBox, QLabel
from qtpy.QtCore import Qt
from badger.gui.components.data_table import (
TableWithCopy,
)
Expand Down
Loading
Loading