Skip to content

Commit ab71703

Browse files
committed
Updating fem pde code to use new manufactured api solutions. Still need to port over transient PDE fem tests
1 parent f0c55a9 commit ab71703

File tree

3 files changed

+605
-226
lines changed

3 files changed

+605
-226
lines changed

pyapprox/pde/collocation/manufactured_solutions.py

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import copy
55

66
import sympy as sp
7+
from pyapprox.util.backends.template import BackendMixin, Array
78
from pyapprox.util.backends.numpy import NumpyMixin
89
from pyapprox.util.sympy import (
910
_evaluate_sp_lambda,
@@ -19,7 +20,7 @@ class ManufacturedSolution(ABC):
1920
def __init__(
2021
self,
2122
nvars: int,
22-
bkd=NumpyMixin,
23+
bkd: BackendMixin = NumpyMixin,
2324
oned: bool = False,
2425
):
2526
self._bkd = bkd
@@ -53,14 +54,14 @@ def time_symbol(self):
5354
def all_symbols(self):
5455
return self.cartesian_symbols() + self.time_symbol()
5556

56-
def _steady_expression_to_function(self, expr):
57+
def _steady_expression_to_function(self, expr: List):
5758
all_symbs = self.cartesian_symbols()
5859
expr_lambda = sp.lambdify(all_symbs, expr, "numpy")
5960
return partial(
6061
_evaluate_sp_lambda, expr_lambda, bkd=self._bkd, oned=self._oned
6162
)
6263

63-
def _steady_expression_list_to_function(self, exprs):
64+
def _steady_expression_list_to_function(self, exprs: List):
6465
all_symbs = self.cartesian_symbols()
6566
expr_lambda = [sp.lambdify(all_symbs, expr, "numpy") for expr in exprs]
6667
return partial(
@@ -70,7 +71,7 @@ def _steady_expression_list_to_function(self, exprs):
7071
oned=False,
7172
)
7273

73-
def _steady_expression_list_of_lists_to_function(self, exprs):
74+
def _steady_expression_list_of_lists_to_function(self, exprs: List):
7475
all_symbs = self.cartesian_symbols()
7576
expr_lambda = [
7677
[sp.lambdify(all_symbs, expr, "numpy") for expr in row]
@@ -93,7 +94,7 @@ def _transient_expression_to_function(self, expr):
9394
oned=self._oned,
9495
)
9596

96-
def _transient_expression_list_to_function(self, exprs):
97+
def _transient_expression_list_to_function(self, exprs: List):
9798
all_symbs = self.all_symbols()
9899
expr_lambda = [sp.lambdify(all_symbs, expr, "numpy") for expr in exprs]
99100
return partial(
@@ -103,7 +104,7 @@ def _transient_expression_list_to_function(self, exprs):
103104
oned=False,
104105
)
105106

106-
def _transient_expression_list_of_lists_to_function(self, exprs):
107+
def _transient_expression_list_of_lists_to_function(self, exprs: List):
107108
all_symbs = self.all_symbols()
108109
expr_lambda = [
109110
[sp.lambdify(all_symbs, expr, "numpy") for expr in row]
@@ -116,7 +117,7 @@ def _transient_expression_list_of_lists_to_function(self, exprs):
116117
oned=False,
117118
)
118119

119-
def is_transient(self):
120+
def is_transient(self) -> bool:
120121
return self._bkd.any(self._bkd.array(list(self.transient.values())))
121122

122123
def _expressions_to_functions(self):
@@ -160,7 +161,7 @@ def _expressions_to_functions(self):
160161
self._transient_expression_to_function(expr)
161162
)
162163

163-
def nvars(self):
164+
def nvars(self) -> int:
164165
return self._nvars
165166

166167
def _set_expression_from_bool(self, name: str, expr, transient: bool):
@@ -182,7 +183,7 @@ def _set_expression(self, name: str, expr, expr_str: str):
182183
transient = False
183184
self._set_expression_from_bool(name, expr, transient)
184185

185-
def __repr__(self):
186+
def __repr__(self) -> str:
186187
fields = f"{self._expressions}".split(",")
187188
expr_str = ",\n".join(fields)[1:-1].replace("'", "")
188189
return "{0}(\n {1}\n)".format(self.__class__.__name__, expr_str)
@@ -256,7 +257,9 @@ def _sympy_reaction_expressions(self, react_str, sol_str):
256257
# react_prime_expr = react_prime_expr.subs(
257258
# sol_symb, self._expressions["solution"]
258259
# )
260+
print(react_str)
259261
react_str = react_str.replace("u", "({0})".format(sol_str))
262+
print(react_str)
260263
return react_str, sp.sympify(react_str) # , react_prime_expr
261264

262265
def sympy_reaction_expressions(self):
@@ -832,3 +835,59 @@ def sympy_expressions(self):
832835
self._set_expression("viscosity", visc_expr, self._visc_str)
833836
self._set_expression("flux", flux_exprs, self._sol_str)
834837
self._expressions["forcing"] += forc_expr
838+
839+
840+
class ManufacturedStokes(VectorSolutionMixin, ManufacturedSolution):
841+
def __init__(
842+
self,
843+
sol_strs: List[str],
844+
nvars: int,
845+
navier_stokes: bool,
846+
bkd: BackendMixin = NumpyMixin,
847+
oned: bool = False,
848+
):
849+
self._navier_stokes = navier_stokes
850+
if len(sol_strs) != nvars + 1:
851+
raise ValueError(f"len(sol_strs)!={nvars+1}")
852+
self._vel_strs = sol_strs[:nvars]
853+
self._pres_str = sol_strs[nvars]
854+
super().__init__(sol_strs, nvars, bkd, oned)
855+
856+
def sympy_expressions(self):
857+
cartesian_symbs = self.cartesian_symbols()
858+
exprs = self._expressions["solution"]
859+
vel_exprs = exprs[: self._nvars]
860+
pres_expr = exprs[self._nvars]
861+
vel_forc_exprs = []
862+
for vel, s1 in zip(vel_exprs, cartesian_symbs):
863+
vel_forc_exprs.append(
864+
sum([-vel.diff(s2, 2) for s2 in cartesian_symbs])
865+
+ pres_expr.diff(s1, 1)
866+
)
867+
if self._navier_stokes:
868+
vel_forc_exprs[-1] += sum(
869+
[
870+
u * vel.diff(s2, 1)
871+
for u, s2 in zip(vel_exprs, cartesian_symbs)
872+
]
873+
)
874+
pres_forc_expr = sum(
875+
[vel.diff(s, 1) for vel, s in zip(vel_exprs, cartesian_symbs)]
876+
)
877+
878+
vel_grad_exprs = [
879+
[v.diff(s, 1) for s in cartesian_symbs] for v in vel_exprs
880+
]
881+
pres_grad_expr = [pres_expr.diff(s, 1) for s in cartesian_symbs]
882+
flux_exprs = vel_grad_exprs + [pres_grad_expr]
883+
self._set_expression("flux", flux_exprs, self._sol_strs[0])
884+
885+
self._set_expression("vel_forcing", vel_forc_exprs, self._sol_strs[0])
886+
self._set_expression(
887+
"pres_forcing", pres_forc_expr, self._sol_strs[-1]
888+
)
889+
890+
forc_exprs = vel_exprs + [pres_forc_expr]
891+
self._expressions["forcing"] = [
892+
f + g for f, g in zip(self._expressions["forcing"], forc_exprs)
893+
]

pyapprox/pde/galerkin/physics.py

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,10 @@ def __call__(self, xx):
630630
# E.g. when manufactured solution, diff etc. string does not have x
631631
# in it. If not dependent on x then must use 1e-16*x
632632
if xx.ndim != vals.ndim:
633-
raise RuntimeError(f"vals has the incorrect shape {vals.shape}")
633+
raise ValueError(
634+
f"vals.ndim is incorrect. Was {vals.ndim} "
635+
f"should be {xx.ndim}"
636+
)
634637
return vals[:, 0]
635638

636639
def __repr__(self):
@@ -673,14 +676,23 @@ def __repr__(self):
673676

674677

675678
class FEMVectorFunctionFromCallable(FromCallableMixin, FEMVectorFunction):
679+
def __init__(self, fun: callable, name: str = None, swapaxes: bool = True):
680+
super().__init__(fun, name)
681+
self._swapeaxes = swapaxes
682+
676683
def __call__(self, xx):
677684
vals = self._fun(xx)
678685
# if self._fun is not implemented correctly this will fail
679686
# E.g. when manufactured solution, diff etc. string does not have x
680687
# in it. If not dependent on x then must use 1e-16*x
681-
assert xx.ndim == vals.ndim
688+
if xx.ndim != vals.ndim:
689+
raise ValueError(
690+
f"vals.ndim is incorrect. Was {vals.ndim} "
691+
f"should be {xx.ndim}"
692+
)
682693
# put vals in format required by FEM
683-
vals = np.swapaxes(vals, 0, 1)
694+
if self._swapeaxes:
695+
vals = np.swapaxes(vals, 0, 1)
684696
return vals
685697

686698

@@ -781,6 +793,7 @@ def apply_dirichlet_boundary_conditions(
781793

782794

783795
class LinearAdvectionDiffusionReaction(AdvectionDiffusionReaction):
796+
# Only to be used to get initial guess for nonlinear advection diffusion
784797
def __init__(
785798
self,
786799
mesh: Mesh,
@@ -793,14 +806,16 @@ def __init__(
793806
react_fun: Optional[FEMNonLinearOperator] = None,
794807
):
795808
if not isinstance(diff_fun, FEMScalarFunction):
796-
raise ValueError(
797-
"diff_fun must be an instance of FEMScalarFunction"
809+
raise TypeError(
810+
"diff_fun must be an instance of FEMScalarFunction "
811+
f"but was {diff_fun.__class__.__name__}"
798812
)
799813
if react_fun is not None and not isinstance(
800-
react_fun, FEMVectorFunction
814+
react_fun, FEMScalarFunction
801815
):
802-
raise ValueError(
803-
"react_fun must be an instance of FEMVectorFunction"
816+
raise TypeError(
817+
"react_fun must be an instance of FEMScalarFunction "
818+
f"but was {react_fun.__class__.__name__}"
804819
)
805820
self._diff_fun = diff_fun
806821
self._react_fun = react_fun
@@ -816,9 +831,7 @@ def _set_funs(self) -> List:
816831
return funs + [self._react_fun, self._forc_fun]
817832

818833
def init_guess(self) -> np.ndarray:
819-
return np.ones(
820-
(self.basis.N),
821-
)
834+
return np.ones((self.basis.N,))
822835

823836

824837
class NonLinearAdvectionDiffusionReaction(AdvectionDiffusionReaction):
@@ -867,7 +880,8 @@ def _set_funs(self) -> List:
867880
return funs + [self._linear_diff_fun, self._diff_op, self._react_op]
868881

869882

870-
class Helmholtz(LinearAdvectionDiffusionReaction):
883+
# class Helmholtz(LinearAdvectionDiffusionReaction):
884+
class Helmholtz(NonLinearAdvectionDiffusionReaction):
871885
def __init__(
872886
self,
873887
mesh: Mesh,
@@ -878,34 +892,46 @@ def __init__(
878892
forc_fun: Optional[Callable[[np.ndarray], np.ndarray]] = None,
879893
):
880894

895+
if len(bndry_conds[1]) > 0 or len(bndry_conds[1]) > 0:
896+
raise NotImplementedError(
897+
"Currently tests do not pass with Robin or Neumann BCs"
898+
)
881899
if forc_fun is None:
882-
forc_fun = self._zero_forcing
900+
forc_fun = FEMScalarFunctionFromCallable(
901+
self._zero_forcing, "forcing"
902+
)
883903

884904
super().__init__(
885905
mesh,
886906
element,
887907
basis,
888908
bndry_conds,
889-
self._unit_diff,
909+
FEMScalarFunctionFromCallable(self._unit_diff, "diffusion"),
890910
forc_fun,
891-
self._zero_vel,
911+
FEMVectorFunctionFromCallable(self._zero_vel, "velocity", False),
912+
FEMNonLinearOperatorFromCallable(
913+
lambda x, u: 0 * u, lambda x, u: 0 * u
914+
),
892915
wave_number_fun,
893916
)
894917

918+
def init_guess(self) -> np.ndarray:
919+
return np.ones((self.basis.N,))
920+
895921
def _unit_diff(self, x):
896922
# negative is taken because advection diffusion code solves
897923
# -k*\nabla^2 u + c*u = f
898924
# but Helmholtz solves
899925
# \nabla^2 u + c*u = 0 which is equivalent when k=-1
900-
return -1 + 0 * x[0]
926+
return -1.0 + 0.0 * x
901927

902928
def _zero_vel(self, x):
903929
# Helmholtz is a special case of the advection diffusion reaction
904930
# equation when there is no advection, i.e. velocity field is zero
905-
return 0 * x
931+
return 0.0 * x
906932

907933
def _zero_forcing(self, x):
908-
return 0 * x[0]
934+
return 0.0 * x
909935

910936

911937
class Stokes(Physics):
@@ -920,6 +946,10 @@ def __init__(
920946
pres_forc_fun: FEMScalarFunction,
921947
viscosity: float = 1.0,
922948
):
949+
if len(bndry_conds[1]) > 0 or len(bndry_conds[1]) > 0:
950+
raise NotImplementedError(
951+
"Currently tests do not pass with Robin or Neumann BCs"
952+
)
923953
super().__init__(mesh, element, basis, bndry_conds)
924954

925955
# if not isinstance(vel_forc_fun, FEMVectorFunction):

0 commit comments

Comments
 (0)