From 090b58eeb40e754161d9dddc5d23a35008a93d70 Mon Sep 17 00:00:00 2001 From: Apoorva Verma Date: Mon, 29 Jun 2026 22:12:18 +0530 Subject: [PATCH] BUG: combine cmaps when merging Universes Merge() handles the connection attributes bonds, angles, dihedrals and impropers through a dedicated path that remaps their atom indices onto the merged Universe; every other topology attribute is concatenated as a plain array. cmaps is a connection attribute as well, but it was missing from that set, so a Universe carrying cmaps fell through to the array path and raised TypeError: Encountered unexpected topology attribute of type Add cmaps to the set so it is merged the same way as the other connection groups. Issue #3672 --- package/AUTHORS | 1 + package/CHANGELOG | 5 ++++- package/MDAnalysis/core/universe.py | 2 +- .../MDAnalysisTests/utils/test_modelling.py | 21 +++++++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/package/AUTHORS b/package/AUTHORS index c4ee0e250aa..da88e54dbd9 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -281,6 +281,7 @@ Chronological list of authors - Shubham Mittal - Charity Grey - Sai Udayagiri + - Apoorva Verma External code ------------- diff --git a/package/CHANGELOG b/package/CHANGELOG index 80385b32993..8ffa66069d8 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -17,11 +17,14 @@ The rules for this file: ??/??/?? IAlibay, orbeckst, marinegor, tylerjereddy, ljwoods2, marinegor, spyke7, talagayev, tanii1125, BradyAJohnston, hejamu, jeremyleung521, harshitgajjela-droid, kunjsinha, aygarwal, jauy123, Dreamstick9, - ollyfutur, Amarendra22, charity-g, ParthUppal523 + ollyfutur, Amarendra22, charity-g, ParthUppal523, apoorva-01 * 2.11.0 Fixes + * `Merge()` no longer raises a TypeError on Universes that have a `cmaps` + attribute; cmaps are now combined like the other connection attributes + (bonds, angles, dihedrals, impropers) (Issue #3672). * The `principal_axes` method in :class:`Masses` now uses `np.linalg.eigh` instead of `np.linalg.eig`, improving numerical stability. This may lead to slightly different results from previous diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index 4b9ec247bd8..e4534461b71 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -2162,7 +2162,7 @@ def Merge(*args): common_attrs = set.intersection( *[set(dir(ag.universe._topology)) for ag in args] ) - tops = set(["bonds", "angles", "dihedrals", "impropers"]) + tops = set(["bonds", "angles", "dihedrals", "impropers", "cmaps"]) attrs = [] diff --git a/testsuite/MDAnalysisTests/utils/test_modelling.py b/testsuite/MDAnalysisTests/utils/test_modelling.py index c014c9f4d03..2bf45502e5c 100644 --- a/testsuite/MDAnalysisTests/utils/test_modelling.py +++ b/testsuite/MDAnalysisTests/utils/test_modelling.py @@ -329,3 +329,24 @@ def test_merge_without_topology(self, u): assert len(u_merge.atoms.angles) == 0 assert len(u_merge.atoms.dihedrals) == 0 assert len(u_merge.atoms.impropers) == 0 + + def test_merge_with_cmaps(self, u): + # cmaps are a connection attribute like bonds/angles, so Merge() must + # route them through the connection path rather than treating them as a + # plain array (Issue #3672). + u.add_TopologyAttr( + "cmaps", [[0, 1, 2, 3, 4], [100, 101, 102, 103, 104]] + ) + + ag1 = u.atoms[:20] + ag2 = u.atoms[100:110] + u_merge = MDAnalysis.Merge(ag1, ag2) + + # Both cmaps lie entirely within their atomgroup, so both survive and + # are renumbered against the merged universe (ag2 is offset by len(ag1)). + assert hasattr(u_merge.atoms, "cmaps") + assert len(u_merge.atoms.cmaps) == 2 + merged = sorted( + tuple(int(i) for i in c.indices) for c in u_merge.atoms.cmaps + ) + assert merged == [(0, 1, 2, 3, 4), (20, 21, 22, 23, 24)]