-
Notifications
You must be signed in to change notification settings - Fork 110
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
Improve handling of invalid experiments in GUI #9589
base: main
Are you sure you want to change the base?
Conversation
CodSpeed Performance ReportMerging #9589 will not alter performanceComparing Summary
|
d644aee
to
2fe1d74
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #9589 +/- ##
==========================================
- Coverage 91.85% 91.83% -0.03%
==========================================
Files 433 433
Lines 26768 26879 +111
==========================================
+ Hits 24587 24683 +96
- Misses 2181 2196 +15
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
and not ensemble.experiment.is_valid() | ||
): | ||
index = self.count() - 1 | ||
model_item = model.item(index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it works well. Should I use ItemData instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure. I looked into this due to style/linting errors. If the error was resolved, this is probably fine.
abb616c
to
d6e1bdd
Compare
src/ert/gui/main_window.py
Outdated
@@ -154,6 +154,7 @@ def right_clicked(self) -> None: | |||
def select_central_widget(self) -> None: | |||
actor = self.sender() | |||
if actor: | |||
self.notifier.refresh() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This refreshes storage to make sure the experiment files (responses, index, metadata, and parameters) are still valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could consider just re-running validation, and refresh if that fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated it so that if will only actually refresh when the validity has changed, when toggling between the panels. This also means that the storage won't be updated if new experiments are added and we toggle between the panels, but that is the same behavior as on main.
There is a bug where it crashes if you are already in the manage experiment window, tab out to delete the responses.json file, and then put focus back on the ert window. I am trying to have ert refresh storage when gaining focus, but reimplementing |
61e01c7
to
9203cd3
Compare
This PR contains the initial work towards making this part of the storage more robust. There is still an issue where ert crashes if you have selected an experiment in the manage-experiment panel, tab out and delete responses.json, and tab back in. It seems like the SelectionModel for the QListView tries reselecting the experiment (which is now invalid) before it has been revalidated. |
@@ -94,6 +94,8 @@ def __init__(self) -> None: | |||
|
|||
@Slot(Experiment) | |||
def setExperiment(self, experiment: Experiment) -> None: | |||
if not experiment.is_valid(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this even happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, when the selected experiment in manage-experiment becomes invalid while still selected. Apparently, when you tab out of ert while an experiment is selected, and tab in again; the same experiment is re-selected, and this signal is emitted. Very fun to debug 🔮
@@ -128,7 +129,9 @@ def data( | |||
qapp = QApplication.instance() | |||
assert isinstance(qapp, QApplication) | |||
return qapp.palette().mid() | |||
|
|||
elif role == Qt.ItemDataRole.ToolTipRole: | |||
if self._error: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is is the same error shown twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am removing the one not on experimentmodel.
@override | ||
def hasChildren(self, parent: QModelIndex | None = None) -> bool: | ||
if parent is None or not parent.isValid(): | ||
return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be False? At least it sounds like it, but it might be my lack of understanding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A person/item who has no parents, or not any valid parents, are guaranteed to have children?
I'm so confused here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not work without that line 😿
Is it that this is the topmost model (will never have a parent), and will always have children; but the number of children is dependent on the number of experiments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if you have no experiments? Is this still correct? I think you need to provide some comment or explanation here, since we don't understand why this is so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think hasChildren is there only to check if the tree can be expanded. In our case, the root node (StorageModel) will always be expanded, but the number of rows (ExperimentModels) will determine how much it will be expanded. I can change it to return len(self._children) > 0
, but hardcoding it to true would probably be fine too.
I will argue that you should alter the title of this PR. The PR does not specifically target the missing file, but rather handling invalid experiments. |
|
||
def check_plot_tool(expected_number_of_cases: int) -> None: | ||
find_and_click_button("button_Create_plot") | ||
# Due to the fact that we create new instances of PlotWindow on tab change, QtBot is defaulting to the first child |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to test the plotter as the missing responses.json
bug also occurred there
Yes, I was hit by scope creep... |
def _validate_files(self) -> None: | ||
self.valid_parameters = (self._path / self._parameter_file).exists() | ||
self.valid_responses = (self._path / self._responses_file).exists() | ||
self.valid_metadata = (self._path / self._metadata_file).exists() | ||
|
||
def is_valid(self) -> bool: | ||
return self.valid_parameters and self.valid_responses and self.valid_metadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to combine these, and check for existence of files in is_valid
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then it becomes more difficult to see if the validity of a specific experiment has changed. I think that is why I chose to have it in two separate steps.
self._storage.refresh() | ||
self.storage_changed.emit(self._storage) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this emit regardless of outcome?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only call this method when we know it will be changed when refreshing. Either if the validity of an experiment has changed; or if an experiment has finished/started running
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if you call the refresh function, this will emit regardless. I see that your statement is true when using revalidate_storage, due to the check there that looks at state changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but we only call it when we already know something has changed, this check is done upstream in both places where it's used 🤔
show_only_undefined: bool = False, | ||
show_only_no_children: bool = False, | ||
show_only_with_valid_experiment: bool = False, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these flags mutually exclusive? I.e. only one is ever set to true?
This could have been converted to an enum if so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, they are two completely separate filters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should spend a couple of minutes looking at this to see if we can alter this. I think this filtering thing have grown outside reasonable scope of multiple booleans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need all those filters, because this component is re-used with a lot of different context
show_only_undefined=True, | ||
show_only_with_valid_experiment=True, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Show only valid and undefined ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are separate, so it think they should have their own flags.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
show_only_undefined is for the ensembles in the experiment; whilst the other flag is for the experiment itself.
9203cd3
to
53e9bac
Compare
53e9bac
to
033426e
Compare
Not sure if it is related but this one hangs: https://github.com/equinor/ert/actions/runs/12924655010/job/36044056095?pr=9589 |
32addbb
to
4cbc09d
Compare
self.addItem( | ||
f"{ensemble.experiment.name} : {ensemble.name}", userData=ensemble | ||
) | ||
if ( | ||
self._show_only_with_valid_experiment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really need to check for _show_only_with_valid_experiment
here?
Does it not make sense to just do this for all experiments that are not valid?
This commit improves the handling of invalid experiments in the gui, in the case of missing responses.json file. The handling of the missing file should in the future be extended to also handle the other experiment files in a similar manner (index.json, metadata.json, and parameters.json) This commit: * Makes storage reload and re-validate when changing between ert modes (manage experient, run experiment, and plot tool) * Disables ensemble with invalid experiments from ensemble_selectors (error is shown as tooltip on hover) * Filters out invalid experiments from dark storage, so plotter won't attempt to plot them. * Reloads and re-validates storage on end of experiment, so ert won't crash if responses.json is deleted mid-run.
92032e8
to
8df6b4e
Compare
Issue
Resolves #9493
Approach
The commit in this PR:
(Screenshot of new behavior in GUI if applicable)
(This is when the responses.json file is deleted mid run. Prior to this PR, ert would crash with
ValueError: responses.json does not exist
)(This is when responses.json is deleted, and we open the plotter. The ensemble exists, but is not given as a option to plot due to it having an invalid experiment)
(This is when responses.json is deleted and we open manage-experiment. The experiment is shown, but cannot be selected. It is greyed out, and gives error as a tooltip when hovered)
git rebase -i main --exec 'pytest tests/ert/unit_tests -n logical -m "not integration_test"'
)When applicable