Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plotting widget #55

Merged
merged 2 commits into from
Nov 7, 2024
Merged
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
109 changes: 108 additions & 1 deletion rascal2/core/commands.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""File for Qt commands."""

import copy
from enum import IntEnum, unique
from typing import Callable
from typing import Callable, Union

import RATapi
from PyQt6 import QtGui
from RATapi import ClassList

Expand Down Expand Up @@ -96,3 +98,108 @@ def update_attribute(self):

def id(self):
return CommandID.EditProject


class SaveCalculationOutputs(QtGui.QUndoCommand):
"""Command for saving the updated problem, results, and log text from a calculation run.

Parameters
----------
problem : RATapi.rat_core.ProblemDefinition
The updated parameter values from a RAT run
results : Union[RATapi.outputs.Results, RATapi.outputs.BayesResults]
The calculation results.
log : str
log text from the given calculation.
presenter : MainWindowPresenter
The RasCAL main window presenter
"""

def __init__(
self,
problem: RATapi.rat_core.ProblemDefinition,
results: Union[RATapi.outputs.Results, RATapi.outputs.BayesResults],
log: str,
presenter,
):
super().__init__()
self.presenter = presenter
self.results = results
self.log = log
self.problem = self.get_parameter_values(problem)
self.old_problem = self.get_parameter_values(RATapi.inputs.make_problem(self.presenter.model.project))
self.old_results = copy.deepcopy(self.presenter.model.results)
self.old_log = self.presenter.model.result_log
self.setText("Save calculation results")

def get_parameter_values(self, problem: RATapi.rat_core.ProblemDefinition):
"""Gets updated parameter values from problem definition.

Parameters
----------
problem : RATapi.rat_core.ProblemDefinition
The updated parameter values from a RAT run.

Returns
-------
values : dict
A dict with updated parameter values from a RAT run.
"""
parameter_field = {
"parameters": "params",
"bulk_in": "bulkIn",
"bulk_out": "bulkOut",
"scalefactors": "scalefactors",
"domain_ratios": "domainRatio",
"background_parameters": "backgroundParams",
"resolution_parameters": "resolutionParams",
}

values = {}
for class_list in RATapi.project.parameter_class_lists:
entry = values.setdefault(class_list, [])
entry.extend(getattr(problem, parameter_field[class_list]))
return values

def set_parameter_values(self, values: dict):
"""Updates the parameter values of the project in the main window model.

Parameters
----------
values : dict
A dict with updated parameter values from a RAT run
"""
for key, value in values.items():
for index in range(len(value)):
getattr(self.presenter.model.project, key)[index].value = value[index]

def undo(self):
self.update_calculation_outputs(self.old_problem, self.old_results, self.old_log)

def redo(self):
self.update_calculation_outputs(self.problem, self.results, self.log)

def update_calculation_outputs(
self,
problem: RATapi.rat_core.ProblemDefinition,
results: Union[RATapi.outputs.Results, RATapi.outputs.BayesResults],
log: str,
):
"""Updates the project, results and log in the main window model

Parameters
----------
problem : RATapi.rat_core.ProblemDefinition
The updated parameter values from a RAT run
results : Union[RATapi.outputs.Results, RATapi.outputs.BayesResults]
The calculation results.
log : str
log text from the given calculation.
"""
self.set_parameter_values(problem)
self.presenter.model.update_results(copy.deepcopy(results))
self.presenter.model.result_log = log
chi_text = "" if results is None else f"{results.calculationResults.sumChi:.6g}"
self.presenter.view.controls_widget.chi_squared.setText(chi_text)
self.presenter.view.terminal_widget.clear()
self.presenter.view.terminal_widget.write(log)
1 change: 1 addition & 0 deletions rascal2/core/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def run(queue, rat_inputs: tuple, procedure: str, display: bool):
if display:
RAT.events.register(RAT.events.EventTypes.Message, queue.put)
RAT.events.register(RAT.events.EventTypes.Progress, queue.put)
RAT.events.register(RAT.events.EventTypes.Plot, queue.put)
queue.put(LogData(INFO, "Starting RAT"))

try:
Expand Down
Binary file added rascal2/static/images/hide-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 29 additions & 19 deletions rascal2/ui/model.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
from pathlib import Path
from typing import Union

import RATapi as RAT
import RATapi.outputs
from PyQt6 import QtCore


class MainWindowModel(QtCore.QObject):
"""Manages project data and communicates to view via signals"""
"""Manages project data and communicates to view via signals

Emits
-----
project_updated
A signal that indicates the project has been updated.
controls_updated
A signal that indicates the control has been updated.
results_updated
A signal that indicates the project and results have been updated.

"""

project_updated = QtCore.pyqtSignal()
controls_updated = QtCore.pyqtSignal()
results_updated = QtCore.pyqtSignal()

def __init__(self):
super().__init__()

self.project = None
self.results = None
self.result_log = ""
self.controls = None

self.save_path = ""
Expand All @@ -33,21 +48,16 @@ def create_project(self, name: str, save_path: str):
self.controls = RAT.Controls()
self.save_path = save_path

def handle_results(self, problem_definition: RAT.rat_core.ProblemDefinition):
"""Update the project given a set of results."""
parameter_field = {
"parameters": "params",
"bulk_in": "bulkIn",
"bulk_out": "bulkOut",
"scalefactors": "scalefactors",
"domain_ratios": "domainRatio",
"background_parameters": "backgroundParams",
"resolution_parameters": "resolutionParams",
}

for class_list in RAT.project.parameter_class_lists:
for index, value in enumerate(getattr(problem_definition, parameter_field[class_list])):
getattr(self.project, class_list)[index].value = value
def update_results(self, results: Union[RATapi.outputs.Results, RATapi.outputs.BayesResults]):
"""Update the project given a set of results.

Parameters
----------
results : Union[RATapi.outputs.Results, RATapi.outputs.BayesResults]
The calculation results.
"""
StephenNneji marked this conversation as resolved.
Show resolved Hide resolved
self.results = results
self.results_updated.emit()

def update_project(self, new_values: dict) -> None:
"""Replaces the project with a new project.
Expand Down Expand Up @@ -111,12 +121,12 @@ def load_r1_project(self, load_path: str):
self.controls = RAT.Controls()
self.save_path = str(Path(load_path).parent)

def update_controls(self, new_values):
"""
def update_controls(self, new_values: dict):
"""Update the control attributes.

Parameters
----------
new_values: Dict
new_values: dict
The attribute name-value pair to updated on the controls.
"""
vars(self.controls).update(new_values)
Expand Down
30 changes: 20 additions & 10 deletions rascal2/ui/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,14 @@ def run(self):

def handle_results(self):
"""Handle a RAT run being finished."""
self.model.handle_results(self.runner.updated_problem)
self.view.undo_stack.push(
commands.SaveCalculationOutputs(
self.runner.updated_problem,
self.runner.results,
self.view.terminal_widget.text_area.toPlainText(),
self,
)
)
self.view.handle_results(self.runner.results)

def handle_interrupt(self):
Expand All @@ -164,15 +171,18 @@ def handle_interrupt(self):
def handle_event(self):
"""Handle event data produced by the RAT run."""
event = self.runner.events.pop(0)
if isinstance(event, str):
self.view.terminal_widget.write(event)
chi_squared = get_live_chi_squared(event, str(self.model.controls.procedure))
if chi_squared is not None:
self.view.controls_widget.chi_squared.setText(chi_squared)
elif isinstance(event, RAT.events.ProgressEventData):
self.view.terminal_widget.update_progress(event)
elif isinstance(event, LogData):
self.view.logging.log(event.level, event.msg)
match event:
case str():
self.view.terminal_widget.write(event)
chi_squared = get_live_chi_squared(event, str(self.model.controls.procedure))
if chi_squared is not None:
self.view.controls_widget.chi_squared.setText(chi_squared)
case RAT.events.ProgressEventData():
self.view.terminal_widget.update_progress(event)
case RAT.events.PlotEventData():
self.view.plot_widget.plot_event(event)
case LogData():
self.view.logging.log(event.level, event.msg)

def edit_project(self, updated_project: dict) -> None:
"""Edit the Project with a dictionary of attributes.
Expand Down
13 changes: 5 additions & 8 deletions rascal2/ui/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rascal2.core.settings import MDIGeometries, Settings
from rascal2.dialogs.project_dialog import PROJECT_FILES, LoadDialog, LoadR1Dialog, NewProjectDialog, StartupDialog
from rascal2.dialogs.settings_dialog import SettingsDialog
from rascal2.widgets import ControlsWidget, TerminalWidget
from rascal2.widgets import ControlsWidget, PlotWidget, TerminalWidget
from rascal2.widgets.project import ProjectWidget
from rascal2.widgets.startup import StartUpWidget

Expand All @@ -32,11 +32,9 @@ def __init__(self):

self.presenter = MainWindowPresenter(self)
self.mdi = QtWidgets.QMdiArea()
# TODO replace the widgets below
# plotting: NO ISSUE YET
# https://github.com/RascalSoftware/RasCAL-2/issues/5
self.plotting_widget = QtWidgets.QWidget()
self.terminal_widget = TerminalWidget(self)

self.plot_widget = PlotWidget(self)
self.terminal_widget = TerminalWidget()
self.controls_widget = ControlsWidget(self)
self.project_widget = ProjectWidget(self)

Expand Down Expand Up @@ -249,13 +247,12 @@ def setup_mdi(self):
return

widgets = {
"Plots": self.plotting_widget,
"Plots": self.plot_widget,
"Project": self.project_widget,
"Terminal": self.terminal_widget,
"Fitting Controls": self.controls_widget,
}
self.setup_mdi_widgets()
self.terminal_widget.text_area.setVisible(True)

for title, widget in reversed(widgets.items()):
widget.setWindowTitle(title)
Expand Down
3 changes: 2 additions & 1 deletion rascal2/widgets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from rascal2.widgets.controls import ControlsWidget
from rascal2.widgets.inputs import AdaptiveDoubleSpinBox, get_validated_input
from rascal2.widgets.plot import PlotWidget
from rascal2.widgets.terminal import TerminalWidget

__all__ = ["ControlsWidget", "AdaptiveDoubleSpinBox", "get_validated_input", "TerminalWidget"]
__all__ = ["ControlsWidget", "AdaptiveDoubleSpinBox", "get_validated_input", "PlotWidget", "TerminalWidget"]
2 changes: 1 addition & 1 deletion rascal2/widgets/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ControlsWidget(QtWidgets.QWidget):
"""Widget for editing the Controls window."""

def __init__(self, parent):
super().__init__(parent)
super().__init__()
self.presenter = parent.presenter
self.presenter.model.controls_updated.connect(self.update_ui)

Expand Down
Loading
Loading