Skip to content
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

Sneddon manufactured solution. #1314

Draft
wants to merge 42 commits into
base: develop
Choose a base branch
from
Draft

Sneddon manufactured solution. #1314

wants to merge 42 commits into from

Conversation

isakhammer
Copy link
Contributor

@isakhammer isakhammer commented Jan 24, 2025

Proposed changes

This pull request improves upon the previously submitted PR (#1283). Runtime on my computer is approx 12 seconds.

This pull request includes reimplementing Sneddon's analytical solution as outlined in https://doi.org/10.1007/s10596-020-10002-5. Sneddon's solution provides an analytical method to compute the normal jump displacement for a pressurized fracture under linear elasticity conditions. The boundary element method is then integrated to compute semi-analytical boundary values, which are subsequently used to verify the MPSA numerical method for an open fracture. This process validates the accuracy and convergence of the proposed numerical method, making it well-suited as a test case.

Types of changes

What types of changes does this PR introduce to PorePy?
Put an x in the boxes that apply.

  • Minor change (e.g., dependency bumps, broken links).
  • Bugfix (non-breaking change which fixes an issue).
  • New feature (non-breaking change which adds functionality).
  • Breaking change (fix or feature that would cause existing functionality to not work as expected).
  • [x ] Testing (contribution related to testing of existing or new functionality).
  • Documentation (contribution related to adding, improving, or fixing documentation).
  • Maintenance (e.g., improve logic and performance, remove obsolete code).
  • Other:

Checklist

Put an x in the boxes that apply or explain briefly why the box is not relevant.

  • The documentation is up-to-date.
  • Static typing is included in the update. Still remains some mypy errors.
  • This PR does not duplicate existing functionality.
  • The update is covered by the test suite (including tests added in the PR).
  • If new skipped tests have been introduced in this PR, pytest was run with the --run-skipped flag.

@isakhammer isakhammer marked this pull request as ready for review January 27, 2025 12:14
@IvarStefansson IvarStefansson marked this pull request as draft January 31, 2025 14:17
src/porepy/viz/data_saving_model_mixin.py Outdated Show resolved Hide resolved
tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved

bound_face_centers = g.face_centers[:, bound_faces]

for i in range(0, u_a.size):
Copy link
Contributor

Choose a reason for hiding this comment

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

Add some documentation.

tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
# Set the type of west and east boundaries to Dirichlet. North and south are
# Neumann by default.
bc = pp.BoundaryConditionVectorial(sd, bounds.all_bf, "dir")
frac_face = sd.tags["fracture_faces"]
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there's a function for this. internal_bc_to_dirichlet or whatever.

Copy link
Contributor

Choose a reason for hiding this comment

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

I also believe this to be the default behaviour - all Dirichlet on external and internal boundaries.

Copy link
Contributor

Choose a reason for hiding this comment

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

Changed these lines to internal_to_dirichlet

tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
"""Test observed order of convergence.
"""
# We the order of L2 convergence on the fracture of displacement to be about 1.0
assert 0.85 < actual_ooc["ooc_displacement"]
Copy link
Contributor

Choose a reason for hiding this comment

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

The test needs to be more precise.

from porepy.geometry.distances import point_pointset


def compute_eta(pointset_centers: np.ndarray, center: np.ndarray) -> np.ndarray:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is a separate function needed for this, since it just wraps the other one?

tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
return cons * np.sqrt(1 - np.power(eta / a, 2))


def transform(xc: np.ndarray, x: np.ndarray, alpha: float) -> np.ndarray:
Copy link
Contributor

Choose a reason for hiding this comment

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

Here and everywhere, would it be helpful to annotate the array sizes?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would rename this function, e.g. tranform_boundary_face_coordinates_bem

Copy link
Contributor

Choose a reason for hiding this comment

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

The name may be improved, but the exact name may depend on context, i.e., Veljko's suggestion to move it.

tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
tests/functional/test_sneddon_2d.py Show resolved Hide resolved
tests/functional/test_sneddon_2d.py Outdated Show resolved Hide resolved
from porepy.geometry.distances import point_pointset


def compute_eta(pointset_centers: np.ndarray, center: np.ndarray) -> np.ndarray:
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a shallow wrapper which can be eliminated.

return bem_centers


def analytical_displacements(
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be moved to class representing the analytical solution.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree

return cons * np.sqrt(1 - np.power(eta / a, 2))


def transform(xc: np.ndarray, x: np.ndarray, alpha: float) -> np.ndarray:
Copy link
Contributor

Choose a reason for hiding this comment

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

Only used once in below method, can be eliminated and code put there.
In general, all these helper methods before the classes are created seem to be part of the analytical solution and can be moved there. Would also help reduce the signatures.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good observation. Please implement!

u_bc = assign_bem(sd, h / 2, box_faces, self.theta, bem_centers, u_a, self.poi)
return u_bc

def exact_sol_fracture(
Copy link
Contributor

Choose a reason for hiding this comment

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

The first tuple element of return value serves currently no purpose anywhere.


# Computing the numerical displacement jump along the fracture on the fracture
# cell centers.
u_n: pp.ad.Operator = nd_vec_to_normal @ self.displacement_jump(frac_sd)
Copy link
Contributor

Choose a reason for hiding this comment

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

type of variable u_n is typed explicitly, but then immediately changed in next line.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, dear. Rename below to u_n_value?

u_n = u_n.value(self.equation_system)

# Checking convergence specifically on the fracture
u_a = self.exact_sol.exact_sol_fracture(self.mdg)[1]
Copy link
Contributor

Choose a reason for hiding this comment

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

Add truncation of around fracture tips to reach linear convergence (@IvarStefansson suggested 15% for example, but trial and error required).

Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't hard-code anything. I think we should allow the user to control this. Can be achieved by having a separate helper method and a parameter controlling the radius being removed around the tips.

}

# Convert angle to radians and compute fracture points
params["frac_pts"] = compute_frac_pts(
Copy link
Contributor

Choose a reason for hiding this comment

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

Since all the parameters required are given as model parameters, this computation can be moved to the custom geometry class of the setup (including the function used to compute the fracture points)

Copy link
Contributor

Choose a reason for hiding this comment

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

Moved it, please check set_fractures.


# Calculate and return the order of convergence for the displacement
order_dict = conv_analysis.order_of_convergence(
conv_analysis.run_analysis(), data_range=slice(None, None, None)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the default value for data_range.

model_params=copy.deepcopy(params),
levels=2,
spatial_refinement_rate=2,
temporal_refinement_rate=1,
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the point of this argument if the rate is 1? i.e. time step remains the same.

Copy link
Contributor

Choose a reason for hiding this comment

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

True. The default is one, so we might as well remove it.

Copy link
Contributor

@IvarStefansson IvarStefansson left a comment

Choose a reason for hiding this comment

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

Thanks for your efforts, @Yuriyzabegaev and @vlipovac. I have made some comments. Please implement everything you feel confident about, possibly consulting eachother and me.

tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved
return bem_centers


def analytical_displacements(
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree

return cons * np.sqrt(1 - np.power(eta / a, 2))


def transform(xc: np.ndarray, x: np.ndarray, alpha: float) -> np.ndarray:
Copy link
Contributor

Choose a reason for hiding this comment

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

Good observation. Please implement!

return cons * np.sqrt(1 - np.power(eta / a, 2))


def transform(xc: np.ndarray, x: np.ndarray, alpha: float) -> np.ndarray:
Copy link
Contributor

Choose a reason for hiding this comment

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

The name may be improved, but the exact name may depend on context, i.e., Veljko's suggestion to move it.

tests/functional/setups/manu_sneddon_2d.py Outdated Show resolved Hide resolved

# Computing the numerical displacement jump along the fracture on the fracture
# cell centers.
u_n: pp.ad.Operator = nd_vec_to_normal @ self.displacement_jump(frac_sd)
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, dear. Rename below to u_n_value?

u_n = u_n.value(self.equation_system)

# Checking convergence specifically on the fracture
u_a = self.exact_sol.exact_sol_fracture(self.mdg)[1]
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't hard-code anything. I think we should allow the user to control this. Can be achieved by having a separate helper method and a parameter controlling the radius being removed around the tips.

tests/functional/test_sneddon_2d.py Outdated Show resolved Hide resolved
model_params=copy.deepcopy(params),
levels=2,
spatial_refinement_rate=2,
temporal_refinement_rate=1,
Copy link
Contributor

Choose a reason for hiding this comment

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

True. The default is one, so we might as well remove it.

tests/functional/test_sneddon_2d.py Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants