Skip to content

Support atoms in showOccupancies#2246

Merged
jamesmkrieger merged 5 commits into
mainfrom
copilot/show-occupancies-support-atoms-kwarg
Jul 19, 2026
Merged

Support atoms in showOccupancies#2246
jamesmkrieger merged 5 commits into
mainfrom
copilot/show-occupancies-support-atoms-kwarg

Conversation

Copilot AI commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

showOccupancies accepted atoms= in practice but forwarded it to matplotlib, causing plotting to fail with an unexpected keyword argument error. It also did not use ensemble atoms by default, so occupancy plots could not align to residue numbering without manual workarounds.

  • Behavior change

    • Consume atoms inside showOccupancies instead of passing it through to plt.plot
    • Default to pdbensemble.getAtoms() when atoms is not provided
  • X-axis handling

    • When atoms are available, plot occupancies against atoms.getResnums()
    • Update the x-axis label from Atom index to Residue number for atom-aware plots
    • Preserve the existing atom-index behavior when no atoms are available
  • Validation

    • Add an explicit size check between computed occupancies and the provided atom set to fail early on mismatches
  • Regression coverage

    • Add focused tests for both:
      • explicit atoms= usage
      • implicit atom extraction from the ensemble

Example:

showOccupancies(dali_ens, normed=True, atoms=dali_ens.getAtoms())

This now plots occupancies against residue numbers instead of passing atoms through to matplotlib.

Copilot AI changed the title [WIP] Fix showOccupancies to support atoms kwarg or extract from ensemble Support atoms in showOccupancies Jul 13, 2026
Copilot AI requested a review from jamesmkrieger July 13, 2026 07:39
@jamesmkrieger
jamesmkrieger marked this pull request as ready for review July 16, 2026 11:30

@karolamik13 karolamik13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test below. I took one of my files from Dali.

In [1]: from prody import *
...: import matplotlib.pyplot as plt
...:
...: dali = DaliRecord('dali_ens.txt', '4nre', 'A', localFile=True)
...: pdbs = dali.filter(cutoff_len=0.7, cutoff_rmsd=2.0, cutoff_Z=10)[:20]
...: structures = parsePDB(pdbs, subset='ca')
...: dali_ens = buildPDBEnsemble(structures, mapping=dali.getMappings())
...:
...: showOccupancies(dali_ens, normed=True, atoms=dali_ens.getAtoms())
...: plt.show()
...:
@> Obtained 1720 PDB chains from Dali for 4nreA.
@> 1611 PDBs have been filtered out from 1720 Dali hits (remaining: 109).
@> 20 PDBs were parsed in 0.95s.
@> Starting iterative superposition:
@> Step #1: RMSD difference = 2.4160e+01
@> Step #2: RMSD difference = 3.0334e-01
@> Step #3: RMSD difference = 1.2812e-01
@> Step #4: RMSD difference = 7.8682e-02
@> Step #5: RMSD difference = 4.8933e-02
@> Step #6: RMSD difference = 3.0505e-02
@> Step #7: RMSD difference = 1.9036e-02
@> Step #8: RMSD difference = 1.1886e-02
@> Step #9: RMSD difference = 7.4247e-03
@> Step #10: RMSD difference = 4.6391e-03
@> Step #11: RMSD difference = 2.8992e-03
@> Step #12: RMSD difference = 1.8121e-03
@> Step #13: RMSD difference = 1.1328e-03
@> Step #14: RMSD difference = 7.0817e-04
@> Step #15: RMSD difference = 4.4275e-04
@> Step #16: RMSD difference = 2.7683e-04
@> Step #17: RMSD difference = 1.7309e-04
@> Step #18: RMSD difference = 1.0823e-04
@> Step #19: RMSD difference = 6.7681e-05
@> Iterative superposition completed in 0.08s.
@> Final superposition to calculate transformations.
@> Superposition completed in 0.00 seconds.
@> Ensemble (20 conformations) were built in 0.82s.

The outcome:

Figure_occupancy

@jamesmkrieger

Copy link
Copy Markdown
Contributor

Thanks, looks good. Can you confirm that the residue numbers start at around 0?

@karolamik13

Copy link
Copy Markdown
Contributor

Thanks, looks good. Can you confirm that the residue numbers start at around 0?

Yes, resid is 2. With almost 700 residues, it is around 0 on the plot.

@jamesmkrieger

Copy link
Copy Markdown
Contributor

Great. Can you try using ensemble select to confirm it changes please

@karolamik13

Copy link
Copy Markdown
Contributor

Great. Can you try using ensemble select to confirm it changes please

Sure:

In [2]: dali_ens
Out[2]: <PDBEnsemble: Unknown (20 conformations; 675 atoms)>

In [3]: dali_ens.getAtoms()
Out[3]: <AtomGroup: 4nreA_ca (675 atoms)>

In [4]: dali_ens.getDataLabels()
Out[4]: []

In [5]: dali_ens.getAtoms().getResnums()
Out[5]:
array([ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92,
93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,
106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144,
145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157,
158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170,
171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183,
184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196,
197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222,
223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235,
236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248,
249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261,
262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274,
275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287,
288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300,
301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313,
314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326,
327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339,
340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352,
353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365,
366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378,
379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391,
392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404,
405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417,
418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430,
431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443,
444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456,
457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469,
470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482,
483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495,
496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508,
509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521,
522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534,
535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547,
548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560,
561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573,
574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586,
587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599,
600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612,
613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625,
626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638,
639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651,
652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664,
665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676])

In [6]: dali_ens.getAtoms().getResnums().min()
Out[6]: 2

In [7]: dali_ens.getAtoms().getResnums().max()
Out[7]: 676

@jamesmkrieger

Copy link
Copy Markdown
Contributor

I meant if you use select or setAtoms on the ensemble to start at 20 or 100 then do we see the x axis change so we can confirm that it is really numbering by atoms. It’s hard to know for sure when the atoms start from 2

@jamesmkrieger

Copy link
Copy Markdown
Contributor

ok, I can confirm it

In [8]: ags = parsePDB(["prody/tests/datafiles/pdb3hsy.pdb", "prody/tests/datafiles/pdb3o21.pdb"], chain='A', subset='ca')
@> 2 PDBs were parsed in 0.01s.                              

In [9]: ens = buildPDBEnsemble(ags)
@> Starting iterative superposition:             
@> Step #1: RMSD difference = 1.6395e+00
@> Step #2: RMSD difference = 1.1942e-14
@> Iterative superposition completed in 0.00s.
@> Final superposition to calculate transformations.
@> Superposition completed in 0.00 seconds.
@> Ensemble (2 conformations) were built in 0.12s.

In [10]: showOccupancies(ens, normed=True, atoms=ens.getAtoms())
Out[10]: [<matplotlib.lines.Line2D at 0x131b78a60>]

In [11]: ens.select('resnum 50 to 300')

In [12]: plt.figure()
Out[12]: <Figure size 1280x960 with 0 Axes>

In [13]: showOccupancies(ens, normed=True, atoms=ens.getAtoms())
Out[13]: [<matplotlib.lines.Line2D at 0x1315cb850>]
image

@jamesmkrieger

Copy link
Copy Markdown
Contributor

However, if we put multiple chains then it doesn't know how to handle that, so this isn't what we want

@jamesmkrieger

Copy link
Copy Markdown
Contributor

However, if we put multiple chains then it doesn't know how to handle that, so this isn't what we want

@copilot wrap through showAtomicLines instead

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

However, if we put multiple chains then it doesn't know how to handle that, so this isn't what we want

@copilot wrap through showAtomicLines instead

Addressed in aff2a14. showOccupancies now routes the atom-aware path through showAtomicLines, and I updated the regression coverage to verify both single-chain and multi-chain atomic labeling behavior.

Comment thread prody/ensemble/functions.py Outdated
plt.xlabel('Atom index')
plt.ylabel('Sum of weights')
if SETTINGS['auto_show']:
if SETTINGS['auto_show'] and atoms is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot this line should stay unchanged

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in cdb0be2.

@jamesmkrieger

Copy link
Copy Markdown
Contributor

This now works as expected

In [1]: from prody import *

In [2]: ags = parsePDB(["prody/tests/datafiles/pdb3hsy.pdb", "prody/tests/datafiles/pdb3o21.pdb"], biomol=True, extend_biomol=Tru
   ...: e, subset='ca')
@> 2 PDBs were parsed in 0.03s.                             

In [3]: ens = buildPDBEnsemble(ags)
@> Starting iterative superposition:                          
@> Step #1: RMSD difference = 1.9801e+00
@> Step #2: RMSD difference = 1.2557e-02
@> Step #3: RMSD difference = 6.4942e-04
@> Step #4: RMSD difference = 3.6103e-05
@> Iterative superposition completed in 0.00s.
@> Final superposition to calculate transformations.
@> Superposition completed in 0.00 seconds.
@> Ensemble (3 conformations) were built in 0.74s.

In [4]: showOccupancies(ens, normed=True, atoms=ens.getAtoms())
Out[4]: [<matplotlib.lines.Line2D at 0x152322c50>]

In [5]: import matplotlib.pyplot as plt

In [6]: plt.ion()
Out[6]: <contextlib.ExitStack at 0x1231128c0>

In [7]: plt.show()
image

@jamesmkrieger

Copy link
Copy Markdown
Contributor

@copilot remove your new tests. We do not run unit tests on plotting functions and using PDBENSEMBLEA that only has 10 atoms with resnums 1 to 10 anyway doesn't help assess it anyway

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot remove your new tests. We do not run unit tests on plotting functions and using PDBENSEMBLEA that only has 10 atoms with resnums 1 to 10 anyway doesn't help assess it anyway

Removed in 440d539.

Copilot AI requested a review from jamesmkrieger July 19, 2026 16:07
@jamesmkrieger
jamesmkrieger merged commit 6f0d937 into main Jul 19, 2026
@jamesmkrieger
jamesmkrieger deleted the copilot/show-occupancies-support-atoms-kwarg branch July 19, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

showOccupancies should support atoms kwarg or extract them from ensemble

3 participants