ddfem 0.0.0__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. ddfem-1.0.0/LICENSE +19 -0
  2. ddfem-1.0.0/PKG-INFO +25 -0
  3. ddfem-1.0.0/README.md +9 -0
  4. ddfem-1.0.0/ddfem/base_model.py +200 -0
  5. ddfem-1.0.0/ddfem/boundary.py +214 -0
  6. ddfem-1.0.0/ddfem/dune.py +3 -0
  7. ddfem-1.0.0/ddfem/geometry/__init__.py +18 -0
  8. ddfem-1.0.0/ddfem/geometry/arc.py +46 -0
  9. ddfem-1.0.0/ddfem/geometry/box.py +34 -0
  10. ddfem-1.0.0/ddfem/geometry/circle.py +39 -0
  11. ddfem-1.0.0/ddfem/geometry/domain.py +37 -0
  12. ddfem-1.0.0/ddfem/geometry/domain_dune.py +66 -0
  13. ddfem-1.0.0/ddfem/geometry/helpers.py +35 -0
  14. ddfem-1.0.0/ddfem/geometry/pie.py +31 -0
  15. ddfem-1.0.0/ddfem/geometry/primitive_base.py +279 -0
  16. ddfem-1.0.0/ddfem/geometry/vesica.py +48 -0
  17. ddfem-1.0.0/ddfem/model2ufl.py +145 -0
  18. ddfem-1.0.0/ddfem/transformers/DDM1.py +94 -0
  19. ddfem-1.0.0/ddfem/transformers/Fitted.py +67 -0
  20. ddfem-1.0.0/ddfem/transformers/Mix0.py +161 -0
  21. ddfem-1.0.0/ddfem/transformers/NNS.py +143 -0
  22. ddfem-1.0.0/ddfem/transformers/NS.py +151 -0
  23. ddfem-1.0.0/ddfem/transformers/__init__.py +5 -0
  24. ddfem-1.0.0/ddfem/transformers/transformer_base.py +126 -0
  25. ddfem-1.0.0/ddfem.egg-info/PKG-INFO +25 -0
  26. ddfem-1.0.0/ddfem.egg-info/SOURCES.txt +30 -0
  27. ddfem-1.0.0/ddfem.egg-info/requires.txt +1 -0
  28. ddfem-1.0.0/pyproject.toml +28 -0
  29. ddfem-0.0.0/PKG-INFO +0 -5
  30. ddfem-0.0.0/ddfem.egg-info/PKG-INFO +0 -5
  31. ddfem-0.0.0/ddfem.egg-info/SOURCES.txt +0 -6
  32. ddfem-0.0.0/pyproject.toml +0 -8
  33. {ddfem-0.0.0 → ddfem-1.0.0}/ddfem/__init__.py +0 -0
  34. {ddfem-0.0.0 → ddfem-1.0.0}/ddfem.egg-info/dependency_links.txt +0 -0
  35. {ddfem-0.0.0 → ddfem-1.0.0}/ddfem.egg-info/top_level.txt +0 -0
  36. {ddfem-0.0.0 → ddfem-1.0.0}/setup.cfg +0 -0
ddfem-1.0.0/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
ddfem-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: ddfem
3
+ Version: 1.0.0
4
+ Summary: Diffuse domain finite element solver
5
+ Author-email: Luke Benfield <luke.benfield@warwick.ac.uk>, Andreas Dedner <a.s.dedner@warwick.ac.uk>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://...
8
+ Project-URL: Issues, https://...
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: fenics-ufl>=2022
15
+ Dynamic: license-file
16
+
17
+ # Diffuse Domain Finite Element Methods
18
+
19
+ This is a package for solving complex PDEs based on the diffuse domain idea.
20
+
21
+ Build package by running
22
+
23
+ ```bash
24
+ python3 -m build
25
+ ```
ddfem-1.0.0/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # Diffuse Domain Finite Element Methods
2
+
3
+ This is a package for solving complex PDEs based on the diffuse domain idea.
4
+
5
+ Build package by running
6
+
7
+ ```bash
8
+ python3 -m build
9
+ ```
@@ -0,0 +1,200 @@
1
+ import inspect
2
+
3
+ import ufl
4
+ from ufl import as_matrix, as_vector, diff, dot, inner, outer, variable, zero
5
+ from ufl.algorithms.ad import expand_derivatives
6
+ from ufl.algorithms.apply_derivatives import apply_derivatives
7
+
8
+
9
+ class BaseModel:
10
+ boundary = {}
11
+
12
+ @staticmethod
13
+ def function(self, *args, **kwargs):
14
+ uh = self._function(*args, **kwargs)
15
+ uh.boundary = self.boundary
16
+ return uh
17
+
18
+ # U_t + div[F_c(x,t,U) - F_v(x,t,U,grad[U]) ] = S(x,t,U, grad[U]).
19
+
20
+ @classmethod
21
+ def F_c_lin(cls, t, x, U):
22
+ U = variable(U)
23
+ d = diff(cls.F_c(t, x, U), U)
24
+ d = apply_derivatives(expand_derivatives(d))
25
+ return d
26
+
27
+ # U.ufl_shape == (1,)
28
+ # F_c(U).ufl_shape == (1, 2,)
29
+ # diff(F_c(U), U).ufl_shape == (1, 2, 1)
30
+ # n.ufl_shape == (2,)
31
+ #
32
+ # s, t = F_c(U).ufl_shape
33
+ # f_c = as_matrix([[dot(d[i, j, :], U) for j in range(t)] for i in range(s)])
34
+ #
35
+ # w, = U.ufl_shape
36
+ # convec = as_vector([dot([f_c[w, :], n) for i in range(w)]) # f_c * n
37
+ #
38
+ # switch order
39
+
40
+ @classmethod
41
+ def F_c_lin_mult(cls, t, x, U, n):
42
+ G = cls.F_c_lin(t, x, U)
43
+ # try:
44
+ # d = dot(G, n)
45
+ # print("F_c dot")
46
+ # return d
47
+ # except:
48
+ m, d, m_ = G.ufl_shape
49
+ return as_matrix([[dot(G[i, :, k], n) for k in range(m_)] for i in range(m)])
50
+
51
+ @classmethod
52
+ def F_v_lin(cls, t, x, U, DU):
53
+ DU = variable(DU)
54
+ d = diff(cls.F_v(t, x, U, DU), DU)
55
+ d = apply_derivatives(expand_derivatives(d))
56
+ return d
57
+
58
+ @classmethod
59
+ def F_v_lin_mult(cls, t, x, U, DU, v):
60
+ G = cls.F_v_lin(t, x, U, DU)
61
+ # try:
62
+ # d = dot(G, v)
63
+ # print("F_v dot")
64
+ # return d
65
+ # except:
66
+ m, d = v.ufl_shape
67
+ return as_matrix(
68
+ [[inner(G[i, k, :, :], v) for k in range(d)] for i in range(m)]
69
+ )
70
+
71
+ # avoid issue with variable capturing in lambdas in the for loop below
72
+ # https://www.geeksforgeeks.org/why-do-python-lambda-defined-in-a-loop-with-different-values-all-return-the-same-result/
73
+ def _createV(v, U=None):
74
+ if U is None:
75
+ return lambda t, x, u: v # classify
76
+ return lambda t, x: v(t, x, U) # jumpV
77
+
78
+ def _createF(v, U=None, DU=None, N=None):
79
+ if U is None and DU is None and N is None:
80
+ return lambda t, x, u, _, n: v(t, x, u, n) # classify
81
+ elif U and N and DU is None:
82
+ return lambda t, x: v(t, x, U, N) # jumpAF
83
+ elif U and N and DU:
84
+ return lambda t, x: v(t, x, U, DU, N) # jumpDF
85
+
86
+ @staticmethod
87
+ def classify_boundary(Model):
88
+ boundaryDict = getattr(Model, "boundary_d", {})
89
+
90
+ boundaryAFlux = {} # Fluxes for the advection term
91
+ boundaryDFlux = {} # Fluxes for the diffusion term
92
+ boundaryValue = {} # Boundary values for Dirichlet
93
+
94
+ hasAdvFlux = hasattr(Model, "F_c")
95
+ hasDiffFlux = hasattr(Model, "F_v")
96
+
97
+ for k, f in boundaryDict.items():
98
+ if isinstance(f, (tuple, list)):
99
+ assert (
100
+ hasAdvFlux and hasDiffFlux
101
+ ), "two boundary fluxes provided but only one bulk flux given"
102
+ boundaryAFlux[k], boundaryDFlux[k] = f
103
+ # (f[0](t, x, u, n), f[1](t, x, u, grad(u), n))
104
+
105
+ elif isinstance(f, ufl.core.expr.Expr):
106
+ boundaryValue[k] = BaseModel._createV(f)
107
+
108
+ else:
109
+ num_args = len(inspect.signature(f).parameters)
110
+
111
+ if num_args == 3:
112
+ boundaryValue[k] = f # f(t, x, u)
113
+
114
+ elif num_args == 4:
115
+ if hasAdvFlux and not hasDiffFlux:
116
+ boundaryAFlux[k] = f # f(t, x, u, n)
117
+ elif not hasAdvFlux and hasDiffFlux:
118
+ boundaryDFlux[k] = BaseModel._createF(f)
119
+ else:
120
+ assert not (
121
+ hasAdvFlux and hasDiffFlux
122
+ ), "one boundary fluxes provided but two bulk fluxes given"
123
+
124
+ else:
125
+ raise NotImplementedError(f"boundary function {num_args} arguments")
126
+
127
+ return boundaryAFlux, boundaryDFlux, boundaryValue
128
+
129
+ @staticmethod
130
+ def boundaryTerms(Model, domain):
131
+ boundaryAFlux, boundaryDFlux, boundaryValue = BaseModel.classify_boundary(Model)
132
+ bd_weight = []
133
+ bN_weight = []
134
+
135
+ for model_key in boundaryValue.keys():
136
+ phi_i_proj = domain.bndProjSDFs(model_key)
137
+ bd_weight.append(phi_i_proj)
138
+
139
+ for model_key in {*boundaryAFlux.keys(), *boundaryDFlux.keys()}:
140
+ phi_i_proj = domain.bndProjSDFs(model_key)
141
+ bN_weight.append(phi_i_proj)
142
+
143
+ def total_weight(t, x):
144
+ weight = 1e-10 # tol
145
+ for w in bd_weight + bN_weight:
146
+ weight += w(t, x)
147
+ return weight
148
+
149
+ # perhaps switch methods around so that gExt is setup and then
150
+ # jumpD does sum(w)*U - gExt. But then the exception needs to be caught...
151
+ def jumpV(t, x, U, U1=None):
152
+ jdv = zero(U.ufl_shape)
153
+
154
+ if U1 is None:
155
+ U1 = U
156
+
157
+ for g, w in zip(boundaryValue.values(), bd_weight):
158
+ g_tmp = BaseModel._createV(v=g, U=U)
159
+ g_ext = domain.omega.boundary_extend(g_tmp)
160
+
161
+ jdv += w(t, x) * (U1 - g_ext(t, x))
162
+
163
+ return jdv / total_weight(t, x)
164
+
165
+ if len(boundaryValue) == 0:
166
+ gExt = None
167
+ else:
168
+
169
+ def gExt(t, x, U):
170
+ z = zero(U.ufl_shape)
171
+ return -jumpV(t, x, U, z)
172
+
173
+ # the models expect to be provided with a unit normal in the boundary fluxes
174
+ def jumpDF(t, x, U, DU, Fv):
175
+ # (sigma.n-gN)*ds(N) = - wN ( sigma.Dphi + gN|Dphi| )
176
+ # = wN ( (-sigma.Dphi) - gN(t,x,-Dphi/|Dphi|)|Dphi| )
177
+ # = wN ( sigma.sn - gN(t,x,sn) ) with sn = -Dphi
178
+ jdf = zero(U.ufl_shape)
179
+
180
+ fv_scaled = Fv * domain.scaledNormal(x)
181
+ for g, w in zip(boundaryDFlux.values(), bN_weight):
182
+ g_tmp = BaseModel._createF(v=g, U=U, DU=DU, N=domain.scaledNormal(x))
183
+ g_ext = domain.omega.boundary_extend(g_tmp)
184
+
185
+ jdf += w(t, x) * (fv_scaled - g_ext(t, x))
186
+
187
+ return jdf / total_weight(t, x)
188
+
189
+ def jumpAF(t, x, U):
190
+ jda = zero(U.ufl_shape)
191
+
192
+ for g, w in zip(boundaryAFlux.values(), bN_weight):
193
+ g_tmp = BaseModel._createF(v=g, U=U, N=domain.scaledNormal(x))
194
+ g_ext = domain.omega.boundary_extend(g_tmp)
195
+
196
+ jda += -w(t, x) * g_ext(t, x)
197
+
198
+ return jda / total_weight(t, x)
199
+
200
+ return jumpV, gExt, jumpDF, jumpAF
@@ -0,0 +1,214 @@
1
+ import inspect
2
+
3
+ import ufl
4
+ from ufl import replace, zero
5
+ from ufl.algorithms.ad import expand_derivatives
6
+ from ufl.core.expr import Expr
7
+
8
+ from .geometry.domain import Domain
9
+
10
+
11
+ class BoundaryCondition:
12
+ def __init__(self, value):
13
+ self.value = value
14
+
15
+ def __call__(self, *args, **kwds):
16
+ return self.value(*args, **kwds)
17
+
18
+
19
+ class BndValue(BoundaryCondition):
20
+ def __init__(self, value):
21
+ if isinstance(value, ufl.core.expr.Expr):
22
+ super().__init__(lambda t, x, u: value)
23
+ else:
24
+ num_args = len(inspect.signature(value).parameters)
25
+ if num_args == 2:
26
+ super().__init__(lambda t, x, u: value(t, x))
27
+ elif num_args == 3:
28
+ super().__init__(value)
29
+ # elif num_args == 4:
30
+ # self.SPLIT = True
31
+ # super().__init__(lambda t, x, u: value(t, x, u, n, n))
32
+ else:
33
+ raise ValueError(f"Boundary has {num_args} arguments.")
34
+
35
+
36
+ class BndFlux_v(BoundaryCondition):
37
+ pass
38
+
39
+
40
+ class BndFlux_c(BoundaryCondition):
41
+ pass
42
+
43
+
44
+ def classify_boundary(boundary_dict):
45
+
46
+ boundary_flux_cs = {} # Fluxes for the advection term
47
+ boundary_flux_vs = {} # Fluxes for the diffusion term
48
+ boundary_values = {} # Boundary values for Dirichlet
49
+
50
+ for k, f in boundary_dict.items():
51
+
52
+ if isinstance(k, (Expr, str)):
53
+ ids = [k]
54
+ elif callable(k):
55
+ ids = [k]
56
+ else:
57
+ try:
58
+ ids = []
59
+ for kk in k:
60
+ ids += [kk]
61
+ except TypeError:
62
+ ids = [k]
63
+
64
+ if isinstance(f, (tuple, list)):
65
+ assert len(f) == 2, "too many boundary fluxes provided"
66
+ if isinstance(f[0], BndFlux_v) and isinstance(f[1], BndFlux_c):
67
+ boundary_flux_vs.update([(kk, f[0]) for kk in ids])
68
+ boundary_flux_cs.update([(kk, f[1]) for kk in ids])
69
+
70
+ elif isinstance(f[0], BndFlux_c) and isinstance(f[1], BndFlux_v):
71
+ boundary_flux_vs.update([(kk, f[1]) for kk in ids])
72
+ boundary_flux_cs.update([(kk, f[0]) for kk in ids])
73
+
74
+ else:
75
+ raise ValueError("Need AFlux and DFlux")
76
+
77
+ elif isinstance(f, BndFlux_v):
78
+ boundary_flux_vs.update([(kk, f) for kk in ids])
79
+
80
+ elif isinstance(f, BndFlux_c):
81
+ boundary_flux_cs.update([(kk, f) for kk in ids])
82
+
83
+ elif isinstance(f, BndValue):
84
+ boundary_values.update([(kk, f) for kk in ids])
85
+
86
+ else:
87
+ print(type(k), type(f))
88
+ raise NotImplementedError(f"unknown boundary type {k} : {f}")
89
+
90
+ return boundary_flux_cs, boundary_flux_vs, boundary_values
91
+
92
+
93
+ def boundary_validation(Model, override_boundary_dict=None):
94
+ if override_boundary_dict is not None:
95
+ boundary_dict = override_boundary_dict
96
+ else:
97
+ boundary_dict = Model.boundary
98
+
99
+ hasFlux_c = hasattr(Model, "F_c")
100
+ hasFlux_v = hasattr(Model, "F_v")
101
+
102
+ boundary_flux_cs, boundary_flux_vs, boundary_values = classify_boundary(
103
+ boundary_dict
104
+ )
105
+
106
+ if hasFlux_c and hasFlux_v:
107
+ assert len(boundary_flux_cs) == len(
108
+ boundary_flux_vs
109
+ ), "two bulk fluxes given, but one boundary fluxes provided"
110
+
111
+ if not hasFlux_c:
112
+ assert len(boundary_flux_cs) == 0, "No bulk Advection, but boundary flux given"
113
+
114
+ if not hasFlux_v:
115
+ assert len(boundary_flux_vs) == 0, "No bulk diffusion, but boundary flux given"
116
+
117
+ assert boundary_values.keys().isdisjoint(boundary_flux_cs)
118
+ assert boundary_values.keys().isdisjoint(boundary_flux_vs)
119
+
120
+ return boundary_flux_cs, boundary_flux_vs, boundary_values
121
+
122
+
123
+ class BoundaryTerms:
124
+ def __init__(self, Model, domainDescription):
125
+ self.Model = Model
126
+
127
+ if isinstance(domainDescription, Domain):
128
+ self.domain = domainDescription
129
+ else:
130
+ self.domain = Domain(domainDescription)
131
+
132
+ condition = lambda k: isinstance(k, str)
133
+ self.diffuse = {k: v for k, v in Model.boundary.items() if condition(k)}
134
+ self.physical = {k: v for k, v in Model.boundary.items() if not condition(k)}
135
+
136
+ self.boundary_flux_cs, self.boundary_flux_vs, self.boundary_values = (
137
+ boundary_validation(self.Model, override_boundary_dict=self.diffuse)
138
+ )
139
+
140
+ self.bV_weight = []
141
+ self.bF_weight = []
142
+
143
+ for model_key in self.boundary_values.keys():
144
+ phi_i_proj = self.domain.bndProjSDFs(model_key)
145
+ self.bV_weight.append(phi_i_proj)
146
+
147
+ for model_key in {*self.boundary_flux_cs.keys(), *self.boundary_flux_vs.keys()}:
148
+ phi_i_proj = self.domain.bndProjSDFs(model_key)
149
+ self.bF_weight.append(phi_i_proj)
150
+
151
+ if not self.boundary_values:
152
+ self.BndValueExt = None
153
+
154
+ if not self.boundary_flux_vs:
155
+ self.BndFlux_vExt = None
156
+
157
+ def _total_weight(self, x):
158
+ weight = 1e-10 # tol
159
+ for w_func in self.bV_weight + self.bF_weight:
160
+ weight += w_func(x)
161
+ return weight
162
+
163
+ def _boundary_extend(self, g, x):
164
+ g_tmp = expand_derivatives(g)
165
+ return replace(g_tmp, {x: self.domain.omega.boundary_projection(x)})
166
+
167
+ # perhaps switch methods around so that BndValueExt is setup and then
168
+ # jumpD does sum(w)*U - BndValueExt. But then the exception needs to be caught...
169
+ def jumpV(self, t, x, U, U1=None):
170
+ jdv = zero(U.ufl_shape)
171
+
172
+ if U1 is None:
173
+ U1 = U
174
+
175
+ for g_func, w_func in zip(self.boundary_values.values(), self.bV_weight):
176
+ g_tmp = g_func(t, x, U) # g_func is a callable from self.boundary_values
177
+ g_ext = self._boundary_extend(g_tmp, x)
178
+ jdv += w_func(x) * (U1 - g_ext)
179
+
180
+ return jdv / self._total_weight(x)
181
+
182
+ def BndValueExt(self, t, x, U):
183
+ # called if self.BndValueExt was not set to None in __init__
184
+ z = zero(U.ufl_shape)
185
+ return -self.jumpV(t, x, U, z)
186
+
187
+ def jumpFv(self, t, x, U, DU, Fv):
188
+ # (sigma.n-gN)*ds(N) = - wN ( sigma.Dphi + gN|Dphi| )
189
+ # = wN ( (-sigma.Dphi) - gN(t,x,-Dphi/|Dphi|)|Dphi| )
190
+ # = wN ( sigma.sn - gN(t,x,sn) ) with sn = -Dphi
191
+ jdf = zero(U.ufl_shape)
192
+
193
+ fv_scaled = Fv * self.domain.scaled_normal(x)
194
+ for g_func, w_func in zip(self.boundary_flux_vs.values(), self.bF_weight):
195
+ g_tmp = g_func(t, x, U, DU, self.domain.normal(x))
196
+ g_ext = self._boundary_extend(g_tmp, x)
197
+ jdf += w_func(x) * (fv_scaled - g_ext * self.domain.surface_delta(x))
198
+
199
+ return jdf / self._total_weight(x)
200
+
201
+ def BndFlux_vExt(self, t, x, U, DU):
202
+ # called if self.BndFlux_vExt was not set to None in __init__
203
+ z = zero(DU.ufl_shape)
204
+ return -self.jumpFv(t, x, U, DU, z)
205
+
206
+ def BndFlux_cExt(self, t, x, U):
207
+ jda = zero(U.ufl_shape)
208
+
209
+ for g_func, w_func in zip(self.boundary_flux_cs.values(), self.bF_weight):
210
+ g_tmp = g_func(t, x, U, self.domain.normal(x))
211
+ g_ext = self._boundary_extend(g_tmp, x)
212
+ jda += w_func(x) * -g_ext * self.domain.surface_delta(x)
213
+
214
+ return -jda / self._total_weight(x)
@@ -0,0 +1,3 @@
1
+ import geometry
2
+ from geometry import dunedomain
3
+ geometry.domain =
@@ -0,0 +1,18 @@
1
+ from .arc import Arc
2
+ from .box import Box
3
+ from .circle import Circle, Sphere
4
+ from .pie import Pie
5
+ from .vesica import Vesica
6
+ from .primitive_base import (
7
+ SDF,
8
+ Intersection,
9
+ Invert,
10
+ Round,
11
+ Rotate,
12
+ Round,
13
+ Scale,
14
+ Subtraction,
15
+ Translate,
16
+ Union,
17
+ Xor,
18
+ )
@@ -0,0 +1,46 @@
1
+ from ufl import as_vector, conditional, cos, sin
2
+
3
+ from .helpers import ufl_length
4
+ from .primitive_base import ORIGIN, SDF
5
+
6
+
7
+ class Arc(SDF):
8
+ """SDF for an arc:
9
+
10
+ Provides the signed distance function for an arc given the
11
+ radius, angle of opening, the width, and the center (which defaults to the origin).
12
+ """
13
+ def __init__(self, radius, angle, width, center=ORIGIN, *args, **kwargs):
14
+ super().__init__(*args, **kwargs)
15
+ self.radius = radius
16
+ self.angle = angle # angle of opening
17
+ self.width = width
18
+
19
+ assert len(center) == 2
20
+ if isinstance(center, (list, tuple)):
21
+ center = as_vector(center)
22
+ self.center = center
23
+
24
+ def __repr__(self):
25
+ return f"Arc({self.radius}, {self.angle}, {self.width}, {self.center})"
26
+
27
+ def sdf(self, x):
28
+ y0_abs = abs(x[1])
29
+ coords = as_vector((x[0], y0_abs))
30
+
31
+ center_radius = self.radius - self.width / 2
32
+
33
+ distance = ufl_length(x) - center_radius
34
+
35
+ edge_point = center_radius * as_vector((cos(self.angle), sin(self.angle)))
36
+
37
+ left_coords = coords - edge_point
38
+
39
+ sign_dist = conditional(
40
+ (sin(self.angle) * x[0]) > (cos(self.angle) * y0_abs),
41
+ ufl_length(left_coords),
42
+ abs(distance),
43
+ )
44
+
45
+ sign_dist -= self.width / 2
46
+ return sign_dist
@@ -0,0 +1,34 @@
1
+ from ufl import as_vector, conditional
2
+ from ufl import max_value as Max
3
+ from ufl import min_value as Min
4
+
5
+ from .helpers import ufl_length
6
+ from .primitive_base import ORIGIN, SDF
7
+
8
+
9
+ class Box(SDF):
10
+ def __init__(self, width, height, center=ORIGIN, *args, **kwargs):
11
+ super().__init__(*args, **kwargs)
12
+ self.width = width
13
+ self.height = height
14
+
15
+ assert len(center) == 2
16
+ if isinstance(center, (list, tuple)):
17
+ center = as_vector(center)
18
+ self.center = center
19
+
20
+ def __repr__(self):
21
+ return f"Box({self.width}, {self.height}, {self.center})"
22
+
23
+ def sdf(self, x):
24
+ # shift x
25
+ center_x = x - self.center
26
+ # aux functions
27
+ w0 = abs(center_x[0]) - self.width / 2
28
+ w1 = abs(center_x[1]) - self.height / 2
29
+
30
+ g = Max(w0, w1)
31
+
32
+ q = as_vector((Max(w0, 0), Max(w1, 0)))
33
+
34
+ return conditional(g > 0, ufl_length(q), g)
@@ -0,0 +1,39 @@
1
+ from ufl import as_vector
2
+
3
+ from .helpers import ufl_length
4
+ from .primitive_base import ORIGIN, SDF
5
+
6
+
7
+ class Circle(SDF):
8
+ def __init__(self, radius, center=ORIGIN, *args, **kwargs):
9
+ super().__init__(*args, **kwargs)
10
+ self.radius = radius
11
+
12
+ assert len(center) == 2
13
+ if isinstance(center, (list, tuple)):
14
+ center = as_vector(center)
15
+ self.center = center
16
+
17
+ def __repr__(self):
18
+ return f"Circle({self.radius}, {self.center})"
19
+
20
+ def sdf(self, x):
21
+ center_x = x - self.center
22
+ return ufl_length(center_x) - self.radius
23
+
24
+ class Sphere(SDF):
25
+ def __init__(self, radius, center=ORIGIN, *args, **kwargs):
26
+ super().__init__(*args, **kwargs)
27
+ self.radius = radius
28
+
29
+ if isinstance(center, (list, tuple)):
30
+ center = as_vector(center)
31
+ self.center = center
32
+
33
+ def __repr__(self):
34
+ return f"Circle({self.radius}, {self.center})"
35
+
36
+ def sdf(self, x):
37
+ center_x = x - self.center
38
+ return ufl_length(center_x) - self.radius
39
+
@@ -0,0 +1,37 @@
1
+ from ufl import conditional, dot, grad, sqrt
2
+
3
+
4
+ class Domain:
5
+ def __init__(self, omega):
6
+ self.omega = omega
7
+
8
+ def phi(self, x):
9
+ tol = 1e-10
10
+ return (1 - tol) * self.omega.phi(x) + tol
11
+
12
+ def scaled_normal(self, x):
13
+ return -grad(self.phi(x))
14
+
15
+ def surface_delta(self, x):
16
+ return sqrt(dot(self.scaled_normal(x), self.scaled_normal(x)))
17
+
18
+ def normal(self, x):
19
+ tol = 1e-10
20
+ sd = conditional(self.surface_delta(x) > tol, self.surface_delta(x), tol)
21
+ return self.scaled_normal(x) / sd # grad(self.omega(x))
22
+
23
+ def bndSDFs(self, SDFname):
24
+ sdf = self.omega.search(SDFname)
25
+ if sdf is None:
26
+ raise ValueError(f"No SDF with name {SDFname}")
27
+ return sdf
28
+
29
+ def bndProjSDFs(self, SDFname):
30
+ sdf = self.omega.search(SDFname)
31
+ if sdf is None:
32
+ raise ValueError(f"No SDF with name {SDFname}")
33
+ return self.generate_projSDF(sdf)
34
+
35
+ def generate_projSDF(self, sdf):
36
+ w = lambda x: sdf.phi(x) * (1 - sdf.phi(x))
37
+ return lambda x: w(self.omega.boundary_projection(x))