diff --git a/CHANGELOG.md b/CHANGELOG.md index bdb0939..6618a16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ API changes; these are called out under **Changed** where they occur. ## [Unreleased] +### Changed + +- **Breaking:** the ``native-3d`` descriptor preset no longer emits the six + absolute-coordinate columns ``centroid_x/y/z`` and ``center_of_mass_x/y/z`` + (preset width drops from 80 to 74). These recorded where a structure happened + to sit in its input file's coordinate frame, so they were translation- and + rotation-variant: two identical molecules placed differently produced different + features, letting a model learn the arbitrary input frame. Size and shape are + already captured frame-independently by ``radius_of_gyration``, ``dim_x/y/z``, + ``principal_moments`` and the gyration-tensor shape scalars. The ``native-basic`` + and ``rdkit-basic`` presets are unaffected. + ## [0.15.0] - 2026-06-07 ### Added diff --git a/molscope/descriptors.py b/molscope/descriptors.py index 7616286..e9f90d5 100644 --- a/molscope/descriptors.py +++ b/molscope/descriptors.py @@ -97,15 +97,7 @@ def descriptors( return _apply_preset(desc, preset, elements_to_count, distance_bins, rdkit_prefix) dims = molecule.dimensions - centroid = molecule.centroid - center_of_mass = molecule.center_of_mass desc.update({ - "centroid_x": float(centroid[0]), - "centroid_y": float(centroid[1]), - "centroid_z": float(centroid[2]), - "center_of_mass_x": float(center_of_mass[0]), - "center_of_mass_y": float(center_of_mass[1]), - "center_of_mass_z": float(center_of_mass[2]), "radius_of_gyration": molecule.radius_of_gyration, "dim_x": float(dims[0]), "dim_y": float(dims[1]), @@ -462,12 +454,6 @@ def flatten_descriptors(desc: dict) -> dict[str, float]: def _empty_descriptors(desc: dict, distance_bins: int) -> dict: desc.update({ - "centroid_x": 0.0, - "centroid_y": 0.0, - "centroid_z": 0.0, - "center_of_mass_x": 0.0, - "center_of_mass_y": 0.0, - "center_of_mass_z": 0.0, "radius_of_gyration": 0.0, "dim_x": 0.0, "dim_y": 0.0, @@ -674,12 +660,6 @@ def _preset_scalar_names(preset: str, elements_to_count, rdkit_prefix: str) -> l ] if preset == "native-3d": names += [ - "centroid_x", - "centroid_y", - "centroid_z", - "center_of_mass_x", - "center_of_mass_y", - "center_of_mass_z", "shape_anisotropy", "asphericity", "acylindricity", diff --git a/tests/test_descriptors.py b/tests/test_descriptors.py index 7dda120..641a1cc 100644 --- a/tests/test_descriptors.py +++ b/tests/test_descriptors.py @@ -108,6 +108,20 @@ def test_native_3d_preset_includes_shape_descriptors(): assert "asphericity" not in water().descriptors(preset="native-basic") +def test_native_3d_omits_pose_variant_absolute_coordinates(): + # Absolute centroid / centre-of-mass coordinates are translation- and + # rotation-variant, so they were dropped from the ML feature table. + desc = water().descriptors(preset="native-3d") + abs_cols = { + "centroid_x", "centroid_y", "centroid_z", + "center_of_mass_x", "center_of_mass_y", "center_of_mass_z", + } + assert abs_cols.isdisjoint(desc) + assert abs_cols.isdisjoint(descriptor_feature_names("native-3d")) + # The default (preset=None) output drops them too, not just the named preset. + assert abs_cols.isdisjoint(water().descriptors()) + + def test_flatten_descriptors_expands_vector_features(): flat = flatten_descriptors({"n_atoms": 3.0, "principal_moments": [1.0, 2.0, 3.0]}) assert flat == {