emerge 0.4.7__py3-none-any.whl → 0.4.9__py3-none-any.whl

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.

Potentially problematic release.


This version of emerge might be problematic. Click here for more details.

Files changed (78) hide show
  1. emerge/__init__.py +14 -14
  2. emerge/_emerge/__init__.py +42 -0
  3. emerge/_emerge/bc.py +197 -0
  4. emerge/_emerge/coord.py +119 -0
  5. emerge/_emerge/cs.py +523 -0
  6. emerge/_emerge/dataset.py +36 -0
  7. emerge/_emerge/elements/__init__.py +19 -0
  8. emerge/_emerge/elements/femdata.py +212 -0
  9. emerge/_emerge/elements/index_interp.py +64 -0
  10. emerge/_emerge/elements/legrange2.py +172 -0
  11. emerge/_emerge/elements/ned2_interp.py +645 -0
  12. emerge/_emerge/elements/nedelec2.py +140 -0
  13. emerge/_emerge/elements/nedleg2.py +217 -0
  14. emerge/_emerge/geo/__init__.py +24 -0
  15. emerge/_emerge/geo/horn.py +107 -0
  16. emerge/_emerge/geo/modeler.py +449 -0
  17. emerge/_emerge/geo/operations.py +254 -0
  18. emerge/_emerge/geo/pcb.py +1244 -0
  19. emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
  20. emerge/_emerge/geo/pcb_tools/macro.py +79 -0
  21. emerge/_emerge/geo/pmlbox.py +204 -0
  22. emerge/_emerge/geo/polybased.py +529 -0
  23. emerge/_emerge/geo/shapes.py +427 -0
  24. emerge/_emerge/geo/step.py +77 -0
  25. emerge/_emerge/geo2d.py +86 -0
  26. emerge/_emerge/geometry.py +510 -0
  27. emerge/_emerge/howto.py +214 -0
  28. emerge/_emerge/logsettings.py +5 -0
  29. emerge/_emerge/material.py +118 -0
  30. emerge/_emerge/mesh3d.py +730 -0
  31. emerge/_emerge/mesher.py +339 -0
  32. emerge/_emerge/mth/common_functions.py +33 -0
  33. emerge/_emerge/mth/integrals.py +71 -0
  34. emerge/_emerge/mth/optimized.py +357 -0
  35. emerge/_emerge/periodic.py +263 -0
  36. emerge/_emerge/physics/__init__.py +0 -0
  37. emerge/_emerge/physics/microwave/__init__.py +1 -0
  38. emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
  39. emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
  40. emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  41. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  42. emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  43. emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
  44. emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
  45. emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
  46. emerge/_emerge/physics/microwave/periodic.py +82 -0
  47. emerge/_emerge/physics/microwave/port_functions.py +53 -0
  48. emerge/_emerge/physics/microwave/sc.py +175 -0
  49. emerge/_emerge/physics/microwave/simjob.py +147 -0
  50. emerge/_emerge/physics/microwave/sparam.py +138 -0
  51. emerge/_emerge/physics/microwave/touchstone.py +140 -0
  52. emerge/_emerge/plot/__init__.py +0 -0
  53. emerge/_emerge/plot/display.py +394 -0
  54. emerge/_emerge/plot/grapher.py +93 -0
  55. emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  56. emerge/_emerge/plot/pyvista/__init__.py +1 -0
  57. emerge/_emerge/plot/pyvista/display.py +931 -0
  58. emerge/_emerge/plot/pyvista/display_settings.py +24 -0
  59. emerge/_emerge/plot/simple_plots.py +551 -0
  60. emerge/_emerge/plot.py +225 -0
  61. emerge/_emerge/projects/__init__.py +0 -0
  62. emerge/_emerge/projects/_gen_base.txt +32 -0
  63. emerge/_emerge/projects/_load_base.txt +24 -0
  64. emerge/_emerge/projects/generate_project.py +40 -0
  65. emerge/_emerge/selection.py +596 -0
  66. emerge/_emerge/simmodel.py +444 -0
  67. emerge/_emerge/simulation_data.py +411 -0
  68. emerge/_emerge/solver.py +993 -0
  69. emerge/_emerge/system.py +54 -0
  70. emerge/cli.py +19 -0
  71. emerge/lib.py +1 -1
  72. emerge/plot.py +1 -1
  73. {emerge-0.4.7.dist-info → emerge-0.4.9.dist-info}/METADATA +7 -6
  74. emerge-0.4.9.dist-info/RECORD +78 -0
  75. emerge-0.4.9.dist-info/entry_points.txt +2 -0
  76. emerge-0.4.7.dist-info/RECORD +0 -9
  77. emerge-0.4.7.dist-info/entry_points.txt +0 -2
  78. {emerge-0.4.7.dist-info → emerge-0.4.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,339 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ import gmsh
19
+ from .geometry import GeoVolume, GeoObject, GeoSurface
20
+ from .selection import Selection, FaceSelection
21
+ from .periodic import PeriodicCell
22
+ import numpy as np
23
+ from typing import Iterable, Callable
24
+ from loguru import logger
25
+ from enum import Enum
26
+ from .bc import Periodic
27
+
28
+ class MeshError(Exception):
29
+ pass
30
+
31
+ class Algorithm2D(Enum):
32
+ MESHADAPT = 1
33
+ AUTOMATIC = 2
34
+ INITIAL_MESH_ONLY = 3
35
+ DELAUNAY = 5
36
+ FRONTAL_DELAUNAY = 6
37
+ BAMG = 7
38
+ FRONTAL_DELAUNAY_QUADS = 8
39
+ PACKING_PARALLELOGRAMS = 9
40
+ QUASI_STRUCTURED_QUAD = 11
41
+
42
+ #(1: Delaunay, 3: Initial mesh only, 4: Frontal, 7: MMG3D, 9: R-tree, 10: HXT)
43
+
44
+ class Algorithm3D(Enum):
45
+ DELAUNAY = 1
46
+ INITIAL_MESH_ONLY = 3
47
+ FRONTAL = 4
48
+ MMG3D = 7
49
+ RTREE = 9
50
+ HXT = 10
51
+
52
+ _DOM_TO_STR = {
53
+ 0: "point",
54
+ 1: "edge",
55
+ 2: "face",
56
+ 3: "volume",
57
+ }
58
+ def unpack_lists(_list: list[list], collector: list = None) -> list:
59
+ '''Unpack a recursive list of lists'''
60
+ if collector is None:
61
+ collector = []
62
+ for item in _list:
63
+ if isinstance(item, list):
64
+ unpack_lists(item, collector)
65
+ else:
66
+ collector.append(item)
67
+
68
+ return collector
69
+
70
+ class Mesher:
71
+
72
+ def __init__(self):
73
+ self.objects: list[GeoObject] = []
74
+ self.size_definitions: list[tuple[int, float]] = []
75
+ self.mesh_fields: list[int] = []
76
+ self.min_size: float = None
77
+ self.max_size: float = None
78
+ self.periodic_cell: PeriodicCell = None
79
+
80
+ @property
81
+ def edge_tags(self) -> list[int]:
82
+ return [tag[1] for tag in gmsh.model.getEntities(1)]
83
+
84
+ @property
85
+ def face_tags(self) -> list[int]:
86
+ return [tag[1] for tag in gmsh.model.getEntities(2)]
87
+
88
+ @property
89
+ def node_tags(self) -> list[int]:
90
+ return [tag[1] for tag in gmsh.model.getEntities(0)]
91
+
92
+ @property
93
+ def volumes(self) -> list[GeoVolume]:
94
+ return [obj for obj in self.objects if isinstance(obj, GeoVolume)]
95
+
96
+ @property
97
+ def domain_boundary_face_tags(self) -> list[int]:
98
+ '''Get the face tags of the domain boundaries'''
99
+ domain_tags = gmsh.model.getEntities(3)
100
+ tags = gmsh.model.getBoundary(domain_tags, combined=True, oriented=False)
101
+ return [int(tag[1]) for tag in tags]
102
+
103
+ @property
104
+ def domain_internal_face_tags(self) -> list[int]:
105
+ alltags = self.face_tags
106
+ boundary = self.domain_boundary_face_tags
107
+ return [tag for tag in alltags if tag not in boundary]
108
+
109
+ def _get_periodic_bcs(self) -> list[Periodic]:
110
+ if self.periodic_cell is None:
111
+ return []
112
+ return self.periodic_cell._bcs
113
+
114
+ def _check_ready(self) -> None:
115
+ if self.max_size is None or self.min_size is None:
116
+ raise MeshError('Either maximum or minimum mesh size is undefined. Make sure \
117
+ to set the simulation frequency range before calling mesh instructions.')
118
+
119
+ def submit_objects(self, objects: list[GeoObject]) -> None:
120
+ """Takes al ist of GeoObjects and computes the fragment.
121
+
122
+ Args:
123
+ objects (list[GeoObject]): The set of GeoObjects
124
+ """
125
+ if not isinstance(objects, list):
126
+ objects = [objects,]
127
+
128
+ objects = unpack_lists(objects)
129
+ embeddings = []
130
+
131
+ gmsh.model.occ.synchronize()
132
+
133
+ final_dimtags = unpack_lists([domain.dimtags for domain in objects])
134
+
135
+ dom_mapping = dict()
136
+ for dom in objects:
137
+ embeddings.extend(dom._embeddings)
138
+ for dt in dom.dimtags:
139
+ dom_mapping[dt] = dom
140
+
141
+
142
+ embedding_dimtags = unpack_lists([emb.dimtags for emb in embeddings])
143
+
144
+ tag_mapping: dict[int, dict] = {0: dict(),
145
+ 1: dict(),
146
+ 2: dict(),
147
+ 3: dict()}
148
+ if len(objects) > 0:
149
+ dimtags, output_mapping = gmsh.model.occ.fragment(final_dimtags, embedding_dimtags)
150
+
151
+ for domain, mapping in zip(final_dimtags + embedding_dimtags, output_mapping):
152
+ tag_mapping[domain[0]][domain[1]] = [o[1] for o in mapping]
153
+ for dom in objects:
154
+ dom.update_tags(tag_mapping)
155
+ else:
156
+ dimtags = final_dimtags
157
+
158
+ self.objects = objects
159
+
160
+ gmsh.model.occ.synchronize()
161
+
162
+ def _set_mesh_periodicity(self,
163
+ face1: FaceSelection,
164
+ face2: FaceSelection,
165
+ lattice: tuple[float,float,float]):
166
+ translation = [1,0,0,lattice[0],
167
+ 0,1,0,lattice[1],
168
+ 0,0,1,lattice[2],
169
+ 0,0,0,1]
170
+ gmsh.model.mesh.set_periodic(2, face2.tags, face1.tags, translation)
171
+
172
+ def set_algorithm(self,
173
+ obj: GeoObject,
174
+ algorithm: Algorithm3D) -> None:
175
+ for tag in obj.tags:
176
+ gmsh.model.mesh.setAlgorithm(obj.dim, tag, algorithm.value)
177
+
178
+ def set_periodic_cell(self, cell: PeriodicCell, excluded_faces: list[FaceSelection] = None):
179
+ """Sets the periodic cell information based on the PeriodicCell class object"""
180
+ if excluded_faces is None:
181
+ for f1, f2, lat in cell.cell_data():
182
+ self._set_mesh_periodicity(f1, f2, lat)
183
+ else:
184
+ for f1, f2, lat in cell.cell_data():
185
+ self._set_mesh_periodicity(f1 - excluded_faces, f2 - excluded_faces, lat)
186
+ self.periodic_cell = cell
187
+
188
+ def _set_size_in_domain(self, tags: list[int], max_size: float) -> None:
189
+ """Define the size of the mesh inside a domain
190
+
191
+ Args:
192
+ tags (list[int]): The tags of the geometry
193
+ max_size (float): The maximum size (in meters)
194
+ """
195
+ ctag = gmsh.model.mesh.field.add("Constant")
196
+ gmsh.model.mesh.field.set_numbers(ctag, "VolumesList", tags)
197
+ gmsh.model.mesh.field.set_number(ctag, "VIn", max_size)
198
+ self.mesh_fields.append(ctag)
199
+
200
+ def _set_size_on_face(self, tags: list[int], max_size: float) -> None:
201
+ """Define the size of the mesh on a face
202
+
203
+ Args:
204
+ tags (list[int]): The tags of the geometry
205
+ max_size (float): The maximum size (in meters)
206
+ """
207
+ ctag = gmsh.model.mesh.field.add("Constant")
208
+ gmsh.model.mesh.field.set_numbers(ctag, "SurfacesList", tags)
209
+ gmsh.model.mesh.field.set_number(ctag, "VIn", max_size)
210
+ self.mesh_fields.append(ctag)
211
+
212
+ def set_mesh_size(self, discretizer: Callable, resolution: float):
213
+
214
+ dimtags = gmsh.model.occ.get_entities(2)
215
+
216
+ for dim, tag in dimtags:
217
+ gmsh.model.mesh.setSizeFromBoundary(2, tag, 0)
218
+
219
+ mintag = gmsh.model.mesh.field.add("Min")
220
+ size_mapping = dict()
221
+
222
+ for obj in sorted(self.objects, key=lambda x: x._priority):
223
+ if obj._unset_constraints:
224
+ self.unset_constraints(obj.dimtags)
225
+
226
+ size = discretizer(obj.material)*resolution*obj.mesh_multiplier
227
+ size = min(size, obj.max_meshsize)
228
+ logger.info(f'Setting mesh size for {_DOM_TO_STR[obj.dim]} domain with tags {obj.tags} to {1000*size:.3f}mm')
229
+ for tag in obj.tags:
230
+ size_mapping[tag] = size
231
+
232
+ for tag, size in size_mapping.items():
233
+ self._set_size_in_domain([tag,], size)
234
+
235
+ gmsh.model.mesh.field.setNumbers(mintag, "FieldsList", self.mesh_fields)
236
+ gmsh.model.mesh.field.setAsBackgroundMesh(mintag)
237
+
238
+ for tag, size in self.size_definitions:
239
+ gmsh.model.mesh.setSize([tag,], size)
240
+
241
+ def unset_constraints(self, dimtags: list[tuple[int,int]]):
242
+ '''Unset the mesh constraints for the given dimension tags.'''
243
+ for dimtag in dimtags:
244
+ gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
245
+
246
+ def set_boundary_size(self, object: GeoSurface | FaceSelection,
247
+ size:float,
248
+ growth_rate: float = 1.4,
249
+ max_size: float = None):
250
+ """Refine the mesh size along the boundary of a conducting surface
251
+
252
+ Args:
253
+ object (GeoSurface | FaceSelection): _description_
254
+ size (float): _description_
255
+ growth_rate (float, optional): _description_. Defaults to 1.1.
256
+ max_size (float, optional): _description_. Defaults to None.
257
+ """
258
+
259
+ dimtags = object.dimtags
260
+
261
+ if max_size is None:
262
+ self._check_ready()
263
+ max_size = self.max_size
264
+
265
+ growth_distance = np.log10(max_size/size)/np.log10(growth_rate)
266
+
267
+ nodes = gmsh.model.getBoundary(dimtags, combined=False, oriented=False, recursive=False)
268
+
269
+ disttag = gmsh.model.mesh.field.add("Distance")
270
+
271
+ gmsh.model.mesh.field.setNumbers(disttag, "CurvesList", [n[1] for n in nodes])
272
+ gmsh.model.mesh.field.setNumber(disttag, "Sampling", 100)
273
+
274
+ thtag = gmsh.model.mesh.field.add("Threshold")
275
+ gmsh.model.mesh.field.setNumber(thtag, "InField", disttag)
276
+ gmsh.model.mesh.field.setNumber(thtag, "SizeMin", size)
277
+ gmsh.model.mesh.field.setNumber(thtag, "SizeMax", max_size)
278
+ gmsh.model.mesh.field.setNumber(thtag, "DistMin", size)
279
+ gmsh.model.mesh.field.setNumber(thtag, "DistMax", growth_distance*size)
280
+
281
+ self.mesh_fields.append(thtag)
282
+
283
+ def set_domain_size(self, obj: GeoVolume | Selection, size: float):
284
+ """Manually set the maximum element size inside a domain
285
+
286
+ Args:
287
+ obj (GeoVolume | Selection): The volumetric domain
288
+ size (float): The maximum mesh size
289
+ """
290
+ self._set_size_in_domain(obj.tags, size)
291
+
292
+ def set_face_size(self, obj: GeoSurface | Selection, size: float):
293
+ """Manually set the maximum element size on a face
294
+
295
+ Args:
296
+ obj (GeoSurface | Selection): The surface domain
297
+ size (float): The maximum size
298
+ """
299
+ self._set_size_on_face(obj.tags, size)
300
+
301
+ def refine_conductor_edge(self, dimtags: list[tuple[int,int]], size):
302
+ nodes = gmsh.model.getBoundary(dimtags, combined=False, recursive=False)
303
+
304
+ # for node in nodes:
305
+ # pcoords = np.linspace(0, 0.5, 10)
306
+ # gmsh.model.mesh.setSizeAtParametricPoints(node[0], node[1], pcoords, size*np.ones_like(pcoords))
307
+ # #self.size_definitions.append((node, size))
308
+ # gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
309
+
310
+ tag = gmsh.model.mesh.field.add("Distance")
311
+
312
+ #gmsh.model.mesh.field.setNumbers(1, "PointsList", [5])
313
+ gmsh.model.mesh.field.setNumbers(tag, "CurvesList", [n[1] for n in nodes])
314
+ gmsh.model.mesh.field.setNumber(tag, "Sampling", 100)
315
+
316
+ # We then define a `Threshold' field, which uses the return value of the
317
+ # `Distance' field 1 in order to define a simple change in element size
318
+ # depending on the computed distances
319
+ #
320
+ # SizeMax - /------------------
321
+ # /
322
+ # /
323
+ # /
324
+ # SizeMin -o----------------/
325
+ # | | |
326
+ # Point DistMin DistMax
327
+ thtag = gmsh.model.mesh.field.add("Threshold")
328
+ gmsh.model.mesh.field.setNumber(thtag, "InField", tag)
329
+ gmsh.model.mesh.field.setNumber(thtag, "SizeMin", size)
330
+ gmsh.model.mesh.field.setNumber(thtag, "SizeMax", 100)
331
+ gmsh.model.mesh.field.setNumber(thtag, "DistMin", 0.2*size)
332
+ gmsh.model.mesh.field.setNumber(thtag, "DistMax", 5*size)
333
+
334
+ self.mesh_fields.append(thtag)
335
+
336
+
337
+ for dimtag in dimtags:
338
+ gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
339
+
@@ -0,0 +1,33 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ import numpy as np
19
+
20
+ def norm(Field: np.ndarray) -> np.ndarray:
21
+ ''' Computes the complex norm of a field (3,N)'''
22
+ return np.sqrt(np.abs(Field[0,:])**2 + np.abs(Field[1,:])**2 + np.abs(Field[2,:])**2)
23
+
24
+ def coax_rout(rin: float,
25
+ eps_r: float = 1,
26
+ Z0: float = 50) -> float:
27
+ return rin*10**(Z0*np.sqrt(eps_r)/138)
28
+
29
+ def coax_rin(rout: float,
30
+ eps_r: float = 1,
31
+ Z0: float = 50) -> float:
32
+ return rout/10**(Z0*np.sqrt(eps_r)/138)
33
+
@@ -0,0 +1,71 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ import numpy as np
19
+ from typing import Callable
20
+ from numba import njit, f8, i8, types, c16
21
+ from .optimized import gaus_quad_tri, generate_int_points_tri, calc_area
22
+
23
+
24
+ @njit(c16(f8[:,:], i8[:,:], c16[:], f8[:,:], c16[:,:]), cache=True, nogil=True)
25
+ def _fast_integral_c(nodes, triangles, constants, DPTs, field_values):
26
+ tot = np.complex128(0.0)
27
+
28
+ for it in range(triangles.shape[1]):
29
+ vertex_ids = triangles[:, it]
30
+ v1 = nodes[:,vertex_ids[0]]
31
+ v2 = nodes[:,vertex_ids[1]]
32
+ v3 = nodes[:,vertex_ids[2]]
33
+ A = calc_area(v1, v2, v3)
34
+ field = np.sum(DPTs[0,:]*field_values[:,it])
35
+ tot = tot + constants[it] * field * A
36
+ return tot
37
+
38
+ @njit(f8(f8[:,:], i8[:,:], f8[:], f8[:,:], f8[:,:]), cache=True, nogil=True)
39
+ def _fast_integral_f(nodes, triangles, constants, DPTs, field_values):
40
+ tot = np.float64(0.0)
41
+ for it in range(triangles.shape[1]):
42
+ vertex_ids = triangles[:, it]
43
+ v1 = nodes[:,vertex_ids[0]]
44
+ v2 = nodes[:,vertex_ids[1]]
45
+ v3 = nodes[:,vertex_ids[2]]
46
+ A = calc_area(v1, v2, v3)
47
+ field = np.sum(DPTs[0,:]*field_values[:,it])
48
+ tot = tot + constants[it] * field * A
49
+ return tot
50
+
51
+ def surface_integral(nodes: np.ndarray,
52
+ triangles: np.ndarray,
53
+ function: Callable,
54
+ constants: np.ndarray = None,
55
+ ndpts: int = 4):
56
+
57
+ if constants is None:
58
+ constants = np.ones(triangles.shape[1])
59
+
60
+ DPTs = gaus_quad_tri(ndpts)
61
+ xall_flat, yall_flat, zall_flat, shape = generate_int_points_tri(nodes, triangles, DPTs)
62
+
63
+ fvals = function(xall_flat, yall_flat, zall_flat)
64
+
65
+ fA = fvals.reshape(shape)
66
+
67
+ if np.iscomplexobj(fA) or np.iscomplexobj(constants):
68
+ return _fast_integral_c(nodes, triangles, constants.astype(np.complex128), DPTs, fA.astype(np.complex128))
69
+ else:
70
+ return _fast_integral_f(nodes, triangles, constants, DPTs, fA)
71
+