Skip to content

Commit 7a4bcf3

Browse files
committed
merge release
2 parents 3c33ccd + ee654fd commit 7a4bcf3

30 files changed

Lines changed: 805 additions & 401 deletions

.github/workflows/pypi.yml

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,28 @@
11
name: Publish on PyPI
22

3-
# By default this action does not push to test or production PyPI. The wheels
4-
# are available as an artifact that can be downloaded and tested locally.
3+
# By default this action does not push to PyPI. The wheels are available
4+
# as an artifact that can be downloaded and tested locally.
55

66
on:
77
workflow_dispatch:
88
inputs:
99
ref:
1010
description: Git ref to publish
11-
default: master
1211
type: string
1312
pypi:
1413
description: Publish to PyPI
1514
default: false
1615
type: boolean
17-
testpypi:
18-
description: Publish to TestPyPI
19-
default: false
20-
type: boolean
2116

2217
workflow_call:
2318
inputs:
2419
ref:
2520
description: Git ref to publish
26-
default: master
2721
type: string
2822
pypi:
2923
description: Publish to PyPI
3024
default: false
3125
type: boolean
32-
testpypi:
33-
description: Publish to TestPyPI
34-
default: false
35-
type: boolean
3626

3727
jobs:
3828
build:
@@ -65,8 +55,6 @@ jobs:
6555
runs-on: ubuntu-latest
6656
environment:
6757
name: pypi
68-
permissions:
69-
id-token: write
7058
steps:
7159
- uses: actions/download-artifact@v4
7260
with:
@@ -75,23 +63,5 @@ jobs:
7563

7664
- name: Push to PyPI
7765
uses: pypa/gh-action-pypi-publish@release/v1
78-
79-
upload_test_pypi:
80-
name: Upload to TestPyPI (optional)
81-
if: inputs.testpypi
82-
needs: build
83-
runs-on: ubuntu-latest
84-
environment:
85-
name: testpypi
86-
permissions:
87-
id-token: write
88-
steps:
89-
- uses: actions/download-artifact@v4
90-
with:
91-
name: dist
92-
path: dist
93-
94-
- name: Push to TestPyPI
95-
uses: pypa/gh-action-pypi-publish@release/v1
9666
with:
97-
repository-url: https://test.pypi.org/legacy/
67+
password: ${{ secrets.PYPI_API_TOKEN }}

FIAT/argyris.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,17 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
5353
# interior dofs
5454
q = degree - 6
5555
if q >= 0:
56-
Q = create_quadrature(ref_el, interpolant_deg + q)
57-
Pq = polynomial_set.ONPolynomialSet(ref_el, q, scale=1)
58-
phis = Pq.tabulate(Q.get_points())[(0,) * sd]
59-
scale = ref_el.volume()
60-
cur = len(nodes)
61-
nodes.extend(IntegralMoment(ref_el, Q, phi/scale) for phi in phis)
62-
entity_ids[sd][0] = list(range(cur, len(nodes)))
56+
cell = ref_el.construct_subelement(sd)
57+
Q_ref = create_quadrature(cell, interpolant_deg + q)
58+
Pq = polynomial_set.ONPolynomialSet(cell, q, scale=1)
59+
Phis = Pq.tabulate(Q_ref.get_points())[(0,) * sd]
60+
for entity in sorted(top[sd]):
61+
Q = FacetQuadratureRule(ref_el, sd, entity, Q_ref)
62+
scale = 1 / Q.jacobian_determinant()
63+
phis = scale * Phis
64+
cur = len(nodes)
65+
nodes.extend(IntegralMoment(ref_el, Q, phi) for phi in phis)
66+
entity_ids[sd][entity] = list(range(cur, len(nodes)))
6367

6468
elif variant == "point":
6569
# edge dofs
@@ -77,9 +81,10 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
7781
# interior dofs
7882
if degree > 5:
7983
cur = len(nodes)
80-
internalpts = ref_el.make_points(2, 0, degree - 3)
81-
nodes.extend(PointEvaluation(ref_el, pt) for pt in internalpts)
82-
entity_ids[2][0] = list(range(cur, len(nodes)))
84+
for entity in sorted(top[sd]):
85+
internalpts = ref_el.make_points(sd, entity, degree - 3)
86+
nodes.extend(PointEvaluation(ref_el, pt) for pt in internalpts)
87+
entity_ids[sd][entity] = list(range(cur, len(nodes)))
8388
else:
8489
raise ValueError("Invalid variant for Argyris")
8590
super().__init__(nodes, ref_el, entity_ids)
@@ -103,7 +108,9 @@ class Argyris(finite_element.CiarletElement):
103108

104109
def __init__(self, ref_el, degree=5, variant=None):
105110

106-
variant, interpolant_deg = check_format_variant(variant, degree)
111+
splitting, variant, interpolant_deg = check_format_variant(variant, degree)
112+
if splitting is not None:
113+
raise NotImplementedError(f"{type(self).__name__} is not implemented as a macroelement.")
107114

108115
poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, variant="bubble")
109116
dual = ArgyrisDualSet(ref_el, degree, variant, interpolant_deg)

FIAT/brezzi_douglas_marini.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from FIAT.check_format_variant import check_format_variant
1111
from FIAT.quadrature_schemes import create_quadrature
1212
from FIAT.quadrature import FacetQuadratureRule
13+
import numpy
1314

1415

1516
class BDMDualSet(dual_set.DualSet):
@@ -26,7 +27,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
2627
entity_ids[dim][entity] = []
2728

2829
if variant == "integral":
29-
facet = ref_el.get_facet_element()
30+
facet = ref_el.construct_subelement(sd-1)
3031
# Facet nodes are \int_F v\cdot n p ds where p \in P_{q}
3132
# degree is q
3233
Q_ref = create_quadrature(facet, interpolant_deg + degree)
@@ -56,14 +57,18 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
5657
if degree > 1:
5758
if interpolant_deg is None:
5859
interpolant_deg = degree
59-
cur = len(nodes)
60-
Q = create_quadrature(ref_el, interpolant_deg + degree - 1)
61-
Nedel = nedelec.Nedelec(ref_el, degree - 1, variant)
62-
Nedfs = Nedel.get_nodal_basis()
63-
Ned_at_qpts = Nedfs.tabulate(Q.get_points())[(0,) * sd]
64-
nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi)
65-
for phi in Ned_at_qpts)
66-
entity_ids[sd][0] = list(range(cur, len(nodes)))
60+
61+
cell = ref_el.construct_subelement(sd)
62+
Q_ref = create_quadrature(cell, interpolant_deg + degree - 1)
63+
Nedel = nedelec.Nedelec(cell, degree - 1, variant)
64+
Ned_at_qpts = Nedel.tabulate(0, Q_ref.get_points())[(0,) * sd]
65+
for entity in top[sd]:
66+
Q = FacetQuadratureRule(ref_el, sd, entity, Q_ref)
67+
Jinv = numpy.linalg.inv(Q.jacobian())
68+
phis = numpy.tensordot(Jinv.T, Ned_at_qpts, (1, 1)).transpose((1, 0, 2))
69+
cur = len(nodes)
70+
nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis)
71+
entity_ids[sd][entity] = list(range(cur, len(nodes)))
6772

6873
super().__init__(nodes, ref_el, entity_ids)
6974

@@ -89,6 +94,11 @@ class BrezziDouglasMarini(finite_element.CiarletElement):
8994
"""
9095

9196
def __init__(self, ref_el, degree, variant=None):
97+
98+
splitting, variant, interpolant_deg = check_format_variant(variant, degree)
99+
if splitting is not None:
100+
ref_el = splitting(ref_el)
101+
92102
if degree < 1:
93103
raise ValueError(f"{type(self).__name__} elements only valid for k >= 1")
94104

@@ -99,7 +109,6 @@ def __init__(self, ref_el, degree, variant=None):
99109
elif variant == "fdm":
100110
dual = demkowicz.FDMDual(ref_el, degree, "HDiv", type(self))
101111
else:
102-
variant, interpolant_deg = check_format_variant(variant, degree)
103112
dual = BDMDualSet(ref_el, degree, variant, interpolant_deg)
104113
formdegree = sd - 1 # (n-1)-form
105114
super().__init__(poly_set, dual, degree, formdegree, mapping="contravariant piola")

FIAT/bubble.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,20 @@
77
# SPDX-License-Identifier: LGPL-3.0-or-later
88

99
from FIAT.lagrange import Lagrange
10+
from FIAT.hierarchical import IntegratedLegendre
1011
from FIAT.restricted import RestrictedElement
1112
from itertools import chain
1213

1314

1415
class CodimBubble(RestrictedElement):
1516
"""Bubbles of a certain codimension."""
1617

17-
def __init__(self, ref_el, degree, codim):
18-
element = Lagrange(ref_el, degree)
18+
def __init__(self, ref_el, degree, codim, variant=None):
19+
if variant and variant.startswith("integral"):
20+
CG = IntegratedLegendre
21+
else:
22+
CG = Lagrange
23+
element = CG(ref_el, degree, variant=variant)
1924

2025
cell_dim = ref_el.get_dimension()
2126
assert cell_dim == max(element.entity_dofs().keys())
@@ -29,12 +34,12 @@ def __init__(self, ref_el, degree, codim):
2934
class Bubble(CodimBubble):
3035
"""The bubble finite element: the dofs of the Lagrange FE in the interior of the cell"""
3136

32-
def __init__(self, ref_el, degree):
33-
super().__init__(ref_el, degree, codim=0)
37+
def __init__(self, ref_el, degree, variant=None):
38+
super().__init__(ref_el, degree, codim=0, variant=variant)
3439

3540

3641
class FacetBubble(CodimBubble):
3742
"""The facet bubble finite element: the dofs of the Lagrange FE in the interior of the facets"""
3843

39-
def __init__(self, ref_el, degree):
40-
super().__init__(ref_el, degree, codim=1)
44+
def __init__(self, ref_el, degree, variant=None):
45+
super().__init__(ref_el, degree, codim=1, variant=variant)

FIAT/check_format_variant.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828

2929
def check_format_variant(variant, degree):
30+
splitting, variant = parse_lagrange_variant(variant, integral=True)
31+
3032
if variant is None:
3133
variant = "integral"
3234

@@ -43,7 +45,7 @@ def check_format_variant(variant, degree):
4345
raise ValueError('Choose either variant="point" or variant="integral"'
4446
'or variant="integral(q)"')
4547

46-
return variant, interpolant_degree
48+
return splitting, variant, interpolant_degree
4749

4850

4951
def parse_lagrange_variant(variant, discontinuous=False, integral=False):
@@ -60,7 +62,7 @@ def parse_lagrange_variant(variant, discontinuous=False, integral=False):
6062

6163
default = "integral" if integral else "spectral"
6264
if integral:
63-
supported_point_variants = {"integral": None, "fdm": "fdm", "demkowicz": "demkowicz", "demkowiczmass": "demkowiczmass"}
65+
supported_point_variants = {"integral": None, "point": "point", "fdm": "fdm", "demkowicz": "demkowicz", "demkowiczmass": "demkowiczmass"}
6466
elif discontinuous:
6567
supported_point_variants = supported_dg_variants
6668
else:
@@ -79,6 +81,8 @@ def parse_lagrange_variant(variant, discontinuous=False, integral=False):
7981
k, = match.groups()
8082
call_split = IsoSplit
8183
splitting_args = (int(k),)
84+
elif opt.startswith("integral"):
85+
point_variant = opt
8286
elif opt in supported_point_variants:
8387
point_variant = supported_point_variants[opt]
8488
else:

FIAT/crouzeix_raviart.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,13 @@ class CrouzeixRaviart(finite_element.CiarletElement):
8181
"""
8282

8383
def __init__(self, ref_el, degree, variant=None):
84-
85-
variant, interpolant_deg = check_format_variant(variant, degree)
86-
8784
if degree % 2 != 1:
8885
raise ValueError("Crouzeix-Raviart only defined for odd degree")
8986

90-
space = polynomial_set.ONPolynomialSet(ref_el, degree, variant="bubble")
87+
splitting, variant, interpolant_deg = check_format_variant(variant, degree)
88+
if splitting is not None:
89+
ref_el = splitting(ref_el)
90+
91+
poly_set = polynomial_set.ONPolynomialSet(ref_el, degree)
9192
dual = CrouzeixRaviartDualSet(ref_el, degree, variant, interpolant_deg)
92-
super().__init__(space, dual, degree)
93+
super().__init__(poly_set, dual, degree)

FIAT/expansions.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,18 @@ def __init__(self, ref_el, scale=None, variant=None):
274274
if scale is None:
275275
scale = math.sqrt(1.0 / base_ref_el.volume())
276276
self.scale = scale
277+
self.variant = variant
277278
self.continuity = "C0" if variant == "bubble" else None
278279
self.recurrence_order = 2
279280
self._dmats_cache = {}
280281
self._cell_node_map_cache = {}
281282

283+
def reconstruct(self, ref_el=None, scale=None, variant=None):
284+
"""Reconstructs this ExpansionSet with modified arguments."""
285+
return ExpansionSet(ref_el or self.ref_el,
286+
scale=scale or self.scale,
287+
variant=variant or self.variant)
288+
282289
def get_scale(self, n, cell=0):
283290
scale = self.scale
284291
sd = self.ref_el.get_spatial_dimension()
@@ -371,7 +378,7 @@ def _tabulate(self, n, pts, order=0):
371378
phi[alpha] /= mult[None, ipts]
372379

373380
# Insert subcell tabulations into the corresponding submatrices
374-
idx = lambda *args: args if args[1] is Ellipsis else numpy.ix_(*args)
381+
idx = lambda *args: args if args[-1] is Ellipsis else numpy.ix_(*args)
375382
num_phis = self.get_num_members(n)
376383
cell_node_map = self.get_cell_node_map(n)
377384
result = {}
@@ -599,7 +606,10 @@ def polynomial_dimension(ref_el, n, continuity=None):
599606
raise ValueError("Only degree zero polynomials supported on point elements.")
600607
return 1
601608
top = ref_el.get_topology()
602-
if continuity == "C0":
609+
610+
if isinstance(continuity, dict):
611+
space_dimension = sum(len(continuity[dim][0]) * len(top[dim]) for dim in top)
612+
elif continuity == "C0":
603613
space_dimension = sum(math.comb(n - 1, dim) * len(top[dim]) for dim in top)
604614
else:
605615
dim = ref_el.get_spatial_dimension()
@@ -620,7 +630,9 @@ def polynomial_entity_ids(ref_el, n, continuity=None):
620630
entity_ids = {}
621631
cur = 0
622632
for dim in sorted(top):
623-
if continuity == "C0":
633+
if isinstance(continuity, dict):
634+
dofs, = set(len(continuity[dim][entity]) for entity in continuity[dim])
635+
elif continuity == "C0":
624636
dofs = math.comb(n - 1, dim)
625637
else:
626638
# DG numbering

FIAT/finite_element.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from FIAT.dual_set import DualSet
1616
from FIAT.polynomial_set import PolynomialSet
1717
from FIAT.quadrature_schemes import create_quadrature
18+
from FIAT.macro import MacroPolynomialSet
1819

1920

2021
class FiniteElement(object):
@@ -134,6 +135,11 @@ def __init__(self, poly_set, dual, order, formdegree=None, mapping="affine", ref
134135
ref_complex = ref_complex or poly_set.get_reference_element()
135136
super().__init__(ref_el, dual, order, formdegree, mapping, ref_complex)
136137

138+
# Tile the poly_set
139+
if ref_complex.is_macrocell() and len(poly_set) > len(dual):
140+
base_element = type(self)(ref_el, order)
141+
poly_set = MacroPolynomialSet(ref_complex, base_element)
142+
137143
if len(poly_set) != len(dual):
138144
raise ValueError(f"Dimension of function space is {len(poly_set)}, but got {len(dual)} nodes.")
139145

0 commit comments

Comments
 (0)