Skip to content

Commit 3fa23c2

Browse files
authored
🎉 New feature: Integration point support (basic) (#400)
coverage for later, sorry to much work ;)
1 parent 13331b3 commit 3fa23c2

5 files changed

Lines changed: 119 additions & 51 deletions

File tree

src/plaid/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"JFaceCenter",
6767
"KFaceCenter",
6868
"EdgeCenter",
69+
"IntegrationPoint"
6970
]
7071

7172
CGNS_ELEMENT_NAMES = [

src/plaid/containers/features.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,20 +1048,29 @@ def get_field_names_one_time_base_zone_location(
10481048
return []
10491049

10501050
names = []
1051-
solution_paths = CGU.getPathsByTypeSet(search_node, [CGK.FlowSolution_t])
1051+
# try to find IntegrationPoint on UserDefinedData_t
1052+
solution_paths = CGU.getPathsByTypeSet(search_node, [CGK.FlowSolution_t, "UserDefinedData_t"])
10521053
for f_path in solution_paths:
1053-
if (
1054-
CGU.getValueByPath(search_node, f_path + "/GridLocation")
1055-
.tobytes()
1056-
.decode()
1057-
!= location
1058-
):
1059-
continue
1054+
1055+
grid_loc_node = CGU.getValueByPath(search_node, f_path + "/GridLocation")
1056+
if grid_loc_node is not None:
1057+
if (grid_loc_node.tobytes().decode()!= location ):
1058+
continue
1059+
else:
1060+
##possible an integration point data check Muscat Implementation for details
1061+
grid_loc_node = CGU.getValueByPath(search_node, f_path + "/gridlocation")
1062+
if grid_loc_node is not None:
1063+
if (grid_loc_node.tobytes().decode()!= location ):
1064+
continue
1065+
else:
1066+
continue
1067+
10601068
f_node = CGU.getNodeByPath(search_node, f_path)
10611069
for path in CGU.getPathByTypeFilter(f_node, CGK.DataArray_t):
10621070
field_name = path.split("/")[-1]
1063-
if not (field_name == "GridLocation"):
1071+
if field_name not in ["GridLocation","ItgRules","gridlocation","ItgPointStartOffset", "Ids","Path"]:
10641072
names.append(field_name)
1073+
10651074
return names
10661075

10671076
field_names = []
@@ -1117,12 +1126,22 @@ def get_field(
11171126
return None
11181127

11191128
full_field = []
1120-
solution_paths = CGU.getPathsByTypeSet(search_node, [CGK.FlowSolution_t])
1129+
solution_paths = CGU.getPathsByTypeSet(search_node, [CGK.FlowSolution_t, "UserDefinedData_t"])
11211130

11221131
for f_path in solution_paths:
11231132
grid_loc = CGU.getValueByPath(search_node, f_path + "/GridLocation")
1124-
if grid_loc.tobytes().decode() != location:
1125-
continue
1133+
if grid_loc is not None:
1134+
if grid_loc.tobytes().decode() != location:
1135+
continue
1136+
else:
1137+
##possible an integration point data
1138+
grid_loc = CGU.getValueByPath(search_node , f_path+"/gridlocation")
1139+
if grid_loc is not None:
1140+
if (grid_loc.tobytes().decode()!= location ):
1141+
continue
1142+
else:
1143+
raise
1144+
11261145

11271146
field = CGU.getValueByPath(search_node, f_path + "/" + name)
11281147
if field is not None and field.size > 0:

src/plaid/storage/common/preprocessor.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ def infer_dtype(value: Any) -> dict[str, int | str]:
4242
dt = "int64"
4343
elif np.issubdtype(dtype, np.str_):
4444
dt = "string"
45+
elif np.issubdtype(dtype, np.dtype('S1')):
46+
dt = "S1"
4547
else: # pragma: no cover
4648
raise ValueError(f"Unrecognized scalar dtype: {dtype}")
4749
return {"dtype": dt, "ndim": arr.ndim}
@@ -183,7 +185,8 @@ def _hash_value(value: Any) -> str:
183185
str: The MD5 hash of the value.
184186
"""
185187
if isinstance(value, np.ndarray):
186-
return hashlib.md5(value.view(np.uint8)).hexdigest()
188+
contiguous_value = np.ascontiguousarray(value)
189+
return hashlib.md5(contiguous_value.tobytes(order="C")).hexdigest()
187190
return hashlib.md5(str(value).encode("utf-8")).hexdigest()
188191

189192

@@ -535,10 +538,10 @@ def preprocess(
535538

536539
# --- build features ---
537540
var_features = sorted(list(set().union(*split_var_path.values())))
538-
if len(var_features) == 0: # pragma: no cover
539-
raise ValueError(
540-
"no variable feature found, is your dataset variable through samples?"
541-
)
541+
#if len(var_features) == 0: # pragma: no cover
542+
# raise ValueError(
543+
# "no variable feature found, is your dataset variable through samples?"
544+
# )
542545

543546
for split_name in split_flat_cst.keys():
544547
for path in var_features:

src/plaid/utils/cgns_helper.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,8 @@ def visit(tree, path=""):
433433
name, data, children, cgns_type = node
434434
new_path = f"{path}/{name}" if path else name
435435

436+
if new_path in flat:
437+
raise RuntimeError(f"Error: Non unique path for every entity. The path '{new_path}' points to multiple data. Try changing the name of your fields")
436438
flat[new_path] = data
437439
cgns_types[new_path] = cgns_type
438440

tests/containers/test_sample.py

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import copy
44
from pathlib import Path
55

6+
import CGNS.PAT.cgnslib as CGL
67
import CGNS.PAT.cgnskeywords as CGK
78
import CGNS.PAT.cgnsutils as CGU
89
import numpy as np
@@ -573,9 +574,7 @@ def test_del_zone_no_cgns_tree(self, sample: Sample):
573574

574575
def test_has_zone(self, sample, base_name, zone_name):
575576
sample.init_base(3, 3, base_name)
576-
sample.init_zone(
577-
np.array([[5, 3, 0]]), zone=zone_name, base=base_name
578-
)
577+
sample.init_zone(np.array([[5, 3, 0]]), zone=zone_name, base=base_name)
579578
sample.show_tree()
580579
assert sample.features.has_zone(zone_name, base_name)
581580
assert not sample.features.has_zone("not_present_zone_name", base_name)
@@ -617,18 +616,14 @@ def test_get_zone_type(self, sample: Sample, zone_name, base_name):
617616
sample.init_base(3, 3, base_name)
618617
with pytest.raises(KeyError):
619618
sample.features.get_zone_type(zone_name, base_name)
620-
sample.init_zone(
621-
np.array([[5, 3, 0]]), zone=zone_name, base=base_name
622-
)
619+
sample.init_zone(np.array([[5, 3, 0]]), zone=zone_name, base=base_name)
623620
assert sample.features.get_zone_type(zone_name, base_name) == CGK.Unstructured_s
624621

625622
def test_get_zone(self, sample: Sample, zone_name, base_name):
626623
assert sample.features.get_zone(zone_name, base_name) is None
627624
sample.init_base(3, 3, base_name)
628625
assert sample.features.get_zone(zone_name, base_name) is None
629-
sample.init_zone(
630-
np.array([[5, 3, 0]]), zone=zone_name, base=base_name
631-
)
626+
sample.init_zone(np.array([[5, 3, 0]]), zone=zone_name, base=base_name)
632627
assert sample.features.get_zone() is not None
633628
assert sample.features.get_zone(zone_name, base_name) is not None
634629
sample.init_zone(
@@ -651,6 +646,21 @@ def test_get_global_names_at_specific_time(self, sample: Sample):
651646
assert sample.get_global_names(time=0.0) == ["g_t0"]
652647
assert sample.get_global_names(time=1.0) == ["g_t1"]
653648

649+
def test_add_global_string_and_update_existing(self, sample: Sample):
650+
sample.add_global("g_str", "abc")
651+
value = sample.get_global("g_str")
652+
assert isinstance(value, np.ndarray)
653+
assert value.tobytes().decode("ascii") == "abc"
654+
655+
sample.add_global("g_str", np.array([7.0]))
656+
assert sample.get_global("g_str") == 7.0
657+
658+
def test_get_global_names_excludes_time_arrays(self, sample: Sample):
659+
sample.init_base(2, 2, "Base_2_2", time=0.0)
660+
sample.add_global("kept_name", np.array([1.0]), time=0.0)
661+
names = sample.get_global_names(time=0.0)
662+
assert names == ["kept_name"]
663+
654664
def test_get_scalar_empty(self, sample):
655665
assert sample.get_global("missing_scalar_name") is None
656666

@@ -709,7 +719,9 @@ def test_add_feature(self, sample_with_tree3d):
709719
def test_del_feature(self, sample_with_scalar: Sample, sample_with_tree3d: Sample):
710720
sample_with_scalar.del_feature_by_path(path="Global/test_scalar_1")
711721
assert sample_with_scalar.get_all_features_identifiers_by_type("scalar") == []
712-
sample_with_tree3d.del_feature_by_path("Base_2_3/Zone/VertexFields/test_node_field_1")
722+
sample_with_tree3d.del_feature_by_path(
723+
"Base_2_3/Zone/VertexFields/test_node_field_1"
724+
)
713725

714726
# -------------------------------------------------------------------------#
715727
def test_get_nodal_tags_empty(self, sample):
@@ -737,15 +749,38 @@ def test_get_nodes_unknown_coordinate_name(self, sample_with_tree):
737749
with pytest.raises(ValueError):
738750
sample_with_tree.get_nodes(name="UnknownCoordinate")
739751

752+
# def test_get_nodes_returns_none_without_gridcoordinates(
753+
# self, sample: Sample, base_name: str, zone_name: str
754+
# ):
755+
# sample.init_base(2, 2, base_name)
756+
# sample.init_zone(np.array([3, 0, 0]), zone=zone_name, base=base_name)
757+
# zone_node = sample.features.get_zone(zone=zone_name, base=base_name)
758+
# gc_node = CGU.getNodeByPath(zone_node, "GridCoordinates")
759+
# assert gc_node is not None
760+
# CGU.nodeDelete(zone_node, gc_node)
761+
# assert sample.get_nodes(zone=zone_name, base=base_name) is None
762+
740763
def test_set_nodes(self, sample, nodes, zone_name, base_name):
741764
sample.init_base(3, 3, base_name)
742765
with pytest.raises(KeyError):
743766
sample.set_nodes(nodes, zone_name, base_name)
744-
sample.init_zone(
745-
np.array([len(nodes), 0, 0]), zone=zone_name, base=base_name
746-
)
767+
sample.init_zone(np.array([len(nodes), 0, 0]), zone=zone_name, base=base_name)
747768
sample.set_nodes(nodes, zone_name, base_name)
748769

770+
def test_set_nodes_replaces_existing_coordinates(
771+
self, sample: Sample, base_name: str, zone_name: str
772+
):
773+
sample.init_base(3, 3, base_name)
774+
sample.init_zone(np.array([3, 0, 0]), zone=zone_name, base=base_name)
775+
776+
nodes_a = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]])
777+
nodes_b = np.array([[2.0, 2.0], [3.0, 2.0], [3.0, 3.0]])
778+
sample.set_nodes(nodes_a, zone=zone_name, base=base_name)
779+
sample.set_nodes(nodes_b, zone=zone_name, base=base_name)
780+
781+
got = sample.get_nodes(zone=zone_name, base=base_name)
782+
assert np.allclose(got, nodes_b)
783+
749784
# -------------------------------------------------------------------------#
750785
def test_get_elements_empty(self, sample: Sample):
751786
assert sample.features.get_elements() == {}
@@ -768,18 +803,10 @@ def test_get_field_names_several_bases(self):
768803
sample = Sample()
769804
sample.init_tree(time=-0.1)
770805
sample.init_tree(time=1.0)
771-
sample.init_base(
772-
topological_dim=1, physical_dim=2, base="Base_1_2", time=-0.1
773-
)
774-
sample.init_base(
775-
topological_dim=2, physical_dim=2, base="Base_2_2", time=-0.1
776-
)
777-
sample.init_base(
778-
topological_dim=1, physical_dim=3, base="Base_1_3", time=1.0
779-
)
780-
sample.init_base(
781-
topological_dim=3, physical_dim=3, base="Base_3_3", time=1.0
782-
)
806+
sample.init_base(topological_dim=1, physical_dim=2, base="Base_1_2", time=-0.1)
807+
sample.init_base(topological_dim=2, physical_dim=2, base="Base_2_2", time=-0.1)
808+
sample.init_base(topological_dim=1, physical_dim=3, base="Base_1_3", time=1.0)
809+
sample.init_base(topological_dim=3, physical_dim=3, base="Base_3_3", time=1.0)
783810
sample.init_zone(
784811
zone_shape=np.array([[5, 3, 0]]),
785812
zone="Zone_1",
@@ -998,6 +1025,28 @@ def test_get_field(self, sample_with_tree):
9981025
"test_elem_field_1", location="CellCenter"
9991026
).shape == (3,)
10001027

1028+
def test_get_field_from_user_defined_data_lowercase_gridlocation(
1029+
self, sample: Sample, base_name: str, zone_name: str
1030+
):
1031+
sample.init_base(2, 2, base_name)
1032+
sample.init_zone(np.array([3, 0, 0]), zone=zone_name, base=base_name)
1033+
1034+
zone_node = sample.features.get_zone(zone=zone_name, base=base_name)
1035+
udd = CGL.newUserDefinedData(zone_node, "ItgPointData")
1036+
CGL.newDataArray(
1037+
udd,
1038+
"gridlocation",
1039+
value=np.frombuffer("Vertex".encode("ascii"), dtype="S1"),
1040+
)
1041+
CGL.newDataArray(udd, "my_udd_field", value=np.array([1.0, 2.0, 3.0]))
1042+
1043+
assert (
1044+
sample.get_field("my_udd_field", zone=zone_name, base=base_name) is not None
1045+
)
1046+
assert "my_udd_field" in sample.get_field_names(
1047+
zone=zone_name, base=base_name, location="Vertex"
1048+
)
1049+
10011050
def test_add_field_vertex(self, sample: Sample, vertex_field, zone_name, base_name):
10021051
sample.init_base(3, 3, base_name)
10031052
with pytest.raises(KeyError):
@@ -1014,9 +1063,7 @@ def test_add_field_vertex(self, sample: Sample, vertex_field, zone_name, base_na
10141063
zone=zone_name,
10151064
base=base_name,
10161065
)
1017-
sample.init_zone(
1018-
np.array([[5, 3, 0]]), zone=zone_name, base=base_name
1019-
)
1066+
sample.init_zone(np.array([[5, 3, 0]]), zone=zone_name, base=base_name)
10201067
sample.add_field(
10211068
name="test_node_field_2",
10221069
field=vertex_field,
@@ -1043,9 +1090,7 @@ def test_add_field_cell_center(
10431090
zone=zone_name,
10441091
base=base_name,
10451092
)
1046-
sample.init_zone(
1047-
np.array([[5, 3, 0]]), zone=zone_name, base=base_name
1048-
)
1093+
sample.init_zone(np.array([[5, 3, 0]]), zone=zone_name, base=base_name)
10491094
sample.add_field(
10501095
name="test_elem_field_2",
10511096
location="CellCenter",
@@ -1117,9 +1162,7 @@ def test_del_field_nonexistent(self, base_name):
11171162
def test_del_field_in_zone(self, zone_name, base_name, cell_center_field):
11181163
sample = Sample()
11191164
sample.init_base(3, 3, base_name)
1120-
sample.init_zone(
1121-
np.array([[5, 3, 0]]), zone=zone_name, base=base_name
1122-
)
1165+
sample.init_zone(np.array([[5, 3, 0]]), zone=zone_name, base=base_name)
11231166
sample.add_field(
11241167
name="test_elem_field_1",
11251168
field=cell_center_field,

0 commit comments

Comments
 (0)