Skip to content

Commit 7a1b78a

Browse files
authored
Ft mfem 3d single-patch IO (#445)
* rough draft for mfem 3d single-patch export * extend 3d single patch read * add single-patch round trip test * update comment * support non-eqaul degrees * add test of various spline types * use ast.literal_eval * update comments from coderabbit's suggestions
1 parent d30f706 commit 7a1b78a

File tree

2 files changed

+123
-53
lines changed

2 files changed

+123
-53
lines changed

splinepy/io/mfem.py

Lines changed: 87 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
Currently hardcoded for 2D-single-patch-splines.
44
"""
55

6+
from ast import literal_eval
7+
68
import gustaf as _gus
79
import numpy as _np
810

@@ -31,7 +33,7 @@
3133
def load(fname):
3234
"""Reads mfem spline and returns a spline.
3335
34-
Again, only supports 2D single patch.
36+
Supports 2D/3D single patch.
3537
3638
Parameters
3739
-----------
@@ -86,30 +88,45 @@ def load(fname):
8688

8789
# parse nurbs_dict
8890
# kvs
89-
knot_vectors = nurbs_dict["knotvectors"][1:]
90-
knot_vectors[0] = eval("[" + knot_vectors[0].replace(" ", ",") + "]")
91-
knot_vectors[1] = eval("[" + knot_vectors[1].replace(" ", ",") + "]")
92-
# pop some values
93-
# ds
94-
degrees = [knot_vectors[0].pop(0), knot_vectors[1].pop(0)]
95-
ncps = knot_vectors[0].pop(0) * knot_vectors[1].pop(0)
91+
knot_vectors_sec = nurbs_dict["knotvectors"][1:]
92+
knot_vectors = []
93+
for kvs in knot_vectors_sec:
94+
knot_vectors.append(literal_eval("[" + kvs.replace(" ", ",") + "]"))
95+
96+
# parse degrees and get number of control points for consistency checks
97+
degrees = []
98+
ncps = 1
99+
for kv in knot_vectors:
100+
# first value in kv is degrees
101+
degrees.append(kv.pop(0))
102+
# second value is number of control points
103+
ncps *= kv.pop(0)
104+
96105
# ws
97106
weights = nurbs_dict["weights"]
98-
weights = [eval(w) for w in weights] # hopefully not too slow
107+
weights = [literal_eval(w) for w in weights] # hopefully not too slow
99108
# cps
100109
control_points = nurbs_dict["Ordering"]
101110
control_points = [
102-
eval(f"[{cp.replace(' ', ',')}]") for cp in control_points
111+
literal_eval(f"[{cp.replace(' ', ',')}]") for cp in control_points
103112
]
104113

105114
# double check
106115
# maybe separate them
107116
if (
108117
ncps != len(control_points)
109118
or ncps != len(weights)
110-
or int(nurbs_dict["knotvectors"][0]) != 2
119+
or int(nurbs_dict["knotvectors"][0]) != len(degrees)
120+
or len(degrees) != len(knot_vectors)
111121
):
112-
raise ValueError("Inconsistent spline info in " + fname)
122+
raise ValueError(
123+
f"Inconsistent spline info in {fname}: "
124+
f"ncps = {ncps}, "
125+
f"len(control_points) = {len(control_points)}, "
126+
f"len(weights) = {len(weights)}, "
127+
f"len(degrees) = {len(degrees)}, "
128+
f"len(knot_vectors) = {len(knot_vectors)}"
129+
)
113130

114131
mfem_dict_spline = {
115132
"degrees": degrees,
@@ -152,7 +169,7 @@ def read_solution(
152169
collect = False
153170
while line is not None:
154171
if collect: # check this first. should be true most time
155-
solution.append(eval(f"[{line.replace(' ', ',')}]"))
172+
solution.append(literal_eval(f"[{line.replace(' ', ',')}]"))
156173
elif hotkey in line:
157174
collect = True
158175

@@ -579,51 +596,68 @@ def export(fname, nurbs, precision=10):
579596
"4",
580597
"",
581598
)
599+
elif nurbs.para_dim == 3:
600+
elements_sec = _form_lines("elements", "1", "1 5 0 1 2 3 4 5 6 7", "")
601+
boundary_sec = _form_lines(
602+
"boundary",
603+
"6",
604+
"1 3 3 2 1 0",
605+
"2 3 4 5 6 7",
606+
"3 3 0 1 5 4",
607+
"4 3 1 2 6 5",
608+
"5 3 2 3 7 6",
609+
"6 3 3 0 4 7",
610+
"",
611+
)
612+
edges_sec = _form_lines(
613+
"edges",
614+
"12",
615+
"0 0 1",
616+
"0 3 2",
617+
"0 4 5",
618+
"0 7 6",
619+
"1 0 3",
620+
"1 1 2",
621+
"1 4 7",
622+
"1 5 6",
623+
"2 0 4",
624+
"2 1 5",
625+
"2 2 6",
626+
"2 3 7",
627+
"",
628+
)
629+
vertices_sec = _form_lines(
630+
"vertices",
631+
"8",
632+
"",
633+
)
634+
else:
635+
raise ValueError("mfem output support 2D and 3D splines.")
582636

583-
# I am not sure if mixed order is allowed, but in case not, let's
584-
# match orders
585-
max_degree = max(nurbs.degrees)
586-
for i, d in enumerate(nurbs.degrees):
587-
d_diff = int(max_degree - d)
588-
if d_diff > 0:
589-
for _ in range(d_diff):
590-
nurbs.elevate_degrees(i)
591-
592-
cnr = nurbs.control_mesh_resolutions
593-
594-
# double-check
595-
if not (nurbs.degrees == nurbs.degrees[0]).all():
596-
raise RuntimeError(
597-
"Something went wrong trying to match degrees of nurbs "
598-
+ "before export."
599-
)
637+
cnr = nurbs.control_mesh_resolutions
600638

601-
# This is reusable
602-
def kv_sec(spline):
603-
kvs = _form_lines(
604-
"knotvectors",
605-
str(len(spline.knot_vectors)),
639+
# This is reusable
640+
def kv_sec(spline):
641+
kvs = _form_lines(
642+
"knotvectors",
643+
str(len(spline.knot_vectors)),
644+
)
645+
kvs2 = ""
646+
for i in range(spline.para_dim):
647+
kvs2 += _form_lines(
648+
str(spline.degrees[i])
649+
+ " "
650+
+ str(cnr[i])
651+
+ " "
652+
+ str(list(spline.knot_vectors[i]))[1:-1].replace(",", "")
606653
)
607-
kvs2 = ""
608-
for i in range(spline.para_dim):
609-
kvs2 += _form_lines(
610-
str(spline.degrees[i])
611-
+ " "
612-
+ str(cnr[i])
613-
+ " "
614-
+ str(list(spline.knot_vectors[i]))[1:-1].replace(",", "")
615-
)
616654

617-
kvs = kvs + kvs2
655+
kvs = kvs + kvs2
618656

619-
kvs += "\n" # missing empty line
657+
kvs += "\n" # missing empty line
658+
return kvs
620659

621-
return kvs
622-
623-
knotvectors_sec = kv_sec(nurbs)
624-
625-
else:
626-
raise NotImplementedError
660+
knotvectors_sec = kv_sec(nurbs)
627661

628662
# disregard inverse
629663
reorder_ids, _ = dof_mapping(nurbs)
@@ -649,7 +683,7 @@ def kv_sec(spline):
649683

650684
fe_space_sec = _form_lines(
651685
"FiniteElementSpace",
652-
"FiniteElementCollection: NURBS" + str(nurbs.degrees[0]),
686+
"FiniteElementCollection: NURBS",
653687
"VDim: " + str(nurbs.dim),
654688
"Ordering: 1",
655689
"",

tests/io/test_mfem_export.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,39 @@ def test_mfem_export(to_tmpf, are_stripped_lines_same):
129129
assert are_stripped_lines_same(
130130
base_file.readlines(), tmp_read.readlines(), True
131131
)
132+
133+
134+
def test_mfem_single_patch_export(to_tmpf, are_splines_equal, np_rng):
135+
"""
136+
Test MFEM single patch export routine
137+
"""
138+
# create 2d/3d splines of all supporting types
139+
box = splinepy.helpme.create.box(1, 2)
140+
boxes = []
141+
boxes.extend([box, box.rationalbezier, box.bspline, box.nurbs])
142+
boxes.extend([b.create.extruded([0, 0, 3]) for b in boxes[:4]])
143+
for spline in boxes:
144+
dim = spline.dim
145+
146+
# make it a bit complex
147+
for i in range(dim):
148+
if not spline.has_knot_vectors:
149+
break
150+
spline.insert_knots(i, np_rng.random([5]))
151+
152+
spline.elevate_degrees(list(range(dim)) * 2)
153+
154+
for i in range(dim):
155+
if not spline.has_knot_vectors:
156+
break
157+
spline.insert_knots(i, np_rng.random([5]))
158+
159+
# Test Output
160+
with tempfile.TemporaryDirectory() as tmpd:
161+
tmpf = to_tmpf(tmpd)
162+
splinepy.io.mfem.export(tmpf, spline)
163+
164+
loaded_spline = splinepy.io.mfem.load(tmpf)
165+
166+
# all mfem export is nurbs
167+
assert are_splines_equal(spline.nurbs, loaded_spline)

0 commit comments

Comments
 (0)