-
Notifications
You must be signed in to change notification settings - Fork 91
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
Nonmatching grid tests #1334
base: develop
Are you sure you want to change the base?
Nonmatching grid tests #1334
Changes from all commits
6f50386
156929e
bd5b8b3
6996974
a1dc1b6
699a478
f75a0b1
e6066b4
2266ab3
67325c1
66678b3
f5f3ccc
1c2f7d0
b8d3d9a
cacc1be
0c6586e
b46cfaf
03d1893
451da30
b1d2632
01998dd
9380cf4
d216ed2
edad744
9eb5e1a
a36be9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,12 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Union | ||
|
||
import numpy as np | ||
|
||
import porepy as pp | ||
from porepy.grids import mortar_grid | ||
from porepy.grids.mdg_generation import _preprocess_cartesian_args | ||
|
||
from . import domains, fracture_sets | ||
|
||
|
@@ -155,3 +159,97 @@ def meshing_arguments(self) -> dict: | |
"cell_size_min": 0.2 * ls, | ||
} | ||
return mesh_sizes | ||
|
||
|
||
class NonMatchingSquareDomainOrthogonalFractures(SquareDomainOrthogonalFractures): | ||
"""Create a non-matching mixed-dimensional grid of a square domain with up to two | ||
orthogonal fractures. | ||
|
||
The setup is similar to :class:`SquareDomainOrthogonalFractures`, but the | ||
geometry allows for non-matching grids and different resolution for each grid. | ||
""" | ||
|
||
def set_geometry(self) -> None: | ||
"""Define geometry and create a non-matching mixed-dimensional grid. | ||
|
||
We here make a non-matching mixed-dimensional grid in the sense that neither of | ||
the fracture grids, interface grids or the rock grid are matching. This is done | ||
by refining the fracture and interface grids. | ||
|
||
""" | ||
IngridKJ marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# The fracture grids are replaced by a refined version of the fractures which | ||
# are already present in the grid. The interface grids are replaced by first | ||
# creating a new mixed-dimensional grid with a higher resolution. The new | ||
# mixed-dimensional grid will "donate" its interface grids to the original | ||
# mixed-dimensional grid. | ||
|
||
# First set the geometry and create a matching mixed-dimensional grid. | ||
assert isinstance(self, pp.ModelGeometry) | ||
super().set_geometry() # type:ignore[safe-super] | ||
self.mdg = pp.mdg_library.square_with_orthogonal_fractures(????, non_matching=True) | ||
|
||
# Refine and replace fracture grids. First we fetch the old fracture grids: | ||
old_fracture_grids = self.mdg.subdomains(dim=1) | ||
|
||
# Ratios which we want to refine the fracture grids with. Default, if no ratios | ||
# are provided, is to refine by a factor of 2. | ||
ratios = self.params.get( | ||
"fracture_refinement_ratios", np.ones(len(old_fracture_grids) * 2) | ||
) | ||
|
||
# The actual refinement of the fracture grids. | ||
new_fracture_grids = [ | ||
pp.refinement.refine_grid_1d(g=old_grid, ratio=ratio) | ||
for old_grid, ratio in zip(old_fracture_grids, ratios) | ||
] | ||
|
||
# Create a mapping between the old and new fracture grids. | ||
grid_map = dict(zip(old_fracture_grids, new_fracture_grids)) | ||
|
||
# Refine and replace interface grids. | ||
# Directly refining the already existing interface grids is not straightforward. | ||
# Instead, we create a new mixed-dimensional grid and donate the interface grids | ||
# to the original mixed-dimensional grid. | ||
|
||
# First we gather the number of cells in x- and y- direction, as well as the | ||
# physical dimensions of the original mixed-dimensional grid: | ||
domain = pp.Domain(self.domain.bounding_box) | ||
meshing_args = self.meshing_arguments() | ||
nx_cells, phys_dims, _ = _preprocess_cartesian_args(domain, meshing_args, {}) | ||
|
||
# Then we create a new mdg with the same fractures and physical dimensions as | ||
# the old one, but with a higher resolution: | ||
nx_cells_new = np.array(nx_cells) * self.params.get( | ||
"interface_refinement_ratio", 2 | ||
) | ||
fracs = [self.fractures[i].pts for i in range(len(self.fractures))] | ||
mdg_new = pp.meshing.cart_grid(fracs, nx_cells_new, physdims=phys_dims) | ||
|
||
# Loop through all the interfaces in the new mixed-dimensional grid (donor) such | ||
# that we can fill intf_map with a map between old and new interface grids. | ||
intf_map: dict[ | ||
pp.MortarGrid, | ||
Union[pp.MortarGrid, dict[mortar_grid.MortarSides, pp.Grid]], | ||
] = {} | ||
for intf in mdg_new.interfaces(dim=1): | ||
# Then, for each interface, we fetch the secondary grid which belongs to it. | ||
_, g_sec = mdg_new.interface_to_subdomain_pair(intf) | ||
|
||
# We then loop through all the interfaces in the original grid (recipient). | ||
for intf_coarse in self.mdg.interfaces(dim=1): | ||
# Fetch the secondary grid of the interface in the original grid. | ||
_, g_sec_coarse = self.mdg.interface_to_subdomain_pair(intf_coarse) | ||
|
||
# Checking the fracture number of the secondary grid in the recipient | ||
# mdg. If they are the same, i.e., if the fractures are the same ones, | ||
# we update the interface map. | ||
if g_sec_coarse.frac_num == g_sec.frac_num: | ||
intf_map.update({intf_coarse: intf}) | ||
|
||
# Finally replace the subdomains and interfaces in the original | ||
# mixed-dimensional grid. | ||
self.mdg.replace_subdomains_and_interfaces(sd_map=grid_map, intf_map=intf_map) | ||
Comment on lines
+192
to
+252
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't lines 192 through 252 just a repetition of what happens during call to pp.mdg_library.square_with_orthogonal_fractures? |
||
|
||
# Create projections between local and global coordinates for fracture grids. | ||
pp.set_local_coordinate_projections(self.mdg) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
Some of these tests are sensitive to meshing or node ordering. If this turns out to | ||
cause problems, we deactivate the corresponding asserts. | ||
""" | ||
|
||
import pytest | ||
import numpy as np | ||
|
||
|
@@ -45,6 +46,7 @@ def check_fracture_coordinates(self, cell_centers, face_centers): | |
assert np.all(np.isclose(g.face_centers, face_centers[i])) | ||
|
||
def check_intersections(self, n_intersections): | ||
"""Check that the number of intersections is as expected.""" | ||
assert len(self.mdg.subdomains(dim=0)) == n_intersections | ||
|
||
def check_domain(self, x_length, y_length, z_length=None): | ||
|
@@ -113,6 +115,67 @@ def test_two_intersecting_custom_values(self): | |
|
||
self.check_fracture_coordinates([cc0, cc1], [fc0, fc1]) | ||
|
||
@pytest.mark.parametrize("mesh_type", ["simplex", "cartesian"]) | ||
@pytest.mark.parametrize("num_fractures", [1, 2]) | ||
def test_two_intersecting_nonmatching(self, mesh_type, num_fractures): | ||
"""Test meshing of a 2d square domain to generate a non-matching grid. | ||
|
||
This is not a very powerful test, but it does verify that the generated grids | ||
have different numbers of cells in the fractures and along the mortars, and | ||
that the mortar grids have a different number of grids than the matrix faces | ||
tagged as on a fracture. | ||
|
||
Parameters: | ||
mesh_type: The type of mesh to generate, either 'simplex' or 'cartesian'. | ||
num_fractures: The number of fractures to include in the domain. | ||
|
||
""" | ||
meshing_arguments = {"cell_size": 1 / 4} | ||
|
||
# Define the fracture endpoints and indices according to the input. | ||
fracture_endpoints = [] | ||
fracture_indices = [] | ||
if num_fractures > 0: | ||
fracture_endpoints.append(np.array([1 / 4, 3 / 4])) | ||
fracture_indices.append(0) | ||
if num_fractures > 1: | ||
fracture_endpoints.append(np.array([0, 1])) | ||
fracture_indices.append(1) | ||
|
||
# Generate | ||
self.mdg, _ = pp.mdg_library.square_with_orthogonal_fractures( | ||
mesh_type, | ||
meshing_arguments, | ||
fracture_indices=fracture_indices, | ||
fracture_endpoints=fracture_endpoints, | ||
non_matching=True, | ||
**{"interface_refinement_ratio": 2, "fracture_refinement_ratio": 4}, | ||
) | ||
|
||
# Number of faces in the 2d grid that are tagged as fracture faces. | ||
num_fracture_faces_from_matrix = ( | ||
self.mdg.subdomains(dim=2)[0].tags["fracture_faces"].sum() | ||
) | ||
|
||
# Loop over the fractures, fetch the projections to the 2d grid, and count the | ||
# number of non-zero entries in the projection matrix. | ||
non_zero_projection_primary = 0 | ||
for mg in self.mdg.interfaces(dim=1): | ||
proj_primary = mg.mortar_to_primary_avg() | ||
non_zero_projection_primary += proj_primary.nnz | ||
# A matching grid would have equality here. We have refined the mortar grid, | ||
# hence there should be more items in the projection matrix than there are | ||
# fracture faces in the matrix grid. | ||
assert non_zero_projection_primary > num_fracture_faces_from_matrix | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For cartesian grids, we could check that these match the refinement ratios, right? |
||
|
||
# For the mortar grid vs fracture grids, we can simply do a cell count. We do | ||
# not know precisely how many cells there will be in each, but with the given | ||
# refinement settings (above), they should at least not be equal. | ||
for mg in self.mdg.interfaces(dim=1): | ||
_, sd_secondary = self.mdg.interface_to_subdomain_pair(mg) | ||
# Multiply by two since there are two sides of the mortar. | ||
assert 2 * sd_secondary.num_cells != mg.num_cells | ||
|
||
def test_benchmark_regular(self): | ||
"""Test the mdg generator for the regular case of the benchmark study. | ||
|
||
|
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 are "rates"?