Skip to content

Add misfit plot for breakthrough observations#12987

Merged
SAKavli merged 3 commits intoequinor:mainfrom
SAKavli:breakthrough-misfit
Mar 3, 2026
Merged

Add misfit plot for breakthrough observations#12987
SAKavli merged 3 commits intoequinor:mainfrom
SAKavli:breakthrough-misfit

Conversation

@SAKavli
Copy link
Copy Markdown
Contributor

@SAKavli SAKavli commented Feb 27, 2026

Issue
Resolves #12968

The strategy has been to tie breakthrough observations plotting functionality close up to summary observations.
To easily achieve this, it's beneficial if breakthrough observations and responses can be matched on dates.

(Screenshot of new behavior in GUI if applicable)

  • PR title captures the intent of the changes, and is fitting for release notes.
  • Added appropriate release note label
  • Commit history is consistent and clean, in line with the contribution guidelines.
  • Make sure unit tests pass locally after every commit (git rebase -i main --exec 'just rapid-tests')

When applicable

  • When there are user facing changes: Updated documentation
  • New behavior or changes to existing untested code: Ensured that unit tests are added (See Ground Rules).
  • Large PR: Prepare changes in small commits for more convenient review
  • Bug fix: Add regression test for the bug
  • Bug fix: Add backport label to latest release (format: 'backport release-branch-name')

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Feb 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.51%. Comparing base (e980540) to head (71e4389).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12987      +/-   ##
==========================================
+ Coverage   90.40%   90.51%   +0.10%     
==========================================
  Files         453      453              
  Lines       31155    31164       +9     
==========================================
+ Hits        28165    28207      +42     
+ Misses       2990     2957      -33     
Flag Coverage Δ
cli-tests 37.24% <0.00%> (-0.01%) ⬇️
gui-tests 67.93% <60.00%> (+0.08%) ⬆️
performance-and-unit-tests 77.05% <80.00%> (-0.01%) ⬇️
test 46.16% <0.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Feb 27, 2026

Merging this PR will not alter performance

✅ 35 untouched benchmarks


Comparing SAKavli:breakthrough-misfit (05c5f54) with main (e980540)

Open in CodSpeed

@SAKavli SAKavli force-pushed the breakthrough-misfit branch 7 times, most recently from 8097c6e to da8bdf9 Compare March 2, 2026 14:27
@SAKavli SAKavli marked this pull request as ready for review March 2, 2026 14:40
@SAKavli SAKavli force-pushed the breakthrough-misfit branch from da8bdf9 to a81fa1e Compare March 2, 2026 14:43
@SAKavli SAKavli added the release-notes:unreleased-bug-fix PR with a bug-fix of unreleased features label Mar 2, 2026
@SAKavli SAKavli changed the title Breakthrough misfit Add misfit plot for breakthrough observations Mar 2, 2026
SAKavli added 2 commits March 2, 2026 15:44
To be more similar to summary responses, the breakthrough
responses benefits from having the observed date as their "time"
values as this is how to match responses with observations
in for example the plotter.
@SAKavli SAKavli force-pushed the breakthrough-misfit branch from a81fa1e to 05c5f54 Compare March 2, 2026 14:44
Comment thread tests/ert/ui_tests/gui/test_breakthrough_visualization.py Outdated
Copy link
Copy Markdown
Contributor

@eivindjahren eivindjahren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just some small readability comments. We could consider refactoring the visualization snapshot tests a bit. I had a thought that we might want to have:

def _iter_keys(key_model, data_model):
    for key_index in range(key_model.rowCount()):
        item = data_model.itemAt(data_model.index(key_index, 0))
        assert item is not None
        yield item, key_model.index(key_index, 0)


def select_plotter_figure(plot_window: PlotWindow, key: str, plot_tab_name: str):
    """the figure of the given key in the given plot tab"""
    central_tab = plot_window._central_tab
    data_types = get_child(plot_window, DataTypeKeysWidget)
    key_list = data_types.data_type_keys_widget

    found_selected_key = False
    for key_def, key_index in _iter_keys(key_list.model(), data_types.model):
        if key_def.key == key:
            key_list.setCurrentIndex(key_index)
            for tab_index, tab in enumerate(plot_window._plot_widgets):
                if tab.name == plot_tab_name:
                    found_selected_key = True
                    assert central_tab.isTabEnabled(tab_index)
                    central_tab.setCurrentWidget(tab)
                    assert key_def.dimensionality == tab._plotter.dimensionality
                    if plot_tab_name == STD_DEV:
                        # we need a better resolution for box plots
                        tab._figure.set_size_inches(
                            2000 / tab._figure.get_dpi(),
                            1000 / tab._figure.get_dpi(),
                        )
                    yield tab._figure.figure
    assert found_selected_key

def summary_response(realization: int) -> pl.DataFrame:
    num_points = 14
    center = 6.0 + 0.35 * realization
    steepness = 1.0 + 0.1 * (realization % 3)

    values = [
        0.01 + 0.94 / (1.0 + exp(-(day - center) / steepness))
        for day in range(1, num_points + 1)
    ]

    return pl.DataFrame(
        {
            "response_key": ["WWCT:OP1"] * num_points,
            "time": [datetime(2000, 1, day) for day in range(1, num_points + 1)],
            "values": pl.Series(values, dtype=pl.Float32),
        }
    )


@contextmanager
def open_plotter(config: ErtConfig, qtbot: QtBot):
    log_handler = GUILogHandler()
    with ErtServerController.init_service(project=Path(config.ens_path)):
        args_mock = Mock()
        args_mock.config = "breakthrough.ert"
        gui = _setup_main_window(config, args_mock, log_handler, config.ens_path)
        qtbot.addWidget(gui)

        button_plot_tool = get_child(gui, QToolButton, name="button_Create_plot")

        qtbot.mouseClick(button_plot_tool, Qt.MouseButton.LeftButton)
        plot_window = get_child(gui, PlotWindow)

        yield plot_window

        plot_window.close()


def setup_storage(config):
    def dump_all(configurations):
        return [c.model_dump(mode="json") for c in configurations]

    ens_config = config.ensemble_config
    num_reals = config.runpath_config.num_realizations
    with open_storage(config.ens_path, mode="w") as storage:
        experiment = storage.create_experiment(
            experiment_config={
                "parameter_configuration": dump_all(ens_config.parameter_configuration),
                "response_configuration": dump_all(ens_config.response_configuration),
                "derived_response_configuration": dump_all(
                    ens_config.derived_response_configuration
                ),
                "observations": dump_all(config.observation_declarations),
                "ert_templates": config.ert_templates,
            },
            name="breakthrough-experiment",
        )
        ensemble = experiment.create_ensemble(ensemble_size=num_reals, name="prior")
        bt_config = experiment.derived_response_configuration["breakthrough"]
        for r in range(num_reals):
            ensemble.save_response("summary", summary_response(r), r)
            breakthrough_response = bt_config.derive_from_storage(0, r, ensemble)
            ensemble.save_response("breakthrough", breakthrough_response, r)


def create_breakthrough_figure(plot_tab_name: str):
    @pytest.fixture
    def plot_figure(use_tmpdir, qtbot: QtBot):
        config = _breakthrough_config()

        setup_storage(config)

        open_storage(config.ens_path, mode="r")

        with open_plotter(config, qtbot) as plot_window:
            yield from select_plotter_figure(
                plot_window, "BREAKTHROUGH:WWCT:OP1", plot_tab_name
            )

    return plot_figure


plot_figure = create_breakthrough_figure(ENSEMBLE)

Note that select_plotter_figure is made a bit more general as it could be reused in other tests.

Add snapshot test for breakthrough misfit plotter
@SAKavli SAKavli force-pushed the breakthrough-misfit branch from 05c5f54 to 71e4389 Compare March 3, 2026 07:40
@SAKavli
Copy link
Copy Markdown
Contributor Author

SAKavli commented Mar 3, 2026

Great suggestion, @eivindjahren ! I will steal your snipped if you don't mind 💰

@SAKavli SAKavli enabled auto-merge (rebase) March 3, 2026 07:46
@SAKavli SAKavli merged commit 6588eab into equinor:main Mar 3, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-notes:unreleased-bug-fix PR with a bug-fix of unreleased features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix misfit values for breakthrough observations

3 participants