### Installing PorePy with Development and Testing Dependencies via Pip Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Installs the PorePy package from the current source directory using pip, including extra dependencies required for development and running tests. ```bash pip install .[development,testing] ``` -------------------------------- ### Testing Metis Installation via Command Line Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Executes the 'mpmetis' command with the '-help' flag to verify that the Metis command-line utility is correctly installed and accessible in the system's PATH. ```bash mpmetis -help ``` -------------------------------- ### Cloning PorePy Repository via Git Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Downloads the most current version of the PorePy source code repository from GitHub to the local machine. ```bash git clone https://github.com/pmgbergen/porepy.git ``` -------------------------------- ### Installing Build Tools via APT on Linux Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Installs essential build tools like CMake and `build-essentials` (containing GCC, G++, Make, etc.) required for compiling software from source on Debian/Ubuntu-based systems. Requires superuser privileges. ```bash sudo apt install cmake build-essentials ``` -------------------------------- ### Installing pybind11 via Pip Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Installs the pybind11 library using pip, which is a header-only library needed for creating Python bindings to C++ code and is a dependency for installing pymetis. ```bash pip install pybind11 ``` -------------------------------- ### Installing Metis Library via APT on Linux Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Installs the Metis graph partitioning library using the APT package manager, commonly used on Debian/Ubuntu-based Linux distributions. Requires superuser privileges. ```bash sudo apt install metis ``` -------------------------------- ### Navigating to PorePy Directory via Bash Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Changes the current working directory in the terminal to the newly cloned 'porepy' repository folder. ```bash cd porepy ``` -------------------------------- ### Checking out Stable PorePy Branch via Git Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Switches the current branch of the local PorePy repository from the default 'develop' to the 'main' branch, which typically represents the stable release. ```bash git checkout main ``` -------------------------------- ### Building Metis Library from Source via Make Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Compiles the Metis graph partitioning library from source code using the Make build system. Must be executed within the Met Metis source directory after running 'make config'. ```bash make ``` -------------------------------- ### Installing pymetis Library via Pip Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Installs the pymetis Python library using pip. This library provides Python bindings to the Metis graph partitioning library. Requires Metis and pybind11 to be installed beforehand. ```bash pip install pymetis ``` -------------------------------- ### Installing PorePy in Editable User Mode via Pip Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Installs the PorePy package from the current source directory into the user's Python environment in editable mode, allowing direct modification of the source files. Includes development and testing dependencies. ```bash pip install --user -e .[development,testing] ``` -------------------------------- ### Configuring Metis Source Build via Make Source: https://github.com/pmgbergen/porepy/blob/develop/Install.md Runs the configuration script (often a Makefile target like 'config') necessary before compiling the Metis library from source files. Must be executed within the Metis source directory. ```bash make config ``` -------------------------------- ### Initializing PorePy Biot Fractured Model - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Imports necessary libraries and defines a custom `BiotFractured` class inheriting from `SquareDomainOrthogonalFractures` and `BiotPoromechanics`. It sets up meshing arguments and prepares the simulation model instance, which is used as the data source for the exporter examples. ```python import numpy as np import porepy as pp from porepy.models.derived_models.biot import BiotPoromechanics from porepy.applications.md_grids.model_geometries import ( SquareDomainOrthogonalFractures, ) class BiotFractured(SquareDomainOrthogonalFractures, BiotPoromechanics): def meshing_arguments(self) -> dict: cell_size = self.units.convert_units(0.25, "m") return {"cell_size": cell_size} params = {"fracture_indices": [0, 1]} model = BiotFractured(params) model.prepare_simulation() ``` -------------------------------- ### Importing Core Libraries - PorePy Example - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/flux_discretizations.ipynb Imports essential libraries required for numerical operations (NumPy), sparse matrix handling (SciPy sparse), and the PorePy library for reservoir simulation tasks. ```python import numpy as np import scipy.sparse as sps import porepy as pp ``` -------------------------------- ### Exporting PorePy Model States (Example 1) - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Demonstrates the basic usage of `pp.Exporter.write_vtu` by creating a new exporter and providing a list of variable keys (e.g., pressure, displacement, flux). This exports the current data associated with these keys from the model's `pp.TIME_STEP_SOLUTIONS` for all relevant grids into a VTU file. ```python exporter_1 = pp.Exporter(model.mdg, file_name="example-1",folder_name="exporter-tutorial") exporter_1.write_vtu([ model.pressure_variable, model.displacement_variable, model.interface_darcy_flux_variable ]) ``` -------------------------------- ### Selecting PorePy Grids by Dimension - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Fetches lists of grids from the mixed-dimensional grid (`model.mdg`) based on their dimension. It retrieves all 1D subdomains, 2D subdomains, and 1D interfaces using the `subdomains()` and `interfaces()` methods with the `dim` argument, preparing subsets for selective data export in subsequent examples. ```python subdomains_1d = model.mdg.subdomains(dim=1) subdomains_2d = model.mdg.subdomains(dim=2) interfaces_1d = model.mdg.interfaces(dim=1) ``` -------------------------------- ### Pulling PorePy Stable Docker Image (Shell) Source: https://github.com/pmgbergen/porepy/blob/develop/Readme.md This command downloads the stable release version of the PorePy simulation tool available as a Docker image. Users must have Docker installed and operational to utilize this command. It fetches the latest stable build from the `porepy/stable` repository hosted on Docker Hub. ```Shell docker pull porepy/stable ``` -------------------------------- ### Exporting PorePy States on Specific Grids (Example 2) - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Creates a new exporter instance and uses `write_vtu` with a list of tuples. Each tuple specifies one or more grids (e.g., `subdomains_1d`, `subdomains_2d`, `interfaces_1d`) and the variable key to export *only* on those grids, demonstrating selective export based on grid identity or dimension. ```python exporter_2 = pp.Exporter(model.mdg, "example-2", "exporter-tutorial") exporter_2.write_vtu([ (subdomains_1d, model.pressure_variable), (subdomains_2d, model.displacement_variable), (interfaces_1d, model.interface_darcy_flux_variable), ]) ``` -------------------------------- ### Pulling PorePy Development Docker Image (Shell) Source: https://github.com/pmgbergen/porepy/blob/develop/Readme.md This command downloads the development version of the PorePy simulation tool packaged as a Docker image. Users need Docker installed and running on their system to execute this command successfully. The command pulls the latest development build from the `porepy/dev` repository on Docker Hub. ```Shell docker pull porepy/dev ``` -------------------------------- ### Generating PorePy PVD File with Times - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Demonstrates how to create a PVD file that links the previously exported time series VTU files (e.g., from Example 5a/b) to actual simulation times. It calls the `write_pvd` method on the exporter used for the time series, providing a list of time values corresponding to each step. ```python times_5 = [0.0, 0.1, 1.0, 2.0, 10.0] exporter_5.write_pvd(times_5) ``` -------------------------------- ### Running Basic Poromechanics Model - PorePy Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporting_models.ipynb This snippet demonstrates the default data export behavior of a basic PorePy poromechanics model. It initializes the model with specific parameters, including a folder name for output, and runs a time-dependent simulation. The model inherits `DataSavingMixin`, enabling automatic export of primary variables and specific domain properties at the start and end of each time step. ```python import porepy as pp import numpy as np from porepy.models.poromechanics import Poromechanics model_params = { "folder_name": "model_exporting" } # The compound class Poromechanics inherits from DataSavingMixin. model_0 = Poromechanics(model_params) pp.run_time_dependent_model(model_0) ``` -------------------------------- ### Exporting Mixed PorePy Data Formats (Example 4) - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Demonstrates the flexibility of the `write_vtu` method by combining different input formats in a single call. It shows how to export explicit data vectors (grid/key/data tuple), state variables on specific grids (grid(s)/key tuple), and state variables on all relevant grids (simple key) simultaneously. ```python exporter_4 = pp.Exporter(model.mdg, "example-4", "exporter-tutorial") exporter_4.write_vtu( [ (sd_2d, "cc", sd_2d.cell_centers), (subdomains_1d, model.pressure_variable), model.displacement_variable] + interface_data ) ``` -------------------------------- ### Importing Mandel Problem Constants - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/mandels_problem.ipynb Imports predefined fluid and solid constant dictionaries (`mandel_fluid_constants`, `mandel_solid_constants`) from the `mandel_biot` example module within PorePy. These dictionaries contain suggested physical properties based on established literature for the Mandel problem. ```python from porepy.examples.mandel_biot import mandel_fluid_constants, mandel_solid_constants ``` -------------------------------- ### Exporting PorePy Time Series Data (Example 5a) - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Demonstrates exporting data for multiple time steps using the `time_step` argument in the `write_vtu` method within a loop. It iterates through steps, exporting the specified variables for each step and generating filenames that include the step number, simulating a time-dependent simulation output. ```python exporter_5 = pp.Exporter(model.mdg, "example-5", "exporter-tutorial") variable_names = [ model.pressure_variable, model.displacement_variable, model.interface_darcy_flux_variable, ] for step in range(5): # Data may change exporter_5.write_vtu(variable_names, time_step=step) ``` -------------------------------- ### Visualizing PorePy Grid with Cell IDs - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/grid_topology.ipynb Plot the initialized PorePy grid, displaying the unique ID for each cell and other basic grid features (`cfn`). This visualization serves as a helpful visual guide throughout the tutorial for understanding the numbering and layout referenced in topological data queries. ```python cell_id = np.arange(g.num_cells) pp.plot_grid(g, cell_value=cell_id, info='cfn', alpha=0.5, figsize=(15,12), plot_2d=True) ``` -------------------------------- ### Selecting a Specific PorePy Grid - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Selects the first grid from the list of 2D subdomains obtained previously. This demonstrates how to isolate a single grid object, like `sd_2d`, from a list of grids. This specific grid is then used in subsequent examples for targeted export operations. ```python sd_2d = subdomains_2d[0] ``` -------------------------------- ### Applying Darcy Flux and Pressure BCs in PorePy (Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/boundary_conditions.ipynb This example uses Darcy flux and pressure Dirichlet boundary conditions with the same spatial configuration as the previous example. It also demonstrates modifying the fluid viscosity to show that the Darcy flux boundary condition correctly accounts for the change in fluid properties. ```python import numpy as np import porepy as pp from porepy.models.fluid_mass_balance import SinglePhaseFlow class SinglePhaseFlowExample2(ModifiedGeometry, SinglePhaseFlow): # Note that now this is bc_type_darcy_flux, not the bc_type_fluid_flux. def bc_type_darcy_flux(self, sd: pp.Grid) -> pp.BoundaryCondition: """Everything is the same as in the previous example.""" domain_sides = self.domain_boundary_sides(sd) return pp.BoundaryCondition(sd, faces=domain_sides.east, cond="dir") # Note that now this is bc_values_darcy_flux, not the bc_values_fluid_flux. def bc_values_darcy_flux(self, bg: pp.BoundaryGrid) -> np.ndarray: """Setting the Darcy flux values on the west boundary.""" darcy_flux_vals = np.zeros(bg.num_cells) # Same as in the previous example domain_sides = self.domain_boundary_sides(bg) influx_cells = np.zeros(bg.num_cells, dtype=bool) influx_cells[domain_sides.west] = True influx_cells &= bg.cell_centers[1] > 0.5 influx_cells &= bg.cell_centers[1] < 1.5 # The value is the same darcy_flux_vals[influx_cells] = self.units.convert_units(-1, "Pa*m^-1") return darcy_flux_vals # This method did not change. def bc_values_pressure(self, bg: pp.BoundaryGrid) -> np.ndarray: """Everything is the same as in the previous example.""" pressure_vals = np.zeros(bg.num_cells) domain_sides = self.domain_boundary_sides(bg) pressure_vals[domain_sides.east] = self.units.convert_units(5, "Pa") return pressure_vals # We modify the fluid viscosity. fluid_constants = pp.FluidComponent( viscosity=10 # 10 times larger than in the previous example. ) model_params = { "material_constants": {"fluid": fluid_constants} } model = SinglePhaseFlowExample2( params=model_params ) pp.run_time_dependent_model(model) pp.plot_grid(model.mdg, model.pressure_variable, plot_2d=True) ``` -------------------------------- ### Instantiating Mortar Projections for Interface Transfer (PorePy, Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/subdomain_and_interface_projections.ipynb This Python snippet retrieves the interfaces and neighboring subdomains from the mixed-dimensional grid object obtained from the simulation model. It then initializes a `pp.ad.MortarProjections` object, which is a PorePy utility class designed to handle quantity transfers and projections across interfaces between different dimensional subdomains, setting it up for subsequent demonstration examples. ```python mdg_interfaces = model.mdg.interfaces(codim=1) subdomains = model.interfaces_to_subdomains(mdg_interfaces) projection = pp.ad.MortarProjections(model.mdg, subdomains, mdg_interfaces, dim=1) ``` -------------------------------- ### Exporting PorePy Constant Data In-File (Example 6b) - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Demonstrates how to include "constant" data within the same output file as the time-dependent data by setting the `export_constants_separately` parameter to `False` during exporter initialization. Constant data is added using `add_constant_data` and then included in the main VTU file generated by each `write_vtu` call. ```python exporter_6_b = pp.Exporter(model.mdg, "example-6-b", "exporter-tutorial", export_constants_separately = False) exporter_6_b.add_constant_data([(sd_2d, "cc", sd_2d.cell_centers)]) for step in range(5): # Update the previously defined "constant" data if step == 2: exporter_6_b.add_constant_data([(sd_2d, "cc", -sd_2d.cell_centers)]) # All constant data will be exported also if not specified exporter_6_b.write_vtu(variable_names, time_step=step) ``` -------------------------------- ### Exporting Explicit PorePy Data (Example 3) - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Shows how to export data vectors that are not stored in the model's default state dictionary (`pp.TIME_STEP_SOLUTIONS`). It defines lists of tuples, each containing a single grid, a string key for the data name, and the data vector itself (e.g., cell centers), then exports them using `write_vtu`. ```python subdomain_data = [(sd_2d, "cc", sd_2d.cell_centers)] interface_data = [ (intf, "cc_e", (-1) ** i * intf.cell_centers) for i, intf in enumerate(interfaces_1d) ] exporter_3 = pp.Exporter(model.mdg, "example-3", "exporter-tutorial") exporter_3.write_vtu(subdomain_data + interface_data) ``` -------------------------------- ### Setting PorePy Variable Numerical Values - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb This snippet shows how to manually update the numerical values stored for a variable within the model's state using the `equation_system.set_variable_values` method. This allows changing the variable's state, for example, to set initial or boundary conditions. ```python import numpy as np p_new = np.sin(np.arange(p_initial.size)) model.equation_system.set_variable_values( values=p_new, variables=[p], iterate_index=0, time_step_index=0, additive=False ) # Check that it worked model.equation_system.evaluate(p)[:10] ``` -------------------------------- ### Applying Fluid Flux and Pressure BCs in PorePy (Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/boundary_conditions.ipynb This class extends `ModifiedGeometry` and `SinglePhaseFlow` to define boundary conditions. It sets a Dirichlet pressure on the east boundary and a constant Neumann fluid flux in the middle of the west boundary, with no flux elsewhere, demonstrating a common BC setup. ```python from porepy.models.fluid_mass_balance import SinglePhaseFlow class SinglePhaseFlowExample1(ModifiedGeometry, SinglePhaseFlow): def bc_type_fluid_flux(self, sd: pp.Grid) -> pp.BoundaryCondition: """Setting the Dirichlet type on the east boundary, Neumann elsewhere.""" domain_sides = self.domain_boundary_sides(sd) return pp.BoundaryCondition(sd, faces=domain_sides.east, cond="dir") def bc_values_fluid_flux(self, bg: pp.BoundaryGrid) -> np.ndarray: """Setting the values of the fluid mass flux.""" mass_flux_vals = np.zeros(bg.num_cells) domain_sides = self.domain_boundary_sides(bg) influx_cells = np.zeros(bg.num_cells, dtype=bool) influx_cells[domain_sides.west] = True # Setting the values on the west boundary where 0.5 < y < 1.5 # "&" operator is the elementwise boolean AND influx_cells &= bg.cell_centers[1] > 0.5 influx_cells &= bg.cell_centers[1] < 1.5 mass_flux_vals[influx_cells] = self.units.convert_units(-1, "kg*s^-1") return mass_flux_vals def bc_values_pressure(self, bg: pp.BoundaryGrid) -> np.ndarray: pressure_vals = np.zeros(bg.num_cells) domain_sides = self.domain_boundary_sides(bg) pressure_vals[domain_sides.east] = self.units.convert_units(5, "Pa") return pressure_vals model_params = {} model = SinglePhaseFlowExample1(params=model_params) pp.run_time_dependent_model(model) pp.plot_grid(model.mdg, model.pressure_variable, plot_2d=True) ``` -------------------------------- ### Exporting Separate PorePy Constant Data (Example 6a) - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Illustrates how to export data that is constant or changes infrequently using `add_constant_data` before calling `write_vtu` in a time loop. This "constant" data is exported to separate files by default alongside the time-dependent data when `write_vtu` is called, saving space if the data is truly constant. ```python exporter_6_a = pp.Exporter(model.mdg, "example-6-a", "exporter-tutorial") # Define some "constant" data exporter_6_a.add_constant_data([(sd_2d, "cc", sd_2d.cell_centers)]) for step in range(5): # Update the previously defined "constant" data if step == 2: exporter_6_a.add_constant_data([(sd_2d, "cc", -sd_2d.cell_centers)]) # All constant data will be exported also if not specified exporter_6_a.write_vtu(variable_names, time_step=step) ``` -------------------------------- ### Plotting Mixed-Dimensional and Refined Fracture Grids (PorePy, Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/subdomain_and_interface_projections.ipynb This Python snippet uses PorePy's plotting functions to visualize the simulation setup. The first plot shows the overall mixed-dimensional grid including the fracture. The second plot focuses specifically on the fracture grid itself, highlighting cells and faces to visually confirm that the fracture grid is refined compared to the matrix grid faces it neighbors, illustrating the non-matching property. ```python pp.plot_grid(model.mdg, fracturewidth_1d=4, plot_2d=True, alpha=0.75) pp.plot_grid( model.mdg.subdomains(dim=1)[0], fracturewidth_1d=4, plot_2d=True, info="cf") ``` -------------------------------- ### Exporting PorePy Time Series Data (Example 5b) - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Shows an alternative way to export time series data by setting `time_dependent=True` in the `write_vtu` call. This allows the exporter to automatically manage the time step numbering for filenames internally as the method is called repeatedly within a loop, simplifying time series file management. ```python exporter_5 = pp.Exporter(model.mdg, "example-5", "exporter-tutorial") for step in range(5): # Data may change exporter_5.write_vtu(variable_names, time_dependent=True) ``` -------------------------------- ### Evaluating Fluid Flux Term with AD - PorePy Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb This code snippet retrieves subdomains from the model's multiscale discrete graph (`mdg`), calculates the `fluid_flux` term for these subdomains, and then evaluates this term using the `equation_system.evaluate` method with `derivative=True` to get both values and the Jacobian contribution. The result (`fluid_flux_data`) contains the evaluated values and the associated derivative data for the flux term. Requires a PorePy model instance (`model`) with a configured `mdg` and `equation_system`. ```python subdomains = model.mdg.subdomains()\nfluid_flux = model.fluid_flux(subdomains)\nfluid_flux_data = model.equation_system.evaluate(fluid_flux, derivative=True)\nfluid_flux_data ``` -------------------------------- ### Inspecting PorePy prepare_simulation Method Source - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb This snippet uses the `inspect` module to print the source code of the `prepare_simulation` method from the model instance. This is done to illustrate the sequence of initialization steps performed when setting up a PorePy model. ```python print(inspect.getsource(model.prepare_simulation)) ``` -------------------------------- ### Creating PorePy Exporter Instance - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Instantiates the `pp.Exporter` class, which is the main tool for exporting PorePy data. It requires the mixed-dimensional grid (`model.mdg`) to access geometric and topological information, a base `file_name` for output files, and an optional `folder_name` for organizing the output directory. ```python exporter = pp.Exporter(model.mdg, file_name="file", folder_name="folder") ``` -------------------------------- ### Accessing PorePy Equation Computed Jacobian - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb After evaluating an equation with `derivative=True` to get an `AdArray`, the automatically computed Jacobian matrix of the equation with respect to the model's variables can be accessed via the `.jac` attribute of the resulting `AdArray`. ```python poly_ad.jac.todense()[:5, :5] ``` -------------------------------- ### Evaluating PorePy Equation Numerical Value - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb The numerical value of an equation, based on the current state of the variables it depends on, can be computed using the `equation_system.evaluate` method. This snippet shows how to get the numerical result of the previously defined polynomial equation. ```python model.equation_system.evaluate(poly_eq)[:10] ``` -------------------------------- ### Initializing PorePy Model and Plotting Grid - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb This snippet creates an instance of the custom model class and calls `prepare_simulation` to initialize the model, which includes setting up the grid based on the inherited geometry and meshing arguments. It then plots the generated grid and the fracture network. ```python model = SinglePhaseFlowWithGeometry() model.prepare_simulation() print("The grid:") pp.plot_grid(model.mdg, plot_2d=True) print("One fracture inside:") model.fracture_network.plot() ``` -------------------------------- ### Configuring & Running Line Search Simulation (PorePy, Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/solution_strategies.ipynb Defines a tailored line search nonlinear solver class by combining different line search components. It then sets up model and solver parameters in dictionaries, specifying the tailored solver class, convergence criteria, and line search options. Finally, it instantiates the previously defined model and runs the simulation using PorePy's time-dependent runner with the specified solver parameters. ```python class ConstraintLineSearchNonlinearSolver( line_search.ConstraintLineSearch, # The tailoring to contact constraints. line_search.SplineInterpolationLineSearch, # Technical implementation of the actual search along given update direction line_search.LineSearchNewtonSolver, # General line search. ): """Collect all the line search methods in one class.""" model_params = { "fracture_indices": [1], # Fracture with constant y-coordinate "meshing_arguments": {"cell_size": 1 / 4}, } solver_params = { "nl_convergence_tol_res": 1e-10, # Since the line search reduces the update, a residual based tolerance is needed "nonlinear_solver": ConstraintLineSearchNonlinearSolver, "Global_line_search": 0, # Set to 1 to use turn on a residual-based line search "Local_line_search": 1, # Set to 0 to use turn off the tailored line search "adaptive_indicator_scaling": 1, # Scale the indicator adaptively to increase robustness } # Run the simulation. model = TailoredPoromechanics(model_params) pp.run_time_dependent_model(model, solver_params) ``` -------------------------------- ### Getting Dense Cell-Face Connectivity in PorePy - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/grid_topology.ipynb Obtain a dense matrix representation of the cell-face connectivity using the `g.cell_faces_as_dense()` method. This matrix is convenient for operations that need to distinguish between internal and boundary faces, as boundary faces are indicated by negative cell indices. ```python g.cell_faces_as_dense() ``` -------------------------------- ### Inspecting Set Equations Method Source - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb This code uses `inspect.getsource` to print the source code of the `model.set_equations` method. This method is where all the individual balance equations and other necessary equations are typically defined and registered within the PorePy `equation_system`. Requires the `inspect` module and a PorePy model instance (`model`). ```python print(inspect.getsource(model.set_equations)) ``` -------------------------------- ### Displaying AD Variable 'p' - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb This snippet simply outputs the value or string representation of the variable `p`. In the context of the document, `p` is likely an `AdArray` variable representing pressure, defined earlier in the PorePy model setup. Requires the variable `p` to be defined in the current scope. ```python p ``` -------------------------------- ### Importing PorePy and MandelModel - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/mandels_problem.ipynb Imports the core PorePy library (aliased as pp) and the specialized MandelModel class required to set up and simulate Mandel's consolidation problem. This prepares the Python environment for defining simulation parameters and running the model. ```python import porepy as pp from porepy.examples.mandel_biot import MandelModel ``` -------------------------------- ### Assembling Balance Equation from Terms - PorePy Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb This snippet first calculates the accumulation, flux, and source terms for the specified subdomains. It then combines these terms using the `model.balance_equation` method, specifying the subdomains, terms, and equation dimension. Finally, it prints the source code of the `model.balance_equation` method to show how the assembly is implemented. Requires a PorePy model instance (`model`) with methods for calculating terms and assembling equations. ```python accumulation = model.fluid_mass(subdomains)\nflux = model.fluid_flux(subdomains)\nsource = model.fluid_source(subdomains)\n\neq = model.balance_equation(\n subdomains=subdomains,\n accumulation=accumulation,\n surface_term=flux,\n source=source,\n dim=1, # Scalar equation\n)\n\nprint(inspect.getsource(model.balance_equation)) ``` -------------------------------- ### Projecting Pressure from Fracture to Mortar in PorePy and Comparing Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/subdomain_and_interface_projections.ipynb This snippet projects pressure from the secondary domain (fracture) onto the mortar grid using the average-based projection method for intensive quantities. It evaluates the result and compares it to manual calculations involving averaging pressure values from specific fracture cell pairs (indices 0, 1 and 2, 3) to illustrate the projection process. ```python # Pressure values projected from the fracture onto the mortar grid: fracture_pressure_on_intf = projection.secondary_to_mortar_avg() @ model.pressure(subdomains) fracture_pressure_on_intf_value = model.equation_system.evaluate(fracture_pressure_on_intf) print("Projection using MortarProjection:", fracture_pressure_on_intf_value) # Pressure values in the fracture: fracture_pressure_values = model.equation_system.evaluate(model.pressure(subdomains))[-4:] # Average fracture pressure values: average_01 = (fracture_pressure_values[0] + fracture_pressure_values[1])/2 average_23 = (fracture_pressure_values[2] + fracture_pressure_values[3])/2 print("Projection from fracture cells 0 and 1 using hard-coded indices:", average_01) print("Projection from fracture cells 2 and 3 using hard-coded indices:", average_23) ``` -------------------------------- ### Setting Simulation Parameters - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/mandels_problem.ipynb Configures the comprehensive set of parameters needed for the `MandelModel` simulation. This includes defining material constants using imported values and PorePy classes, setting up length scaling for numerical stability, configuring time stepping schedules, specifying mesh arguments, and bundling all settings into a dictionary. ```python # Set material constants material_constants = { "solid": pp.SolidConstants(**mandel_solid_constants), "fluid": pp.FluidComponent(**mandel_fluid_constants), } # Set scaling scaling = {"m": 1e-3} units = pp.Units(**scaling) # Create time manager time_manager = pp.TimeManager( schedule=[0, 1e2, 1e3, 5e3, 1e4], # [s] dt_init=100, # [s] constant_dt=True, # [s] ) # Set mesh size (only mesh_size_bound is used). ls = 1 / units.m # length scaling mesh_arguments = {"cell_size": 50.0 * ls} # Create model params model_params = { "material_constants": material_constants, "meshing_arguments": mesh_arguments, "time_manager": time_manager, "plot_results": True, "units": units, } ``` -------------------------------- ### Exporting Single Grid Data with PorePy Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb This snippet shows how to initialize a PorePy Exporter object for a single grid and export specific data, like cell centers, to a VTU file. It requires a PorePy grid object (sd_2d) and demonstrates providing data as a key-value pair within a list for the write_vtu method. ```python exporter_7 = pp.Exporter(sd_2d, "example-7", "exporter-tutorial") exporter_7.write_vtu([("cc", sd_2d.cell_centers)]) ``` -------------------------------- ### Solving Linear Elasticity with MPSA PorePy Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/stress_discretization.ipynb Initializes the MPSA discretization class, defines simulation parameters (stiffness tensor, source term, boundary conditions and values), initializes the data dictionary, performs the discretization, assembles the linear system matrix (A) and right-hand side vector (b), and solves the system for the displacement vector (u) using a direct solver. ```python parameter_keyword = "mechanics" mpsa_class = pp.Mpsa(parameter_keyword) f = np.zeros(g.dim * g.num_cells) specified_parameters = { "fourth_order_tensor": C, "source": f, "bc": bound, "bc_values": u_b, } data = pp.initialize_default_data(g, {}, parameter_keyword, specified_parameters) mpsa_class.discretize(g, data) A, b = mpsa_class.assemble_matrix_rhs(g, data) u = np.linalg.solve(A.toarray(), b) ``` -------------------------------- ### Examining and Using PorePy Cell-Node Method - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/grid_topology.ipynb First, print the source code of the built-in `g.cell_nodes()` method to understand its implementation, then call it to find the indices of the nodes connected to the first cell. This method efficiently derives the cell-node relationship by combining cell-face and face-node data. ```python print(inspect.getsource(g.cell_nodes)) g.cell_nodes()[:,0].nonzero()[0] ``` -------------------------------- ### Setting PorePy Domain and Meshing Parameters (Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/boundary_conditions.ipynb This class defines fundamental methods for setting up the simulation domain and meshing arguments using PorePy's utilities. It specifies a 2D square domain and defines cell size for meshing. ```python from porepy.applications.md_grids.domains import nd_cube_domain import numpy as np import porepy as pp class ModifiedGeometry: units: pp.Units def set_domain(self) -> None: """Defining a two-dimensional square domain with sidelength 2.""" size = self.units.convert_units(2, "m") self._domain = nd_cube_domain(2, size) def grid_type(self) -> str: """Choosing the grid type for our domain.""" return self.params.get("grid_type", "cartesian") def meshing_arguments(self) -> dict: """Meshing arguments for md-grid creation.""" cell_size = self.units.convert_units(0.25, "m") mesh_args: dict[str, float] = {"cell_size": cell_size} return mesh_args ``` -------------------------------- ### Constructing a Triangle Grid from Nodes in PorePy (Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/grids.ipynb Illustrates creating a 2D simplex (triangle) grid using `pp.TriangleGrid` by providing an array of node coordinates. It demonstrates how to perturb existing nodes and then use the modified node set to generate the new grid. ```python nodes = g.nodes[:2] nodes[1, 5:7] = np.array([1.2, 0.8]) g = pp.TriangleGrid(nodes) g.compute_geometry() pp.plot_grid(g, plot_2d=True) ``` -------------------------------- ### Initializing a PorePy CartGrid - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/grid_topology.ipynb Initialize a 2D Cartesian grid with specified dimensions using `pp.CartGrid` and compute its geometry. This code sets up the basic grid structure that is used throughout the tutorial to demonstrate topological concepts. It requires the `numpy` and `porepy` libraries. ```python import numpy as np import porepy as pp import inspect nx = np.array([3, 2]) g = pp.CartGrid(nx) g.compute_geometry() ``` -------------------------------- ### Inspecting Mass Balance Equation Source - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb This snippet uses the `inspect` module to print the source code of the `mass_balance_equation` method of the `model` object. It's used to show how the equation is assembled from different terms internally. Requires the `inspect` module and a PorePy model instance (`model`). ```python print(inspect.getsource(model.mass_balance_equation)) ``` -------------------------------- ### Defining Custom Poromechanics Model (PorePy, Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/solution_strategies.ipynb Defines a custom Poromechanics model class by inheriting from multiple PorePy components. This includes geometry definition (CubeDomainOrthogonalFractures), a nonlinear constitutive law for fracture permeability (CubicLawPermeability), contact mechanics indicators required for the tailored line search (ContactIndicators), and the base Poromechanics class. This class represents the specific multiphysics problem setup. ```python import porepy as pp from porepy.applications.md_grids.model_geometries import CubeDomainOrthogonalFractures from porepy.numerics.nonlinear import line_search class TailoredPoromechanics( # Problem definition CubeDomainOrthogonalFractures, # Add nonlinearity to the fracture flow pp.constitutive_laws.CubicLawPermeability, # Needed for the tailored line search algorithm pp.models.solution_strategy.ContactIndicators, # Base class pp.Poromechanics, ): """Combine mixins with the poromechanics class.""" ``` -------------------------------- ### Inspecting PorePy Model State Keys - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/exporter.ipynb Iterates through subdomains and interfaces of the model's mixed-dimensional grid (`mdg`). It collects and prints the unique keys stored in the `pp.TIME_STEP_SOLUTIONS` dictionary for both subdomains and interfaces, showing available default data fields that can be exported. ```python # Determine all keys of all states on all subdomains subdomain_states = [] for sd, data in model.mdg.subdomains(return_data=True): subdomain_states += data[pp.TIME_STEP_SOLUTIONS].keys() print("Keys of the states defined on subdomains:", set(subdomain_states)) # Determine all keys of all states on all interfaces interface_states = [] for sd, data in model.mdg.interfaces(return_data=True): interface_states += data[pp.TIME_STEP_SOLUTIONS].keys() print("Keys of the states defined on interfaces:", set(interface_states)) ``` -------------------------------- ### Assembling Full Equation System - PorePy Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/equations.ipynb This snippet calls the `assemble` method on the model's `equation_system`. This method evaluates the entire system of equations that have been registered, computing the current values of the residuals (`vals`) and the full system Jacobian matrix (`jacobian`) using automatic differentiation. Requires a PorePy model instance (`model`) with a configured `equation_system` containing defined equations. ```python jacobian, vals = model.equation_system.assemble() ``` -------------------------------- ### Manually Summing Traced Pressures by Index for Comparison in PorePy Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/subdomain_and_interface_projections.ipynb This snippet evaluates the full traced pressure array from the primary domain. It then demonstrates how to manually calculate the sum of specific traced pressure values using assumed hard-coded indices (7, 23, 4, 22), mimicking the result of projection for comparison with the MortarProjection output. ```python pressure_trace = model.equation_system.evaluate(model.pressure_trace(subdomains)) # Traced pressures neighboring fracture cells 0 and 1: print("Projection using hard-coded indices:", pressure_trace[7] + pressure_trace[23]) # Traced pressures neighboring fracture cells 2 and 3: print("Projection using hard-coded indices:", pressure_trace[4] + pressure_trace[22]) ``` -------------------------------- ### Defining Geometry and BCs with Non-Matching Grids (PorePy, Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/subdomain_and_interface_projections.ipynb This Python class defines the geometry and boundary conditions for a PorePy simulation involving a fracture in a 2D domain. It demonstrates creating a non-matching grid by refining the fracture subdomain. It sets domain size, meshing arguments, fracture location, Darcy flux boundary condition type (Dirichlet), and specific pressure boundary values on the domain edges. ```python import porepy as pp import numpy as np class GeometryAndBC: """Mixin for defining the model geometry and boundary conditions.""" def set_geometry(self) -> None: """Define geometry and create a non-matching mixed-dimensional grid.""" super().set_geometry() # Refine fracture grid such that the mixed-dimensional geometry is non-matching. old_fracture_grid = self.mdg.subdomains(dim=1)[0] new_fracture_grid = pp.refinement.refine_grid_1d(g=old_fracture_grid, ratio=2) self.mdg.replace_subdomains_and_interfaces( {old_fracture_grid: new_fracture_grid} ) # Create projections between local and global coordinates for fracture grids. pp.set_local_coordinate_projections(self.mdg) def set_domain(self) -> None: """Defining a two-dimensional rectangular domain.""" size = self.units.convert_units(1.0, "m") box: dict[str, pp.number] = {"xmax": size, "ymax": 2.0 * size} self._domain = pp.Domain(box) def meshing_arguments(self) -> dict[str, float]: """Set the meshing arguments.""" return self.params.get("meshing_arguments", {"cell_size": 0.5}) def set_fractures(self) -> None: """Set a vertical fracture.""" frac_1_points = self.units.convert_units( np.array([[0.5, 0.5], [0.5, 1.5]]), "m" ) frac_1 = pp.LineFracture(frac_1_points) self._fractures = [frac_1] def bc_type_darcy_flux(self, sd: pp.Grid) -> pp.BoundaryCondition: """Assign Dirichlet to the west and east boundaries. Parameters: sd: The subdomain grid. Returns: The boundary condition with Dirichlet on the west and east boundaries. """ domain_sides = self.domain_boundary_sides(sd) bc = pp.BoundaryCondition(sd, domain_sides.west + domain_sides.east, "dir") return bc def bc_values_pressure(self, bg: pp.BoundaryGrid) -> np.ndarray: """Assign pressure boundary values for projection tutorial. We assign some non-trivial pressure boundary condition values for demonstrating the projection of quantities between the rock matrix, mortar grids and fracture. Parameters: bg: The boundary grid. Returns: The pressure values at the boundary. """ domain_sides = self.domain_boundary_sides(bg) values = np.zeros(bg.num_cells) y = bg.cell_centers[1, :][domain_sides.west] values[domain_sides.west] = self.units.convert_units(42 * y, "Pa") values[domain_sides.east] = self.units.convert_units(24, "Pa") return values ``` -------------------------------- ### Running Single Phase Flow Simulation with Non-Matching Grid (PorePy, Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/subdomain_and_interface_projections.ipynb This Python snippet defines a new class by inheriting from the `GeometryAndBC` mixin and the base `pp.SinglePhaseFlow` class, effectively combining the custom geometry and boundary conditions with the standard flow model. It then instantiates this combined model and runs a time-dependent simulation using PorePy's `run_time_dependent_model` function. ```python class SinglePhaseFlowNonMatchingGrid(GeometryAndBC, pp.SinglePhaseFlow): """Single phase flow in a fractured domain represented by non-matching grids.""" model = SinglePhaseFlowNonMatchingGrid() pp.run_time_dependent_model(model) ``` -------------------------------- ### Running Mandel Model Simulation - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/mandels_problem.ipynb Initializes an instance of the `MandelModel` class, passing the previously defined `model_params` dictionary containing all simulation configurations. It then executes the time-dependent simulation using the `pp.run_time_dependent_model` function, which manages the simulation loop and output. ```python model = MandelModel(params=model_params) pp.run_time_dependent_model(model=model) ``` -------------------------------- ### Initializing Data Dictionary - PorePy - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/flux_discretizations.ipynb Creates a comprehensive data dictionary associated with the grid. It uses `pp.initialize_default_data` to include the specified parameters under the 'flow' key and add any default values not explicitly provided. ```python data_key = "flow" data = pp.initialize_default_data(g, {}, data_key, parameters) ``` -------------------------------- ### Solving with MPFA - PorePy - Python Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/flux_discretizations.ipynb Instantiates the Multi-Point Flux Approximation (MPFA) discretization, applies it to the grid and data (overwriting previous discretizations), assembles the linear system, and solves for pressure. It reuses the previously defined source term discretization. ```python flow_discretization = pp.Mpfa(data_key) flow_discretization.discretize(g, data) A, b_flow = flow_discretization.assemble_matrix_rhs(g, data) p_mpfa = sps.linalg.spsolve(A, b_flow + b_rhs) ``` -------------------------------- ### Listing Available PorePy Boundary Condition Methods (Python) Source: https://github.com/pmgbergen/porepy/blob/develop/tutorials/boundary_conditions.ipynb This Python snippet demonstrates how to programmatically discover available boundary condition methods (`bc_type_*` and `bc_values_*`) within a specific PorePy model class using the `dir()` function. ```python for x in dir(pp.models.fluid_mass_balance.BoundaryConditionsSinglePhaseFlow): if x.startswith("bc_values") or x.startswith("bc_type"): print(x) ```