diff --git a/CHANGELOG.md b/CHANGELOG.md index 9027a5a4..672e80b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- (sample/features) add_field: check field size consistency with geometrical support. + ### Changed ### Fixes diff --git a/examples/containers/bench_parallel_load.py b/examples/containers/bench_parallel_load.py index 84d1769d..8390b3bc 100644 --- a/examples/containers/bench_parallel_load.py +++ b/examples/containers/bench_parallel_load.py @@ -78,7 +78,8 @@ # ---# Add some random data smp.add_scalar("id", i) smp.add_scalar("s0", np.random.randn()) - smp.add_field("f0", np.random.randn(100)) + n_nodes = smp.get_nodes().shape[0] + smp.add_field("f0", np.random.randn(n_nodes)) dset.add_sample(smp) diff --git a/examples/containers/sample_example.py b/examples/containers/sample_example.py index d0aae52d..d5864261 100644 --- a/examples/containers/sample_example.py +++ b/examples/containers/sample_example.py @@ -172,7 +172,7 @@ def show_sample(sample: Sample): # %% # Init CGNS tree zone to a base at time 0. -shape = np.array((len(points), len(triangles), 0)) +shape = np.array([[len(points), len(triangles), 0]]) sample.init_zone(shape, zone_name="TestZoneName", base_name="SurfaceMesh", time=0.0) show_sample(sample) @@ -454,7 +454,7 @@ def show_sample(sample: Sample): # Create a new zone in 'SurfaceMesh' base sample.init_zone( - zone_shape=np.array([5, 3, 0]), + zone_shape=np.array([[5, 3, 0]]), zone_type=CGK.Structured_s, zone_name="new_zone", base_name="SurfaceMesh", diff --git a/examples/convert_users_data_example.py b/examples/convert_users_data_example.py index 59b7bdbb..8cbc76f1 100644 --- a/examples/convert_users_data_example.py +++ b/examples/convert_users_data_example.py @@ -169,7 +169,7 @@ def in_notebook(): # Add random field values to the sample for j, sname in enumerate(out_fields_names): - sample.add_field(sname, np.random.rand(1, len(nodes_3D))) + sample.add_field(sname, np.random.rand(len(nodes_3D))) samples.append(sample) diff --git a/examples/utils/stats_example.py b/examples/utils/stats_example.py index ffbc5007..f0a65fcf 100644 --- a/examples/utils/stats_example.py +++ b/examples/utils/stats_example.py @@ -130,8 +130,9 @@ def sprint(stats: dict): for sample in samples: sample.add_scalar("test_scalar", np.random.randn()) sample.init_base(2, 3, "test_base") - zone_shape = np.array([0, 0, 0]) + zone_shape = np.array([[spatial_shape_max, 0, 0]]) sample.init_zone(zone_shape, zone_name="test_zone") + sample.set_nodes(np.zeros((spatial_shape_max, 3))) sample.add_field("test_field", np.random.randn(spatial_shape_max)) stats.add_samples(samples) @@ -154,12 +155,13 @@ def sprint(stats: dict): for sample in samples: sample.add_scalar("test_scalar", np.random.randn()) sample.init_base(2, 3, "test_base") - zone_shape = np.array([0, 0, 0]) + zone_shape = np.array([[spatial_shape_max, 0, 0]]) sample.init_zone(zone_shape, zone_name="test_zone") - sample.add_field("test_field_same_size", np.random.randn(7)) + sample.set_nodes(np.zeros((spatial_shape_max, 3))) + sample.add_field("test_field_same_size", np.random.randn(spatial_shape_max)) sample.add_field( "test_field", - np.random.randn(np.random.randint(spatial_shape_max // 2, spatial_shape_max)), + np.random.randn(spatial_shape_max), ) stats.add_samples(samples) diff --git a/src/plaid/containers/features.py b/src/plaid/containers/features.py index d6fe9845..86fd54ac 100644 --- a/src/plaid/containers/features.py +++ b/src/plaid/containers/features.py @@ -1174,6 +1174,10 @@ def add_field( KeyError: Raised if the specified zone does not exist in the given base. """ assert isinstance(field, np.ndarray) + if field.ndim != 1: + raise ValueError( + f"field has {field.ndim} dimensions, but must be a 1D array." + ) _check_names([name]) # init_tree will look for default time self.init_tree(time) @@ -1185,6 +1189,17 @@ def add_field( f"there is no Zone with name {zone_name} in base {base_name}. Did you check topological and physical dimensions ?" ) + # Check field size consistency with its geometrical support + n_nodes, n_elems, _ = zone_node[1][0] + if location == "Vertex" and field.shape[0] != n_nodes: + raise ValueError( + f"field has {field.shape[0]} nodes but zone has {n_nodes} nodes (based on the zone node metadata)" + ) + elif location == "CellCenter" and field.shape[0] != n_elems: + raise ValueError( + f"field has {field.shape[0]} nodes but zone has {n_elems} elements (based on the zone node metadata)" + ) + if field.dtype in (np.int32, np.int64): logger.warning( f"(add_field) provided field is of type {field.dtype} and has been converted to np.float64 for CGNS compatibility." diff --git a/tests/conftest.py b/tests/conftest.py index 80e8e3ee..09c1aec9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,9 @@ def generate_samples_no_string(nb: int, zone_name: str, base_name: str) -> list[ for i in range(nb): sample = Sample() sample.init_base(3, 3, base_name) - sample.init_zone(np.array([0, 0, 0]), zone_name=zone_name, base_name=base_name) + sample.init_zone( + np.array([[17, 10, 0]]), zone_name=zone_name, base_name=base_name + ) sample.add_scalar("test_scalar", float(i)) sample.add_scalar("test_scalar_2", float(i**2)) sample.add_global("global_0", 0.5 + np.ones((2, 3))) @@ -41,9 +43,10 @@ def generate_samples_no_string(nb: int, zone_name: str, base_name: str) -> list[ ) sample.add_field( name="test_field_2785", - field=float(i**5) * np.ones(3 * (i + 1)), + field=float(i**5) * np.ones(10), zone_name=zone_name, base_name=base_name, + location="CellCenter", ) sample_list.append(sample) return sample_list @@ -150,7 +153,7 @@ def muscat_mesh(nodes, triangles, vertex_field, cell_center_field, nodal_tags): muscat_mesh = MCT.CreateMeshOfTriangles(nodes, triangles) muscat_mesh.GetNodalTag("tag").AddToTag(nodal_tags) muscat_mesh.nodeFields["test_node_field_1"] = vertex_field - muscat_mesh.nodeFields["big_node_field"] = np.random.randn(50) + muscat_mesh.nodeFields["big_node_field"] = np.random.randn(5) muscat_mesh.elemFields["test_elem_field_1"] = cell_center_field return muscat_mesh diff --git a/tests/containers/test_dataset.py b/tests/containers/test_dataset.py index 2099fc88..067a39f8 100644 --- a/tests/containers/test_dataset.py +++ b/tests/containers/test_dataset.py @@ -344,7 +344,7 @@ def test_get_field_names(self, dataset_with_samples, nb_samples): ) == sorted(["test_field_2785", "test_field_same_size"]) assert sorted( dataset_with_samples.get_field_names(location="Vertex") - ) == sorted(["test_field_2785", "test_field_same_size"]) + ) == sorted(["test_field_same_size"]) assert sorted( dataset_with_samples.get_field_names(zone_name="Zone_Name") ) == sorted(["test_field_2785", "test_field_same_size"]) @@ -745,13 +745,13 @@ def test_get_tabular_from_homogeneous_identifiers_inconsistent_features_through_ dataset_with_samples_with_tree_ = copy.deepcopy(dataset_with_samples_with_tree) for sample in dataset_with_samples_with_tree_: sample.add_field( - "test_node_field_1", np.array([0, 1]), warning_overwrite=False + "test_node_field_1", np.array([0, 1, 2, 3, 4]), warning_overwrite=False ) with pytest.raises(AssertionError): dataset_with_samples_with_tree_.get_tabular_from_homogeneous_identifiers( feature_identifiers=[ FeatureIdentifier({"type": "field", "name": "test_node_field_1"}), - FeatureIdentifier({"type": "field", "name": "OriginalIds"}), + FeatureIdentifier({"type": "field", "name": "test_elem_field_1"}), ], ) @@ -760,16 +760,21 @@ def test_get_tabular_from_homogeneous_identifiers_inconsistent_features_through_ ): dataset_with_samples_with_tree_ = copy.deepcopy(dataset_with_samples_with_tree) dataset_with_samples_with_tree_[0].add_field( - "test_node_field_1", np.array([0, 1]), warning_overwrite=False + "test_node_field_1", + np.array([0, 1, 2, 3, 4], dtype=np.float32), + warning_overwrite=False, ) dataset_with_samples_with_tree_[0].add_field( - "OriginalIds", np.array([0, 1]), warning_overwrite=False + "OriginalIds", + np.array([0, 1, 2], dtype=np.float32), + location="CellCenter", + warning_overwrite=False, ) with pytest.raises(AssertionError): dataset_with_samples_with_tree_.get_tabular_from_homogeneous_identifiers( feature_identifiers=[ FeatureIdentifier({"type": "field", "name": "test_node_field_1"}), - FeatureIdentifier({"type": "field", "name": "OriginalIds"}), + FeatureIdentifier({"type": "field", "name": "test_elem_field_1"}), ], ) diff --git a/tests/containers/test_sample.py b/tests/containers/test_sample.py index afbd4a8f..50808222 100644 --- a/tests/containers/test_sample.py +++ b/tests/containers/test_sample.py @@ -99,10 +99,10 @@ def full_sample(sample_with_tree_and_scalar: Sample, tree3d): sample_with_tree_and_scalar.add_scalar("r", np.random.randn()) sample_with_tree_and_scalar.add_scalar("test_scalar_1", np.random.randn()) sample_with_tree_and_scalar.add_field( - name="test_field_1", field=np.random.randn(5, 3), location="CellCenter" + name="test_field_1", field=np.random.randn(3), location="CellCenter" ) sample_with_tree_and_scalar.init_zone( - zone_shape=np.array([5, 3]), zone_name="test_field_1" + zone_shape=np.array([[5, 3, 0]]), zone_name="test_field_1" ) sample_with_tree_and_scalar.init_base( topological_dim=2, physical_dim=3, base_name="test_base_1" @@ -575,7 +575,7 @@ def test_del_zone_no_cgns_tree(self, sample: Sample): def test_has_zone(self, sample, base_name, zone_name): sample.init_base(3, 3, base_name) sample.init_zone( - np.random.randint(0, 10, size=3), zone_name=zone_name, base_name=base_name + np.array([[5, 3, 0]]), zone_name=zone_name, base_name=base_name ) sample.show_tree() assert sample.features.has_zone(zone_name, base_name) @@ -588,12 +588,12 @@ def test_has_zone(self, sample, base_name, zone_name): def test_get_zone_names(self, sample: Sample, base_name): sample.init_base(3, 3, base_name) sample.init_zone( - np.random.randint(0, 10, size=3), + np.array([[5, 3, 0]]), zone_name="zone_name_1", base_name=base_name, ) sample.init_zone( - np.random.randint(0, 10, size=3), + np.array([[5, 3, 0]]), zone_name="zone_name_2", base_name=base_name, ) @@ -619,7 +619,7 @@ def test_get_zone_type(self, sample: Sample, zone_name, base_name): with pytest.raises(KeyError): sample.features.get_zone_type(zone_name, base_name) sample.init_zone( - np.random.randint(0, 10, size=3), zone_name=zone_name, base_name=base_name + np.array([[5, 3, 0]]), zone_name=zone_name, base_name=base_name ) assert sample.features.get_zone_type(zone_name, base_name) == CGK.Unstructured_s @@ -628,12 +628,12 @@ def test_get_zone(self, sample: Sample, zone_name, base_name): sample.init_base(3, 3, base_name) assert sample.features.get_zone(zone_name, base_name) is None sample.init_zone( - np.random.randint(0, 10, size=3), zone_name=zone_name, base_name=base_name + np.array([[5, 3, 0]]), zone_name=zone_name, base_name=base_name ) assert sample.features.get_zone() is not None assert sample.features.get_zone(zone_name, base_name) is not None sample.init_zone( - np.random.randint(0, 10, size=3), + np.array([[5, 3, 0]]), zone_name="other_zone_name", base_name=base_name, ) @@ -790,56 +790,56 @@ def test_get_field_names_several_bases(self): topological_dim=3, physical_dim=3, base_name="Base_3_3", time=1.0 ) sample.init_zone( - zone_shape=np.array([0, 0, 0]), + zone_shape=np.array([[5, 3, 0]]), zone_name="Zone_1", base_name="Base_1_2", time=-0.1, ) sample.init_zone( - zone_shape=np.array([0, 0, 0]), + zone_shape=np.array([[5, 3, 0]]), zone_name="Zone_2", base_name="Base_1_2", time=-0.1, ) sample.init_zone( - zone_shape=np.array([0, 0, 0]), + zone_shape=np.array([[5, 3, 0]]), zone_name="Zone_1", base_name="Base_2_2", time=-0.1, ) sample.init_zone( - zone_shape=np.array([0, 0, 0]), + zone_shape=np.array([[5, 3, 0]]), zone_name="Zone_2", base_name="Base_2_2", time=-0.1, ) sample.init_zone( - zone_shape=np.array([0, 0, 0]), + zone_shape=np.array([[5, 3, 0]]), zone_name="Zone_1", base_name="Base_1_3", time=1.0, ) sample.init_zone( - zone_shape=np.array([0, 0, 0]), + zone_shape=np.array([[5, 3, 0]]), zone_name="Zone_2", base_name="Base_1_3", time=1.0, ) sample.init_zone( - zone_shape=np.array([0, 0, 0]), + zone_shape=np.array([[5, 3, 0]]), zone_name="Zone_1", base_name="Base_3_3", time=1.0, ) sample.init_zone( - zone_shape=np.array([0, 0, 0]), + zone_shape=np.array([[5, 3, 0]]), zone_name="Zone_2", base_name="Base_3_3", time=1.0, ) sample.add_field( name="vertex_Zone_1_Base_1_2_t_m0.1", - field=np.random.randn(10), + field=np.random.randn(5), location="Vertex", zone_name="Zone_1", base_name="Base_1_2", @@ -847,7 +847,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="cell_Zone_1_Base_1_2_t_m0.1", - field=np.random.randn(10), + field=np.random.randn(3), location="CellCenter", zone_name="Zone_1", base_name="Base_1_2", @@ -855,7 +855,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="vertex_Zone_2_Base_1_2_t_m0.1", - field=np.random.randn(10), + field=np.random.randn(5), location="Vertex", zone_name="Zone_2", base_name="Base_1_2", @@ -863,7 +863,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="cell_Zone_2_Base_1_2_t_m0.1", - field=np.random.randn(10), + field=np.random.randn(3), location="CellCenter", zone_name="Zone_2", base_name="Base_1_2", @@ -871,7 +871,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="vertex_Zone_1_Base_2_2_t_m0.1", - field=np.random.randn(10), + field=np.random.randn(5), location="Vertex", zone_name="Zone_1", base_name="Base_2_2", @@ -879,7 +879,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="cell_Zone_1_Base_2_2_t_m0.1", - field=np.random.randn(10), + field=np.random.randn(3), location="CellCenter", zone_name="Zone_1", base_name="Base_2_2", @@ -887,7 +887,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="vertex_Zone_2_Base_2_2_t_m0.1", - field=np.random.randn(10), + field=np.random.randn(5), location="Vertex", zone_name="Zone_2", base_name="Base_2_2", @@ -895,7 +895,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="cell_Zone_2_Base_2_2_t_m0.1", - field=np.random.randn(10), + field=np.random.randn(3), location="CellCenter", zone_name="Zone_2", base_name="Base_2_2", @@ -903,7 +903,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="vertex_Zone_1_Base_1_3_t_1.0", - field=np.random.randn(10), + field=np.random.randn(5), location="Vertex", zone_name="Zone_1", base_name="Base_1_3", @@ -911,7 +911,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="cell_Zone_1_Base_1_3_t_1.0", - field=np.random.randn(10), + field=np.random.randn(3), location="CellCenter", zone_name="Zone_1", base_name="Base_1_3", @@ -919,7 +919,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="vertex_Zone_2_Base_1_3_t_1.0", - field=np.random.randn(10), + field=np.random.randn(5), location="Vertex", zone_name="Zone_2", base_name="Base_1_3", @@ -927,7 +927,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="cell_Zone_2_Base_1_3_t_1.0", - field=np.random.randn(10), + field=np.random.randn(3), location="CellCenter", zone_name="Zone_2", base_name="Base_1_3", @@ -935,7 +935,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="vertex_Zone_1_Base_3_3_t_1.0", - field=np.random.randn(10), + field=np.random.randn(5), location="Vertex", zone_name="Zone_1", base_name="Base_3_3", @@ -943,7 +943,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="cell_Zone_1_Base_3_3_t_1.0", - field=np.random.randn(10), + field=np.random.randn(3), location="CellCenter", zone_name="Zone_1", base_name="Base_3_3", @@ -951,7 +951,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="vertex_Zone_2_Base_3_3_t_1.0", - field=np.random.randn(10), + field=np.random.randn(5), location="Vertex", zone_name="Zone_2", base_name="Base_3_3", @@ -959,7 +959,7 @@ def test_get_field_names_several_bases(self): ) sample.add_field( name="cell_Zone_2_Base_3_3_t_1.0", - field=np.random.randn(10), + field=np.random.randn(3), location="CellCenter", zone_name="Zone_2", base_name="Base_3_3", @@ -986,7 +986,7 @@ def test_get_field_names_several_bases(self): assert sample.get_field_names() == sorted(set(expected_field_names)) sample.add_field( name="field_of_ints", - field=np.arange(10), + field=np.arange(5), zone_name="Zone_2", base_name="Base_3_3", time=1.0, @@ -1016,8 +1016,15 @@ def test_add_field_vertex(self, sample: Sample, vertex_field, zone_name, base_na zone_name=zone_name, base_name=base_name, ) + with pytest.raises(ValueError): + sample.add_field( + name="test_node_field_2", + field=np.zeros((5, 2)), + zone_name=zone_name, + base_name=base_name, + ) sample.init_zone( - np.random.randint(0, 10, size=3), zone_name=zone_name, base_name=base_name + np.array([[5, 3, 0]]), zone_name=zone_name, base_name=base_name ) sample.add_field( name="test_node_field_2", @@ -1025,6 +1032,13 @@ def test_add_field_vertex(self, sample: Sample, vertex_field, zone_name, base_na zone_name=zone_name, base_name=base_name, ) + with pytest.raises(ValueError): + sample.add_field( + name="test_node_field_2", + field=np.zeros((13)), + zone_name=zone_name, + base_name=base_name, + ) def test_add_field_cell_center( self, sample: Sample, cell_center_field, zone_name, base_name @@ -1039,7 +1053,7 @@ def test_add_field_cell_center( base_name=base_name, ) sample.init_zone( - np.random.randint(0, 10, size=3), zone_name=zone_name, base_name=base_name + np.array([[5, 3, 0]]), zone_name=zone_name, base_name=base_name ) sample.add_field( name="test_elem_field_2", @@ -1048,6 +1062,14 @@ def test_add_field_cell_center( zone_name=zone_name, base_name=base_name, ) + with pytest.raises(ValueError): + sample.add_field( + name="test_elem_field_2", + location="CellCenter", + field=np.zeros((13)), + zone_name=zone_name, + base_name=base_name, + ) def test_add_field_vertex_already_present( self, sample_with_tree: Sample, vertex_field @@ -1105,7 +1127,7 @@ def test_del_field_in_zone(self, zone_name, base_name, cell_center_field): sample = Sample() sample.init_base(3, 3, base_name) sample.init_zone( - np.random.randint(0, 10, size=3), zone_name=zone_name, base_name=base_name + np.array([[5, 3, 0]]), zone_name=zone_name, base_name=base_name ) sample.add_field( name="test_elem_field_1", diff --git a/tests/pipelines/conftest.py b/tests/pipelines/conftest.py index 4a8da522..fec95327 100644 --- a/tests/pipelines/conftest.py +++ b/tests/pipelines/conftest.py @@ -82,6 +82,10 @@ def wrapped_sklearn_transformer_2(sklearn_pca): return WrappedSklearnTransformer( sklearn_block=sklearn_pca, in_features_identifiers=[{"type": "field", "name": "test_field_same_size"}], + out_features_identifiers=[ + {"type": "scalar", "name": "pca_component_0"}, + {"type": "scalar", "name": "pca_component_1"}, + ], ) diff --git a/tests/utils/test_stats.py b/tests/utils/test_stats.py index 10382ed9..523b06b9 100644 --- a/tests/utils/test_stats.py +++ b/tests/utils/test_stats.py @@ -44,7 +44,7 @@ def np_samples_5(): @pytest.fixture() def np_samples_6(): - return np.random.randn(50, 1) + return np.random.randn(50) @pytest.fixture() @@ -71,7 +71,7 @@ def sample_with_field(np_samples_6): s.features.init_tree() # 2. Create a base and a zone s.init_base(topological_dim=3, physical_dim=3) - s.init_zone(zone_shape=np.array([np_samples_6.shape[0], 0, 0])) + s.init_zone(zone_shape=np.array([[np_samples_6.shape[0], 0, 0]])) # 3. Set node coordinates (required for a valid zone) s.set_nodes(np.zeros((np_samples_6.shape[0], 3))) # 4. Add a field named "bar"