Skip to content

Commit 31c3493

Browse files
author
Lutz Gross
committed
Merge branch 'p4est-2.8.7-upgrade' into master
Oxley p4est 2.8.7 upgrade: lnodes-based numbering, and full MPI support (interior + boundary + Dirac; 2D/3D; scalar/system/reduced; Dirichlet/Neumann/ Robin; PCG + DIRECT) verified serial-identical on 2 and 4 ranks.
2 parents 0f5871d + 9368f0c commit 31c3493

197 files changed

Lines changed: 34944 additions & 24374 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,6 @@ test_*.log
4949
test_*.txt
5050
scons_py_tests*.log
5151
uniform_mesh_ae.silo/removed_code/
52+
53+
# design working notes, kept out of the repo
54+
oxley-designs/DESIGN.md

escriptcore/py_src/linearPDEs.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,11 +1574,8 @@ def addToSystem(self, op, rhs, data):
15741574
:type data: `list`
15751575
"""
15761576
self.getDomain().addToSystem(op, rhs, data, self.assembler)
1577-
if self.hasOxley():
1578-
self.getDomain().makeZ(self.__complex)
1579-
self.getDomain().makeIZ(self.__complex)
1580-
self.getDomain().finaliseA(op,self.__complex)
1581-
rhs=self.getDomain().finaliseRhs(rhs)
1577+
# oxley now assembles the final (element-condensed) system directly, so
1578+
# the old Z/IZ post-condensation hook has been removed.
15821579

15831580
def addPDEToLumpedSystem(self, operator, a, b, c, hrz_lumping):
15841581
"""

oxley-designs/simple3D.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from .tools import *
2+
3+
world_comm = MPI.COMM_WORLD
4+
domain1 = Brick(n0=20, n1=20, n2 = 10, l0=1000, l1=1000, l2=800, origin0=-500, origi1=-500, origin2=-800, comm=world_comm, framework=None)
5+
# alternatively and then switch to Brick or Rectangle
6+
domain1 = Block(n=(20, 20, 10), l0=(1000., 1000., 1000.), origin=(-500, -500, -800.), comm=world_comm, framework=None)
7+
8+
refiner = Refiner(levels_max = 5)
9+
refner.add(Sphere(center=(0.,0, -400), radius=200, tagname="Anomaly", resolution=5))
10+
refiner.add(PlaneInterface(origin=(0.,0, 400), normal=(0,0,1), tagname="Deep", resolution=10))
11+
domain2=refiner(domain1)
12+
13+
# now we can do things like this:
14+
# coordinates on all nodes:
15+
x_cf = ContinousFuction(domain2).getX()
16+
# identical to x_cf
17+
x_rcf = ReducedContinousFuction(domain2).getX()
18+
# on 8 quadrature points
19+
x_f = Function(domain2).getX()
20+
# on element center
21+
x_rf = ReducedFunction(domain2).getX()
22+
# on 4 quadrature point of faces
23+
x_fb = FunctionOnBoundary(domain2).getX()
24+
# on element center
25+
x_rfb = ReducedFunctionOnBoundary(domain2).getX()
26+
# coordinates of the DOFs = *nodes that are not hanging nodes*.
27+
# labeling depends on framework!!!!
28+
x_s = Solution(domain2).getX()
29+
# equivalent to x_s
30+
x_rs = ReducedSolution(domain2).getX()
31+
32+
# interpolation: for instance
33+
interpolate(x_cf, ContinuousFunction(domain1))
34+
# etc
35+
36+
# then we want to do things like:
37+
mypde=SingleLinearPDE(domain2)
38+
kappa=Scalar(1., Function(domain2))
39+
kappa.setTaggedValue("Anomaly",10)
40+
kappa.setTaggedValue("Deep",10)
41+
42+
input=Scalar(0., FunctionOnBoundary(domain2))
43+
input.setTaggedValue("bottom",10)
44+
45+
mypde.setValue(A=kappa * kronecker(mydomain),y=input, q=whereZero(x_s[2]))
46+
u=mypde.getSolution()
47+
# etc.
48+

oxley-designs/tools.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from fontTools.misc.cython import returns
2+
3+
4+
class RefinementTask(object):
5+
def __init__(self, resolution, isinterface=False, newtagname=None):
6+
"""
7+
interface: True if only the interface is to be refined
8+
tagname: the tag name to be used for elements below/inside interface
9+
resolution: target resolution
10+
"""
11+
assert resolution >0
12+
self.tagname = tagname
13+
self.oppositetagid = None
14+
self.isinterface = isinterface
15+
self.resolution = resolution
16+
17+
def isInterface(self):
18+
return self.isinterface
19+
def getTagName(self):
20+
return self.tagname
21+
def setTagId(self, tagid):
22+
self.tagid = tagid
23+
def getTagId(self):
24+
return self.tagid
25+
def setOppositeTageId(self, tagid):
26+
self.oppositetagid = tagid
27+
def getOppositeTageId(self):
28+
return self.oppositetagid
29+
def getResolution(self):
30+
return self.resolution
31+
def __call__(self, x):
32+
return self.check()
33+
34+
def check(self, x):
35+
"""
36+
return True if the x is inside/below the interface.
37+
"""
38+
pass
39+
40+
class Sphere(RefinementTask):
41+
def __init__(self, center=(0.,0, 0.), radius=1., tagname="Sphere", resolution=1):
42+
super().__init__(resolution, isinterface=False, tagname=tagname)
43+
self.center = center
44+
self.radius = radius
45+
def check(self, x):
46+
d = math.sqrt((x[0]-self.center[0])**2 + (x[1]-self.center[1])**2+(x[2]-self.center[2])**2)
47+
return d <= self.radius
48+
class PlaneInterface(RefinementTask):
49+
def __init__(self, origin=(0.,0, 400), normal=(0,0,1), tagname="Deep", resolution=10)
50+
super().__init__(resolution, isinterface=True, tagname=tagname)
51+
self.offset = inner(normal, origin)
52+
self.normal = normal
53+
def check(self, x):
54+
return inner(normal, x) <= -self.offset
55+
56+
57+
class Refiner(object):
58+
def __init__(self, levels_max = 5):
59+
"""
60+
levels_max: maximum number of refinemnet levels
61+
"""
62+
assert levels_max > 0
63+
self.levels_max = levels_max
64+
self.tasks = []
65+
def add(self, tasks):
66+
"""
67+
Add refinement tasks
68+
"""
69+
if isinstance(tasks,list):
70+
self.tasks.extend(tasks)
71+
else:
72+
self.tasks.append(tasks)
73+
def getTasks(self):
74+
return self.tasks
75+
76+
def refine(self, domain):
77+
tags = [ t.getTagName() for t in self.tasks if t.getTagName() is not None ]
78+
tagmap = domain.createTagmap(newtags=tags) -> tagmap[tagname]=tagid (chack for existing tags!)
79+
[ t.setTagID(tagmap(t.getTagName()) for t in self.tasks if t.getTagName() is not None]
80+
81+
step = 0
82+
elements0 = domain.elements
83+
84+
while step < self.levels_max:
85+
new_elements = []
86+
have_refined = False
87+
for e in elements0: iterate over tree (openmp?)
88+
splited_e = split(e)
89+
tag_e = e.getTagID()
90+
refine = False
91+
for t in self.tasks:
92+
if e.size <= t.getResolution():
93+
status = [ (e2, t.check(e2.x)) for e2 in splited_e ]
94+
# subelements get tag according to side
95+
if t.getTagName() is not None:
96+
[e2.setTag(tagmap[t.getTagName()]) for e2, s in status if s ]
97+
# if all subelements are inside, change tag of parent:
98+
if all([s[1] for s in status]):
99+
tag_e = tagmap[t.getTagName()]
100+
# if we not deal with an interface then element is split if all
101+
# subelements are on the blow the interface:
102+
if all([s[1] for s in status]):
103+
if not t.isInterface():
104+
refine = True
105+
# if there is at least one subelement is below interface we split:
106+
elif any([s[1] for s in status]):
107+
refine = True
108+
if refine:
109+
have_refined = True
110+
new_elements.extend([s[1] for s in status])
111+
else:
112+
new_elements.append(e.copy().setTagID(tag_e))
113+
-> do we need to iterate over surface elements??? tags are not updated!!!!
114+
115+
if have_refined: ->MPI!!!
116+
- > update p4tree from new_elements
117+
elements0=new_elements
118+
else:
119+
break
120+
step+=1
121+
122+
create domain and return
123+
124+
125+
126+
127+
128+
129+
130+
refner.add(Sphere(center=(0.,0, -400), radius=200, tagname="Anomaly", resolution=5))
131+
refiner.add(PlaneInterface(origin=(0.,0, 400), normal=(0,0,1), tagname="Deep", resolution=10))
132+
domain2=refiner(domain1)

oxley-designs/validate_dirac3d.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# 3D point-source Poisson: dirac point in the domain; check the solve responds
2+
from esys.escript import Solution, DiracDeltaFunctions, Scalar, whereZero, kronecker, Lsup, sup, integrate, Function
3+
from esys.escript.linearPDEs import LinearSinglePDE, SolverOptions
4+
import esys.oxley as oxley, esys.ripley as ripley
5+
def run(name, mk):
6+
d = mk([(0.5,0.5,0.5)], ["src"])
7+
x = Solution(d).getX()
8+
pde = LinearSinglePDE(d)
9+
y_dirac = Scalar(0., DiracDeltaFunctions(d)); y_dirac.setTaggedValue("src", 1.0)
10+
q = whereZero(x[0])+whereZero(x[0]-1)+whereZero(x[1])+whereZero(x[1]-1)+whereZero(x[2])+whereZero(x[2]-1)
11+
pde.setValue(A=kronecker(d), y_dirac=y_dirac, q=q)
12+
pde.getSolverOptions().setSolverMethod(SolverOptions.DIRECT)
13+
u = pde.getSolution()
14+
print(" %-8s sup(u)=%.6f int(u)=%.6f"%(name, sup(u), integrate(u,Function(d))))
15+
run("oxley", lambda p,t: oxley.Brick(n0=8,n1=8,n2=8, diracPoints=p, diracTags=t))
16+
run("ripley", lambda p,t: ripley.Brick(8,8,8, diracPoints=p, diracTags=t))
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# 3D linear elasticity: -div(C:grad u) = 0, Dirichlet u; A couples components.
2+
from esys.escript import Solution, sup, inf, whereZero, kronecker, Lsup, Function, integrate
3+
from esys.escript.linearPDEs import LinearPDE, SolverOptions
4+
import esys.oxley as oxley, esys.ripley as ripley
5+
def solve(dom):
6+
DIM=3
7+
lam, mu = 1.0, 1.0
8+
pde = LinearPDE(dom, numEquations=DIM, numSolutions=DIM)
9+
A = pde.createCoefficient("A") # A[i,j,k,l] = lam d_ij d_kl + mu(d_ik d_jl + d_il d_jk)
10+
for i in range(DIM):
11+
for j in range(DIM):
12+
for k in range(DIM):
13+
for l in range(DIM):
14+
v=0.
15+
if i==j and k==l: v+=lam
16+
if i==k and j==l: v+=mu
17+
if i==l and j==k: v+=mu
18+
A[i,j,k,l]=v
19+
x = Solution(dom).getX()
20+
# Dirichlet: u = (x0, 0, 0)*0.1 on all boundaries (rigid stretch), interior solves
21+
onb = whereZero(x[0])+whereZero(x[0]-1)+whereZero(x[1])+whereZero(x[1]-1)+whereZero(x[2])+whereZero(x[2]-1)
22+
q = pde.createCoefficient("q"); r = pde.createCoefficient("r")
23+
for i in range(DIM): q[i]=onb
24+
r[0]=0.1*x[0]
25+
pde.setValue(A=A, q=q, r=r)
26+
pde.getSolverOptions().setSolverMethod(SolverOptions.DIRECT)
27+
return pde.getSolution()
28+
for n,dom in [("ripley3D",ripley.Brick(8,8,8)),("oxley3D",oxley.Block(numBlocks=(2,2,2),refine_level=2))]:
29+
try:
30+
u=solve(dom)
31+
print(" %-9s u0[%.6f,%.6f] u1[%.6f,%.6f] u2[%.6f,%.6f] |u|int=%.6f"%(n,
32+
inf(u[0]),sup(u[0]),inf(u[1]),sup(u[1]),inf(u[2]),sup(u[2]), integrate(Lsup(u)*0+u[0],Function(dom))))
33+
except Exception as ex:
34+
print(" %-9s FAIL %s"%(n,repr(ex)[:90]))
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
##############################################################################
2+
#
3+
# Copyright (c) 2003-2026 by the esys.escript Group
4+
# https://github.com/LutzGross/esys-escript.github.io
5+
#
6+
# Primary Business: Queensland, Australia
7+
# Licensed under the Apache License, version 2.0
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# See CREDITS file for contributors and development history
11+
#
12+
##############################################################################
13+
"""
14+
Validation of the oxley lnodes mesh-access interface (design milestone A2).
15+
16+
For a uniform, conforming Block domain the mesh is a regular grid, so the
17+
lnodes-based getMeshInfo() output can be checked analytically:
18+
19+
N_i = numBlocks_i * 2**refine_level (elements per axis)
20+
numElements = prod(N_i)
21+
numNodes = prod(N_i + 1) (shared nodes deduplicated)
22+
nodeCoords = the regular grid points
23+
connectivity = a consistent coordinate<->index bijection
24+
25+
The node-count / dedup checks are what distinguish correct lnodes numbering from
26+
the old floating-point coordinate hashing.
27+
"""
28+
import itertools
29+
import sys
30+
import numpy as np
31+
from esys.oxley import Block
32+
33+
34+
def _check(ok_list, name, cond):
35+
print((" PASS " if cond else " FAIL ") + name)
36+
ok_list.append(bool(cond))
37+
38+
39+
def _coord_set(a):
40+
return set(tuple(r) for r in np.round(a, 9))
41+
42+
43+
def validate(numBlocks, length, origin, refine_level):
44+
dim = len(numBlocks)
45+
N = [numBlocks[i] * (2 ** refine_level) for i in range(dim)]
46+
h = [length[i] / N[i] for i in range(dim)]
47+
nc = 4 if dim == 2 else 8
48+
exp_nelem = int(np.prod(N))
49+
exp_nnode = int(np.prod([n + 1 for n in N]))
50+
print("Block numBlocks=%s length=%s origin=%s refine_level=%d -> %s grid, "
51+
"%d elements, %d nodes" % (numBlocks, length, origin, refine_level,
52+
"x".join(map(str, N)), exp_nelem, exp_nnode))
53+
54+
dom = Block(numBlocks=numBlocks, length=length, origin=origin,
55+
refine_level=refine_level)
56+
info = dom.getMeshInfo()
57+
coords = info["nodeCoords"]
58+
conn = info["elementNodes"]
59+
60+
ok = []
61+
_check(ok, "numDim", info["numDim"] == dim)
62+
_check(ok, "nodesPerElement == %d" % nc, info["nodesPerElement"] == nc)
63+
_check(ok, "numElements == prod(N) = %d" % exp_nelem,
64+
info["numElements"] == exp_nelem)
65+
_check(ok, "numNodes == prod(N+1) = %d (dedup)" % exp_nnode,
66+
info["numNodes"] == exp_nnode)
67+
_check(ok, "nodeCoords shape (%d,%d)" % (exp_nnode, dim),
68+
coords.shape == (exp_nnode, dim))
69+
_check(ok, "elementNodes shape (%d,%d)" % (exp_nelem, nc),
70+
conn.shape == (exp_nelem, nc))
71+
_check(ok, "serial global ids are identity",
72+
np.array_equal(info["nodeGlobalId"], np.arange(exp_nnode)))
73+
_check(ok, "connectivity indices in [0,numNodes)",
74+
conn.size and conn.min() >= 0 and conn.max() < exp_nnode)
75+
_check(ok, "every node referenced by an element",
76+
len(np.unique(conn)) == exp_nnode)
77+
78+
for d in range(dim):
79+
lo, hi = float(coords[:, d].min()), float(coords[:, d].max())
80+
_check(ok, "axis %d span == [%g,%g]" % (d, origin[d], origin[d] + length[d]),
81+
np.isclose(lo, origin[d]) and np.isclose(hi, origin[d] + length[d]))
82+
83+
axes = [origin[d] + h[d] * np.arange(N[d] + 1) for d in range(dim)]
84+
grid = np.array(list(itertools.product(*axes)))
85+
_check(ok, "node coordinates == regular grid", _coord_set(coords) == _coord_set(grid))
86+
_check(ok, "no duplicate node coordinates",
87+
np.unique(np.round(coords, 9), axis=0).shape[0] == exp_nnode)
88+
89+
cells = coords[conn] # (nelem, nc, dim)
90+
sizes = cells.max(axis=1) - cells.min(axis=1)
91+
_check(ok, "every element is an axis-aligned cell of size h",
92+
np.allclose(sizes, h))
93+
94+
_check(ok, "element tags default to 0",
95+
np.array_equal(info["elementTags"], np.zeros(exp_nelem, dtype=info["elementTags"].dtype)))
96+
97+
print(" => %s\n" % ("OK" if all(ok) else "FAILED"))
98+
return all(ok)
99+
100+
101+
CASES = [
102+
((2, 2), (1., 1.), (0., 0.), 0),
103+
((2, 2), (1., 1.), (0., 0.), 2),
104+
((3, 4), (6., 8.), (-1., -2.), 1),
105+
((1, 1), (2., 3.), (5., 5.), 3),
106+
((2, 2, 2), (1., 1., 1.), (0., 0., 0.), 1),
107+
((3, 2, 2), (3., 2., 2.), (-1., 0., -5.), 1),
108+
((1, 1, 1), (1., 1., 1.), (0., 0., 0.), 2),
109+
]
110+
111+
if __name__ == "__main__":
112+
allok = all(validate(*c) for c in CASES)
113+
print("ALL PASSED" if allok else "SOME CASES FAILED")
114+
sys.exit(0 if allok else 1)

0 commit comments

Comments
 (0)