Skip to content

Expose computed density in InterRDF results - #5482

Open
sumitsaraswat362 wants to merge 15 commits into
MDAnalysis:developfrom
sumitsaraswat362:fix-rdf-density-dev
Open

sumitsaraswat362 wants to merge 15 commits into
MDAnalysis:developfrom
sumitsaraswat362:fix-rdf-density-dev

Conversation

@sumitsaraswat362

@sumitsaraswat362 sumitsaraswat362 commented Oct 10, 2026 •

Copy link
Copy Markdown

Fixes #5362

Changes made in this Pull Request:

  • Expose computed average number density as results.density (and alias results.rho) in InterRDF and InterRDF_s when norm is "rdf" or "density".
  • Guard against zero-division / zero box volume cases when computing density.
  • Add unit tests for exposed density in test_rdf.py and test_rdf_s.py.
  • Update package/CHANGELOG and package/AUTHORS.

LLM / AI generated code disclosure

LLMs or other AI-powered tools (beyond simple IDE use cases) were used in this contribution: no

PR Checklist

  • Issue raised/referenced?
  • Tests updated/added?
  • Documentation updated/added?
  • package/CHANGELOG file updated?
  • Is your name in package/AUTHORS? (If it is not, add it!)
  • I have read and understand the current AI Policy
  • LLM/AI disclosure was updated.

Developers Certificate of Origin

I certify that I can submit this code contribution as described in the Developer Certificate of Origin, under the MDAnalysis LICENSE.

@read-the-docs-community

read-the-docs-community Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.92%. Comparing base (6cd1f7e) to head (436b64a).

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #5482   +/-   ##
========================================
  Coverage    93.91%   93.92%           
========================================
  Files          184      184           
  Lines        22644    22661   +17     
  Branches      3219     3223    +4     
========================================
+ Hits         21267    21284   +17     
  Misses         917      917           
  Partials       460      460           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sici17

sici17 commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

I think that trying to compute the average density value in the norm="density" case might lead to zero division erros given that a nonzero box_vol is not actually required in these cases in order to compute the distance dependent density. Probably the two cases should be handled separately. I am talking about the line 401 of the diff. I am not an expert or anything though so I would wait someone with more expertise to express his opinion obviously. I was just practicing code reviewing and understing.

- Only compute results.density/results.rho when box_vol > 0, otherwise set to None
- Expose results.rho alias alongside results.density as suggested in MDAnalysis#5362
@sumitsaraswat362

Copy link
Copy Markdown
Author

Thanks for the great catch, @sici17! You are completely right—for non-periodic trajectories or simulations without box dimensions where norm="density" is used, box_vol can be zero, which would have triggered an unintended ZeroDivisionError.

I've just pushed a commit guarding against this:

  1. results.density (and results.rho) is now only computed when box_vol > 0, and set to None if no box volume is available.
  2. In the norm="rdf" path, it proceeds with normalization as before.
  3. Also exposed results.rho as an alias alongside results.density as originally suggested in Calculating the rdf for norm = "rdf" or "density" at the same time. #5362.

Really appreciate you taking the time to review!

@orbeckst

Copy link
Copy Markdown
Member

Before we can review this PR, please restore the original PR template https://github.com/MDAnalysis/mdanalysis/blob/develop/.github/PULL_REQUEST_TEMPLATE.md and answer all questions.

@orbeckst orbeckst added the more information needed Please reply to requests for information or the issue will be closed. label Oct 13, 2026
@sumitsaraswat362

Copy link
Copy Markdown
Author

@orbeckst I have restored the official PR template in the PR description and answered all questions, updated package/CHANGELOG, added my name to package/AUTHORS, and added unit tests in test_rdf.py and test_rdf_s.py.

@sici17 Good catch! I added guards for zero box volume / non-periodic systems (box_vol > 0 and volume_cum > 0) so that results.density and results.rho are cleanly set to None without raising any ZeroDivisionError.

@orbeckst orbeckst removed the more information needed Please reply to requests for information or the issue will be closed. label Oct 15, 2026

@orbeckst orbeckst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Quick initial review

  • only include results.density (drop results.rho) — there should be only one way to access the data
  • add documentation, for instance, results.density needs to be explained where the other results attributes are listed; add a .. versionadded:: 2.11.0

These are the first things I noticed, there may be more comments.

@orbeckst

Copy link
Copy Markdown
Member

Please also resolve the conflicts with the develop branch.

@sumitsaraswat362

Copy link
Copy Markdown
Author

@orbeckst Thanks for the review!

I have addressed the points:

  • Dropped results.rho completely so results.density is the sole way to access the data.
  • Added documentation for results.density under the Attributes section of both InterRDF and InterRDF_s with .. versionadded:: 2.11.0.
  • Merged the latest develop branch and resolved the merge conflict in package/CHANGELOG.
  • Updated test assertions in test_rdf.py and test_rdf_s.py.

@orbeckst orbeckst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The computed "density" is $N_a N_b / \langle V \rangle$. This is not a real density and it's not the $\rho$ from the docs at the top. One could write it as $N_a \rho_b = N_b \rho_a$. We cannot store this as an attribute results.density because it's not a density.

(Furthermore, it's confusing because norm="rdf gives results.rdf and then people/LLMs may look for results.density for norm="density".)

We could store it as results.normalization_density or something like that. What would be a good name? Does it still make sense to store this quantity or should we be storing $\rho_a$, $\rho_b$, $\langle V \rangle$ as well?

Whatever we store, we need then document properly, i.e., write down the equations that define the quantity.

We cannot really continue with the PR until we have decided this.

@sumitsaraswat362

Copy link
Copy Markdown
Author

Thank you for pointing this out, Oliver. You are completely right. The quantity $N_a N_b / \langle V \rangle$ is the pair normalization density $N_a \rho_b$ rather than the single-species density $\rho$, and exposing it as results.density is inaccurate and misleading for coordination calculations ($N_{ab} = \rho G_{ab}$). It also collides with norm="density" where users expect the radial profile.

I have updated the implementation to address this:

  1. results.normalization_density: Renamed from results.density, storing the pair normalization density $N_{\text{pairs}} / \langle V \rangle = (N_a N_b - N_{\text{excl}}) / \langle V \rangle$ used in the RDF denominator (or $1 / \langle V \rangle$ for InterRDF_s).
  2. results.box_volume: Stores the trajectory-averaged box volume $\langle V \rangle$ across analyzed frames.
  3. results.density_a and results.density_b: Store the true bulk number densities $\rho_a = N_a / \langle V \rangle$ and $\rho_b = N_b / \langle V \rangle$, where $\rho_b$ is the density $\rho$ appearing in the documentation for $n_{ab}(r) = \rho g_{ab}(r)$ and $N_{ab}(r) = \rho G_{ab}(r)$.
  4. If the box volume is zero or unavailable, all four attributes evaluate to None.
  5. When norm="none", none of these normalization attributes are set.

The equations and physical definitions have been added to the docstrings of both InterRDF and InterRDF_s with .. versionadded:: 2.11.0, and the test suites in test_rdf.py and test_rdf_s.py have been updated with numerical assertions against the theoretical values.

@orbeckst orbeckst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like your new attributes and docs. Please see comment below for necessary updates.

s1, s2 = sels
# When norm is "rdf"
rdf = InterRDF(s1, s2, norm="rdf").run(**client_InterRDF)
assert not hasattr(rdf.results, "density")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove this assertion — why is this here??


# When norm is "density"
rdf_dense = InterRDF(s1, s2, norm="density").run(**client_InterRDF)
assert not hasattr(rdf_dense.results, "density")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove this assertion — why is this here??

Comment thread package/MDAnalysis/analysis/rdf.py Outdated
.. versionadded:: 2.11.0

results.density_a : float
The bulk number density :math:`\rho_a = N_a / \langle V \rangle` of the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

rename to density_1 and density_2 and change the equations in the doc string to use 1 instead of a and 2 instead of b.

In this way it better matches g1 and g2.

I think we can leave the general docs at the module level. Readers should be able to make the jump between a=1 and b=2.

Comment thread package/MDAnalysis/analysis/rdf.py Outdated

.. versionadded:: 2.11.0

results.density_b : float

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

density_2

This attribute will be removed in 3.0.0.
Use :attr:`results.count` instead.

results.normalization_density : float

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use a=1 and b=2

s1 = u.select_atoms("name OW")
s2 = u.select_atoms("name HW1 HW2")
rdf = InterRDF(s1, s2, norm="density").run(**client_InterRDF)
assert not hasattr(rdf.results, "density")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

??? Is this an LLM remnant ???

kwargs = {"density": density} if density is not None else {}
rdf = InterRDF_s(u, sels, **kwargs).run(**client_InterRDF_s)
assert_almost_equal(max(rdf.results.rdf[0][0][0]), value)
assert not hasattr(rdf.results, "density")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

rm

assert not hasattr(rdf.results, "density")
assert hasattr(rdf.results, "box_volume")
assert hasattr(rdf.results, "normalization_density")
assert rdf.results.box_volume > 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

assert a value

s2 = u.select_atoms("name OD1 and resid 51")
ags = [[s1, s2]]
rdf = InterRDF_s(u, ags, norm="density").run(**client_InterRDF_s)
assert not hasattr(rdf.results, "density")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

rm

Comment thread package/CHANGELOG Outdated
Enhancements
* InterRDF and InterRDF_s now expose computed average box volume and densities
as results.box_volume, results.normalization_density, and (for InterRDF)
results.density_a and results.density_b when norm is "rdf" or "density"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

rename, see comments

@sumitsaraswat362

Copy link
Copy Markdown
Author

Thanks for the careful review, @orbeckst! I have addressed all feedback in the latest commits:

  1. Attribute & Doc Renaming:
    • Renamed density_a and density_b to density_1 and density_2 across InterRDF, docstrings, and CHANGELOG.
    • Updated docstring formulas to use indices 1 and 2 ($N_{\text{pairs}} = N_1 N_2 - N_{\text{excl}}$, $\rho_1 = N_1 / \langle V \rangle$, $\rho_2 = N_2 / \langle V \rangle$) to match g1 and g2.
  2. Cleaned up tests:
    • Good catch on the assert not hasattr(..., "density") checks — these were leftover artifacts from early drafts of the PR and have been completely removed.
    • In test_rdf.py, parametrized test_exposed_density over norm in ("rdf", "density") and separated norm="none" into its own test function.
    • Replaced assert box_volume > 0 with exact reference values: $10^6,\text{Å}^3$ for the $100,\text{Å}$ box in test_rdf.py, and the mean trajectory volume $\frac{1}{N}\sum V_i$ in test_rdf_s.py.
    • Verified formatting with black (line-length = 79).

This branch has not been deployed

No deployments
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.

Calculating the rdf for norm = "rdf" or "density" at the same time.

3 participants