Skip to content

Commit

Permalink
MOD: Purging VerificationDataSaving from porepy.
Browse files Browse the repository at this point in the history
  • Loading branch information
vlipovac committed Jan 30, 2025
1 parent 179510f commit f927ca7
Show file tree
Hide file tree
Showing 16 changed files with 93 additions and 59 deletions.
3 changes: 1 addition & 2 deletions src/porepy/examples/mandel_biot.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from porepy.models.derived_models.biot import BiotPoromechanics
from porepy.numerics.linalg.matrix_operations import sparse_array_to_row_col_data
from porepy.utils.examples_utils import VerificationUtils
from porepy.viz.data_saving_model_mixin import VerificationDataSaving

# PorePy typings
number = pp.number
Expand Down Expand Up @@ -116,7 +115,7 @@ class MandelSaveData:
"""Current simulation time."""


class MandelDataSaving(VerificationDataSaving):
class MandelDataSaving(pp.PorePyModel):
"""Mixin class to save relevant data."""

darcy_flux: Callable[[list[pp.Grid]], pp.ad.Operator]
Expand Down
3 changes: 1 addition & 2 deletions src/porepy/examples/terzaghi_biot.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
from porepy.applications.convergence_analysis import ConvergenceAnalysis
from porepy.models.derived_models.biot import BiotPoromechanics
from porepy.utils.examples_utils import VerificationUtils
from porepy.viz.data_saving_model_mixin import VerificationDataSaving

# PorePy typings
number = pp.number
Expand Down Expand Up @@ -100,7 +99,7 @@ class TerzaghiSaveData:
"""Current simulation time."""


class TerzaghiDataSaving(VerificationDataSaving):
class TerzaghiDataSaving(pp.PorePyModel):
"""Mixin class to save relevant data."""

exact_sol: TerzaghiExactSolution
Expand Down
19 changes: 12 additions & 7 deletions src/porepy/models/derived_models/biot.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,7 @@
"""

import porepy as pp
from porepy.models.poromechanics import (
ConstitutiveLawsPoromechanics,
Poromechanics,
SolutionStrategyPoromechanics,
)
from porepy.models.poromechanics import Poromechanics, SolutionStrategyPoromechanics


class SolutionStrategyBiot(SolutionStrategyPoromechanics):
Expand All @@ -100,8 +96,17 @@ def set_materials(self):
class ConstitutiveLawsBiot(
pp.constitutive_laws.SpecificStorage,
pp.constitutive_laws.BiotPoroMechanicsPorosity,
ConstitutiveLawsPoromechanics,
): ...
):
"""Additional constitutive laws required for the Biot-Poromechanics model.
Note:
These are additions and do not contain everything a poromechanical model needs.
Intention behind this choice include a cleaner MRO in the
:class:`BiotPoromechanics`.
"""

...


class BiotPoromechanics( # type: ignore[misc]
Expand Down
5 changes: 4 additions & 1 deletion src/porepy/models/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"""

from pathlib import Path
from typing import TYPE_CHECKING, Callable, Literal, Optional, Protocol, Sequence
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Protocol, Sequence

import numpy as np
import scipy.sparse as sps
Expand Down Expand Up @@ -537,6 +537,9 @@ class SolutionStrategyProtocol(Protocol):
"""Time step as an automatic differentiation scalar."""
nonlinear_solver_statistics: pp.SolverStatistics
"""Solver statistics for the nonlinear solver."""
results: list[Any]
"""A list of results collected by the data saving mixin in
:meth:`~porepy.viz.data_saving_model_mixin.DataSavingMixin.collect_data`."""

@property
def time_step_indices(self) -> np.ndarray:
Expand Down
3 changes: 3 additions & 0 deletions src/porepy/models/solution_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ def __init__(self, params: Optional[dict] = None):
"""Restart options. The template is provided in `SolutionStrategy.__init__`."""
self.ad_time_step = pp.ad.Scalar(self.time_manager.dt)
"""Time step as an automatic differentiation scalar."""
self.results: list[Any] = []
"""A list of results collected by the data saving mixin in
:meth:`~porepy.viz.data_saving_model_mixin.DataSavingMixin.collect_data`."""

self.set_solver_statistics()

Expand Down
74 changes: 43 additions & 31 deletions src/porepy/viz/data_saving_model_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from __future__ import annotations

from pathlib import Path
from typing import Optional, Union
from typing import Any, Optional, Union

import numpy as np

Expand All @@ -29,14 +29,19 @@ class DataSavingMixin(pp.PorePyModel):
def save_data_time_step(self) -> None:
"""Export the model state at a given time step and log time.
The options for exporting times are:
* `None`: All time steps are exported
* `list`: Export if time is in the list. If the list is empty, then no
times are exported.
The options for exporting times can be given as ``params['times_to_export']``:
- ``None``: All time steps are exported.
- ``list``: Export if time is in the list. If the list is empty, then no
times are exported.
In addition, save the solver statistics to file if the option is set.
Finally, :meth:`collect_data` is called and stored in :attr:`results` for
data collection and verification in runtime.
"""

# Fetching the desired times to export.
times_to_export = self.params.get("times_to_export", None)
if times_to_export is None:
Expand All @@ -55,6 +60,39 @@ def save_data_time_step(self) -> None:
# Save solver statistics to file.
self.nonlinear_solver_statistics.save()

# Collecting and storing data in runtime for analysis.
if not self._is_time_dependent(): # stationary problem
if (
self.nonlinear_solver_statistics.num_iteration > 0
): # avoid saving initial condition
collected_data = self.collect_data()
if collected_data is not None:
self.results.append(collected_data)
else: # time-dependent problem
t = self.time_manager.time # current time
scheduled = self.time_manager.schedule[1:] # scheduled times except t_init
if any(np.isclose(t, scheduled)):
collected_data = self.collect_data()
if collected_data is not None:
self.results.append(collected_data)

def collect_data(self) -> Any:
"""Collect relevant simulation data to be stored in attr:`results`.
Override to collect data respectively. By default, this method returns None and
nothing is stored.
For stationary problems, this method is called in every iteration. For time
dependent problems, it is called after convergence of a time step which is
scheduled by the time manager.
Returns:
Any data structure relevant for future verification. By default, None.
If it is not None, it is stored in :attr:`results`.
"""
return None

def write_pvd_and_vtu(self) -> None:
"""Helper function for writing the .vtu and .pvd files and time information."""
self.exporter.write_vtu(self.data_to_export(), time_dependent=True)
Expand Down Expand Up @@ -237,29 +275,3 @@ def load_data_from_pvd(
self.time_manager.load_time_information(times_file)
self.time_manager.set_time_and_dt_from_exported_steps(time_index)
self.exporter._time_step_counter = time_index


class VerificationDataSaving(DataSavingMixin):
"""Class to store relevant data for a generic verification setup."""

results: list
"""List of objects containing the results of the verification."""

def save_data_time_step(self) -> None:
"""Save data to the `results` list."""
if not self._is_time_dependent(): # stationary problem
if (
self.nonlinear_solver_statistics.num_iteration > 0
): # avoid saving initial condition
collected_data = self.collect_data()
self.results.append(collected_data)
else: # time-dependent problem
t = self.time_manager.time # current time
scheduled = self.time_manager.schedule[1:] # scheduled times except t_init
if any(np.isclose(t, scheduled)):
collected_data = self.collect_data()
self.results.append(collected_data)

def collect_data(self):
"""Collect relevant data for the verification setup."""
raise NotImplementedError()
5 changes: 2 additions & 3 deletions tests/applications/test_convergence_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
)
from porepy.models.fluid_mass_balance import SinglePhaseFlow
from porepy.utils.txt_io import read_data_from_txt
from porepy.viz.data_saving_model_mixin import VerificationDataSaving


# -----> Fixtures that are required on a module level.
Expand Down Expand Up @@ -461,7 +460,7 @@ class StationaryModelSaveData:
error_var_0: float # error associated with variable 0
error_var_1: float # error associated with variable 1

class StationaryModelDataSaving(VerificationDataSaving):
class StationaryModelDataSaving(pp.PorePyModel):
"""Class that collects and store data."""

def collect_data(self) -> StationaryModelSaveData:
Expand Down Expand Up @@ -528,7 +527,7 @@ class TimeDependentModelSaveData:
error_var_0: float # error associated with variable 0
error_var_1: float # error associated with variable 1

class TimeDependentModelDataSaving(VerificationDataSaving):
class TimeDependentModelDataSaving(pp.PorePyModel):
"""Class that collects and store data."""

def collect_data(self) -> TimeDependentModelSaveData:
Expand Down
3 changes: 1 addition & 2 deletions tests/functional/setups/manu_flow_comp_2d_frac.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@

import porepy as pp
from porepy.applications.convergence_analysis import ConvergenceAnalysis
from porepy.viz.data_saving_model_mixin import VerificationDataSaving
from tests.functional.setups.manu_flow_incomp_frac_2d import (
ManuIncompSaveData,
ManuIncompUtils,
Expand Down Expand Up @@ -75,7 +74,7 @@ class ManuCompSaveData(ManuIncompSaveData):
"""Current simulation time."""


class ManuCompDataSaving(VerificationDataSaving):
class ManuCompDataSaving(pp.PorePyModel):
"""Mixin class to store relevant data."""

darcy_flux: Callable[[list[pp.Grid]], pp.ad.Operator]
Expand Down
3 changes: 1 addition & 2 deletions tests/functional/setups/manu_flow_incomp_frac_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from porepy.applications.convergence_analysis import ConvergenceAnalysis
from porepy.applications.md_grids.domains import nd_cube_domain
from porepy.utils.examples_utils import VerificationUtils
from porepy.viz.data_saving_model_mixin import VerificationDataSaving

# PorePy typings
number = pp.number
Expand Down Expand Up @@ -97,7 +96,7 @@ class ManuIncompSaveData:
"""Exact pressure in the matrix."""


class ManuIncompDataSaving(VerificationDataSaving):
class ManuIncompDataSaving(pp.PorePyModel):
"""Mixin class to save relevant data."""

darcy_flux: Callable[[list[pp.Grid]], pp.ad.Operator]
Expand Down
3 changes: 1 addition & 2 deletions tests/functional/setups/manu_poromech_nofrac_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
from porepy.applications.convergence_analysis import ConvergenceAnalysis
from porepy.applications.md_grids.domains import nd_cube_domain
from porepy.utils.examples_utils import VerificationUtils
from porepy.viz.data_saving_model_mixin import VerificationDataSaving

# PorePy typings
number = pp.number
Expand Down Expand Up @@ -112,7 +111,7 @@ class ManuPoroMechSaveData:
"""Current simulation time."""


class ManuPoroMechDataSaving(VerificationDataSaving):
class ManuPoroMechDataSaving(pp.PorePyModel):
"""Mixin class to save relevant data."""

darcy_flux: Callable[[list[pp.Grid]], pp.ad.Operator]
Expand Down
9 changes: 7 additions & 2 deletions tests/functional/setups/manu_thermoporomech_nofrac_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
import porepy as pp
from porepy.applications.convergence_analysis import ConvergenceAnalysis
from porepy.applications.md_grids.domains import nd_cube_domain
from porepy.viz.data_saving_model_mixin import VerificationDataSaving

# PorePy typings
number = pp.number
Expand Down Expand Up @@ -116,7 +115,7 @@ class ManuThermoPoroMechSaveData:
time: number


class ManuThermoPoroMechDataSaving(VerificationDataSaving):
class ManuThermoPoroMechDataSaving(pp.PorePyModel):
"""Mixin class to save relevant data."""

exact_sol: ManuThermoPoroMechExactSolution2d
Expand All @@ -138,6 +137,12 @@ class ManuThermoPoroMechDataSaving(VerificationDataSaving):
"""

energy_flux: Callable[[list[pp.Grid]], pp.ad.Operator]
"""Method that returns the energy fluxes in the form of an Ad operator. Usually
provided by the mixin class
:class:`porepy.models.energy_balance.EnergyBalanceEquations`.
"""
darcy_flux: Callable[[list[pp.Grid]], pp.ad.Operator]
"""Method that returns the Darcy fluxes in the form of an Ad operator. Usually
provided by the mixin class :class:`porepy.models.constitutive_laws.DarcysLaw`.
Expand Down
4 changes: 3 additions & 1 deletion tests/functional/test_mandel.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ def results() -> list[MandelSaveData]:


@pytest.mark.parametrize("time_index", [0, 1])
def test_error_primary_and_secondary_variables(time_index: int, results):
def test_error_primary_and_secondary_variables(
time_index: int, results: list[MandelSaveData]
):
"""Checks error for pressure, displacement, flux, force, and consolidation degree.
Physical parameters used in this test have been adapted from [1].
Expand Down
4 changes: 3 additions & 1 deletion tests/functional/test_manu_flow_comp_frac.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,15 @@ def actual_l2_errors(
"reference_variable_values": reference_values,
"meshing_arguments": {"cell_size": 0.125},
"time_manager": pp.TimeManager([0, 0.5, 1.0], 0.5, True),
"times_to_export": [], # Suppress output for tests
}

# Retrieve actual L2-relative errors.
errors: list[list[dict[str, float]]] = []
# Loop through models, i.e., 2d and 3d.
for model in [ManuCompFlowSetup2d, ManuCompFlowSetup3d]:
# Make deep copy of params to avoid nasty bugs.
setup = model(deepcopy(model_params))
setup: pp.PorePyModel = model(deepcopy(model_params))
pp.run_time_dependent_model(setup, {})
errors_setup: list[dict[str, float]] = []
# Loop through results, i.e., results for each scheduled time.
Expand Down Expand Up @@ -279,6 +280,7 @@ def actual_ooc(
"material_constants": material_constants,
"reference_variable_values": reference_values,
"meshing_arguments": {"cell_size": 0.125},
"times_to_export": [], # Suppress output for tests
}
# Use 4 levels of refinement for 2d and 3 levels for 3d
if model_idx == 0:
Expand Down
6 changes: 5 additions & 1 deletion tests/functional/test_manu_flow_incomp_frac.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,17 @@ def actual_l2_errors(material_constants: dict) -> list[dict[str, float]]:
"grid_type": "cartesian",
"material_constants": material_constants,
"meshing_arguments": {"cell_size": 0.125},
"times_to_export": [], # Suppress output for tests
}

# Retrieve actual L2-relative errors
errors: list[dict[str, float]] = []
# Loop through models, i.e., 2d and 3d
for model in [ManuIncompFlowSetup2d, ManuIncompFlowSetup3d]:
# Make deep copy of params to avoid nasty bugs.
setup = model(deepcopy(model_params))
setup: ManuIncompFlowSetup2d | ManuIncompFlowSetup3d = model(
deepcopy(model_params)
)
pp.run_time_dependent_model(setup)
errors.append(
{
Expand Down Expand Up @@ -217,6 +220,7 @@ def actual_ooc(material_constants: dict) -> list[list[dict[str, float]]]:
"grid_type": grid_type,
"material_constants": material_constants,
"meshing_arguments": {"cell_size": 0.125},
"times_to_export": [], # Suppress output for tests
}
# Use 4 levels of refinement for 2d and 3 levels for 3d
if model_idx == 0:
Expand Down
4 changes: 3 additions & 1 deletion tests/functional/test_manu_poromech_nofrac.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,15 @@ def actual_l2_errors(material_constants: dict) -> list[list[dict[str, float]]]:
"meshing_arguments": {"cell_size": 0.25},
"manufactured_solution": "nordbotten_2016",
"time_manager": pp.TimeManager([0, 0.5, 1.0], 0.5, True),
"times_to_export": [], # Suppress output for tests
}

# Retrieve actual L2-relative errors.
errors: list[list[dict[str, float]]] = []
# Loop through models, i.e., 2d and 3d.
for model in [ManuPoroMechSetup2d, ManuPoroMechSetup3d]:
# Make deep copy of params to avoid nasty bugs.
setup = model(deepcopy(model_params))
setup: pp.PorePyModel = model(deepcopy(model_params))
pp.run_time_dependent_model(setup)
errors_setup: list[dict[str, float]] = []
# Loop through results, i.e., results for each scheduled time.
Expand Down Expand Up @@ -251,6 +252,7 @@ def actual_ooc(material_constants: dict) -> list[list[dict[str, float]]]:
"grid_type": grid_type,
"material_constants": material_constants,
"meshing_arguments": {"cell_size": 0.25},
"times_to_export": [], # Suppress output for tests
}
# Use 4 levels of refinement for 2d and 3 levels for 3d.
if model_idx == 0:
Expand Down
Loading

0 comments on commit f927ca7

Please sign in to comment.