From 0a47b8a6d750aacbb4726e3bbdf19ce0eb14739b Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Sat, 11 Jul 2026 16:33:45 +0300 Subject: [PATCH 01/23] docs: update development plan --- .gitignore | 3 + DEV_PLAN.md | 2745 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 2723 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index b7faf40..76cee96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# user-specific +atomref-development.code-workspace + # Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] diff --git a/DEV_PLAN.md b/DEV_PLAN.md index 94cdaac..aa29af4 100644 --- a/DEV_PLAN.md +++ b/DEV_PLAN.md @@ -1,33 +1,2728 @@ -# Development plan +# atomref 0.2.x development plan -## Current status (implemented in the `0.1.x` line) +> **Status:** third review draft +> **Plan lifecycle state:** `ACTIVE` +> **Primary target:** `atomref 0.2.0` — dataset architecture, neutral proatomic density, IAS analysis, and an executed feature notebook +> **Planned follow-up:** `atomref 0.2.1` — notebook/documentation infrastructure, complete API reference, and README positioning +> **Source data:** `atomref-proatoms 2.0.0`, dataset `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2` -- stable element metadata -- curated covalent, van der Waals, and atomic-radius support datasets -- explicit provenance and coverage metadata -- generic value-policy core plus radii and X–H convenience wrappers -- substitution and linear transfer -- custom element-indexed scalar sets -- policy-backed transfer sources -- nested-policy safeguards, transfer-depth tracking, and cycle detection -- provisional X–H support via `csd_legacy_xh_cno`, `XHPolicy`, and - `DEFAULT_XH_POLICY` +## 1. Purpose and lifecycle of this file -## Planned for `0.2.x` +This root-level `DEV_PLAN.md` is the authoritative implementation plan for the +`0.2.x` development cycle. It is written for a coding agent working in the +repository, but it should remain understandable to a human reviewer. -- broader X–H datasets and policies -- experimental plus computational support sets -- pairwise helper logic such as reference sums and normalization schemes -- restoration of incomplete experimental data from broader-support predictors +The plan has three scopes: -## Longer-term design ideas +1. **`0.2.0` binding implementation work** — generic packaged-dataset loading, + neutral radial density data, interpolation, IAS analysis, tests, and one + executed explanatory notebook. +2. **`0.2.1` planned documentation work** — direct rendering of saved executed + notebooks, richer notebook explanations, complete docstring-based API + reference, and a marketing-oriented README rewrite. +3. **Future reference only** — ions, additional properties, vectorized + evaluation, and three-dimensional grid-density generation. These items are + recorded to protect future compatibility but MUST NOT be implemented during + `0.2.0` or `0.2.1` unless the repository owner explicitly changes the scope. -- radial atomic reference functions -- simple proto-density support based on spherically averaged atomic data +Use this file to: -## Possible future directions +- understand the scientific and product contract before changing code; +- implement the work in the stated order; +- record completed stages and justified deviations; +- avoid adding abstractions, files, or process machinery that the project does + not need; +- preserve a clear distinction between current work and future ideas; +- verify that each release is complete. + +Do **not** copy this whole file into the public documentation. During `0.2.1`, +move only durable user-facing or architectural information into the existing +documentation. The complete execution plan should remain at the repository root +until the planned cycle is finished. + +The current duplicate page at `docs/dev/dev_plan.md` should be removed in +`0.2.1`, together with its MkDocs navigation entry. There should ultimately be +only one development plan. + +After `0.2.1` is released, this file may be: + +- replaced by the next small roadmap; +- shortened to unresolved future work; +- or removed after the changelog and release history preserve completed work. + +### 1.1 Plan authority, terminal states, and external review + +This file is the central control document for LLM-driven implementation of the +work described here. The implementation agent may update checkboxes, stage +results, deviations, and closeout records, but it MUST NOT silently extend the +scope or turn future-reference items into active work. + +The plan has four lifecycle states: + +```text +ACTIVE +COMPLETE_AWAITING_REVIEW +REPLAN_REQUIRED +CLOSED +``` + +Their meanings are: + +- **`ACTIVE`** — the accepted plan authorizes implementation of the current + stage. +- **`COMPLETE_AWAITING_REVIEW`** — the implementation agent believes every + binding requirement and acceptance criterion has been completed. It has + filled the closeout record and stopped further feature development pending an + independent review. +- **`REPLAN_REQUIRED`** — implementation cannot safely continue under the + current plan because of an unexpected scientific, architectural, data, + licensing, tooling, or repository constraint. The agent has documented the + blocker and stopped rather than inventing a replacement plan. +- **`CLOSED`** — an external reviewer has checked the implementation or the + blocker report and formally closed this plan as completed, superseded, or + abandoned. + +An **external reviewer** means the repository owner, another human reviewer, or +a separate planning/review agent that is not simply the implementation agent +continuing its own task without an independent review step. + +When all planned work appears complete, the implementation agent MUST: + +1. run every applicable release and acceptance check; +2. complete the closeout record in Section 20; +3. change the lifecycle state to `COMPLETE_AWAITING_REVIEW`; +4. state exactly what was implemented, tested, deferred, or intentionally + omitted; +5. stop adding functionality until an external reviewer approves or requests + corrections. + +When the plan becomes unexecutable, the implementation agent MUST: + +1. stop at the smallest safe repository state; +2. preserve or clearly identify all partial changes; +3. record what happened, why it blocks the plan, evidence gathered, approaches + attempted, and which decisions require replanning; +4. state whether the partial branch should be retained, reverted, or used as a + basis for a new plan; +5. change the lifecycle state to `REPLAN_REQUIRED`; +6. stop implementation until an external reviewer accepts a revised plan. + +After the state is `COMPLETE_AWAITING_REVIEW`, `REPLAN_REQUIRED`, or `CLOSED`, +an implementation agent asked to “continue development” MUST explain that the +current plan no longer authorizes additional coding. A new or revised plan must +be prepared and externally reviewed first. The agent MAY help draft that plan in +a separate planning task when asked, but MUST NOT resume implementation until +the new plan is accepted and marked `ACTIVE`. + +## 2. Normative language and implementation freedom + +The words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** express +the intended strength of a requirement. + +This plan separates decisions into three classes. + +### 2.1 Binding contract + +A binding contract defines scientific meaning, public behavior, provenance, +units, supported range, compatibility, or release acceptance. It MUST be +preserved unless the repository owner explicitly changes the plan. + +Examples: + +- neutral H–Lr coverage; +- the 20-bohr public radius limit; +- no density correlation or element substitution; +- no runtime dependency on `atomref-proatoms`; +- reproducible source verification; +- explicit units and failures outside the supported domain; +- exposing all relevant IAS minima rather than hiding them. + +### 2.2 Preferred first design + +A preferred design is the expected starting point for implementation. The agent +MAY replace it with a materially clearer, smaller, safer, or faster design when +all binding behavior and tests remain intact. + +Any deviation MUST be reported with: + +1. the original proposed design; +2. the observed problem; +3. the replacement; +4. why the replacement is better; +5. which tests protect the same contract. + +Examples: + +- exact class and function names; +- whether two closely related result classes are combined; +- precise internal CSV column names; +- exact local-minimum refinement algorithm; +- whether density and IAS implementation share one module or use two modules. + +### 2.3 Open decision + +An open decision is intentionally left for focused design work during the +relevant stage. It MUST be resolved explicitly before implementation of the +dependent behavior. + +The main open decision in this plan is the exact IAS local-minimum search and +candidate-merging algorithm. The required outputs and invariants are binding; +the numerical search mechanics are not yet final. + +## 3. Agent operating rules + +### 3.1 Keep the repository small + +Before adding any new file, abstraction, schema, public class, or tool, ask: + +> Considering the probable maximum future scope—ionic variants of some +> properties, a small number of additional atomic properties, and optional +> spatial sampling of radial densities—does the project genuinely need this +> object or file? + +Prefer: + +- one generic packaged-dataset dispatcher over separate unrelated loaders; +- the existing registry over a second metadata registry; +- one feature module over several one-purpose internal modules; +- one maintained notebook execution/checking tool over parallel exporters and + validators; +- direct notebook rendering over generated duplicate Markdown copies; +- existing release and validation tools extended in place; +- straightforward dataclasses, protocols, unions, and functions over a + framework. + +Do not add: + +- `AGENTS.md`; +- architecture-decision-record directories; +- a second development-plan copy; +- a plugin system; +- a general multidimensional scientific-data framework; +- a generic atomic-state selection engine; +- a new runtime dependency merely to simplify a small numerical routine; +- separate documentation pages when one existing page or one combined new page + is clearer; +- generated copies of notebooks when MkDocs can render the actual notebook. + +A maintainer-only script in `tools/` is justified when it makes a committed +scientific artifact reproducible or verifiable. Such a tool is not a user-facing +package feature and MUST NOT be presented as one. + +### 3.2 Protect the working scalar policy core + +The existing scalar registry metadata, policy, transfer, radii, and X–H behavior +is working and well tested. + +The implementation MUST NOT: + +- apply `ValuePolicy` to radial values; +- apply `SubstitutionTransfer` or `LinearTransfer` to radial profiles; +- change existing default radii or X–H results; +- break public `0.1.x` APIs; +- perform speculative cleanup outside the affected code. + +However, the packaged-dataset loader itself MUST be generalized so that +`get_builtin_set()` and the dataset-centered discovery machinery work for both +scalar and radial packaged sets. This is a focused architecture change required +by the new feature, not an unrelated policy rewrite. + +Policy code may receive small type-narrowing or helper changes so that it +explicitly requests scalar datasets from the now-generic loader. Its scientific +behavior must remain unchanged. + +### 3.3 Preserve project conventions + +- Runtime code remains pure Python with no required third-party dependencies. +- Optional notebook/documentation dependencies do not become runtime + dependencies. +- Runtime access performs no network requests. +- `README.md` remains generated from `docs/index.md`; do not edit the generated + README as the source of truth. +- Generated profile data MUST NOT be hand-edited. +- Public objects remain typed and compatible with the shipped `py.typed`. +- Package data MUST be validated in both wheel and source distribution. +- Existing commands and CI checks remain operational until deliberately + replaced by a smaller equivalent in `0.2.1`. +- New public names MUST be added deliberately to `atomref.__all__` and the + public-API test. +- New behavior MUST have tests before the stage is considered complete. +- Notebooks committed as release examples MUST contain saved execution counts + and outputs. + +### 3.4 Work stage by stage + +Complete, test, and report each stage before proceeding. + +At the end of each stage, provide: + +- a concise change summary; +- files added, removed, or substantially changed; +- commands run and their results; +- any deviation from this plan; +- unresolved questions; +- the suggested conventional commit message; +- the next stage. + +Do not combine unrelated stages into one unreviewable change. + +Do not begin the `0.2.1` documentation overhaul until the repository owner has +accepted the `0.2.0` functionality and release state. + +## 4. Product goal + +`atomref 0.2.0` should establish the package as a small source of **reference +atoms for structure algorithms**, not only a radii lookup package. + +The release should support two new workflows: + +```python +import atomref as ar + +rho = ar.get_proatomic_density("O", 0.75) +analysis = ar.estimate_ias_position("C", "O", 1.43) +``` + +The first evaluates a frozen neutral spherical proatomic density. + +The second evaluates the promolecular density along the vector between two +atoms and returns a transparent analysis of its minima, including a practical +averaged IAS estimate. + +The release is successful when a quantum-chemistry, crystallographic, or +molecular-software developer can immediately understand: + +- what the supplied profiles represent; +- where they came from; +- how to evaluate them; +- how the IAS approximation is constructed; +- what numerical domain is supported; +- what the result does and does not claim physically. + +## 5. Release scope + +### 5.1 `0.2.0` included work + +`0.2.0` includes: + +- a generic `get_builtin_set()` dispatcher that loads every packaged dataset + through the same dataset-centered machinery; +- one packaged neutral H–Lr spherical proatomic-density dataset; +- reproducible construction from the immutable `atomref-proatoms 2.0.0` + release; +- profile access by element symbol or atomic number through existing element + normalization; +- scalar radial-density evaluation; +- independent radius-coordinate and density-output unit selection; +- a strict public radius domain of `0 <= r <= 20 bohr`; +- positive-region log–log interpolation; +- approximate IAS analysis for two atoms separated by a supplied distance; +- discovery and inspection of all relevant local minima; +- global-minimum identification; +- a geometrically interpretable minimum-width measure; +- a dimensionless depth-weighted minima center; +- a weighted positional spread or equivalent ambiguity measure; +- explicit provenance, method, basis, units, limitations, and data licensing; +- one executed and saved notebook for proatomic density and IAS workflows, + including explanatory Markdown cells and plots; +- minimum accurate user/API documentation needed to release the functionality; +- tests, packaging checks, and release metadata. + +### 5.2 `0.2.1` planned work + +`0.2.1` is a documentation and discoverability release. It includes: + +- replacing transient notebook execution with reproducible execution that saves + and verifies notebook outputs; +- executing and saving all shipped notebooks; +- adding short explanatory Markdown before each logical code section; +- directly rendering `.ipynb` files in MkDocs, including plots and rich output; +- removing generated notebook Markdown copies and their exporter when direct + rendering is accepted; +- configuring `mkdocstrings` to show complete typed signatures and structured + Parameters, Returns, Raises, Attributes, and Examples sections; +- upgrading all public docstrings to a consistent structured style; +- reviewing API-page member selection so every intended public type and + parameter is documented; +- substantially improving the README/home page as a product introduction; +- removing the duplicate documentation copy of the development plan; +- simplifying notebook/documentation CI after the new workflow is stable. + +`0.2.1` MUST NOT change density or IAS numerical results except to fix a +confirmed defect with an explicit changelog entry and regression test. + +### 5.3 Excluded from `0.2.0` and `0.2.1` + +These releases do not include: + +- ionic profile selection; +- fractional-charge interpolation; +- atom typing or environment-dependent profiles; +- correlations between profile datasets; +- element substitution for missing profiles; +- reconstruction of one basis-set profile from another; +- user-defined radial profile datasets; +- vectorized NumPy density APIs; +- three-dimensional density-grid generation; +- periodic image summation; +- arbitrary interpolation modes; +- far-tail extrapolation; +- profile evaluation beyond 20 bohr; +- molecular electron densities; +- exact QTAIM or other rigorous molecular-density boundaries; +- an automatic Hirshfeld or Hirshfeld-I cycle; +- a generalized arbitrary-rank data registry; +- new required runtime dependencies. + +### 5.4 Versions + +The primary feature release is **`0.2.0`**, not `0.1.5`. + +It adds: + +- a new scientific data family; +- a new radial payload shape; +- generalized packaged-dataset dispatch; +- new numerical semantics; +- new public APIs; +- a derived pairwise analysis. + +The documentation and discoverability work is **`0.2.1`** because it follows +and documents a stabilized `0.2.0` API rather than forcing the scientific +implementation and the complete presentation overhaul into one review. + +A later grid-density or ion/state release should receive its own version after +separate design review. Do not assign that version in advance. + +## 6. Scientific data contract + +### 6.1 Upstream source + +The packaged profiles MUST be derived from: + +```text +project: + atomref-proatoms + +release: + 2.0.0 + +version-specific DOI: + 10.5281/zenodo.21291022 + +concept DOI: + 10.5281/zenodo.21291021 + +upstream dataset_id: + pbe0_sfx2c_dyallv4z_h-lr_spherical_v2 +``` + +Pinned source files: + +```text +profiles.csv +sha256: + b5520ab009542d52098dd6dbb920966d8d13377a4a5004f584a7bd15cd41c299 + +metadata.json +sha256: + 32c833ca69fa0f7eb9ed32841aafc638123ff872861e636156610e417fc4c514 + +basis_id: + dyall-v4z + +basis_sha256: + 0ee543855f8b1e7fbe9868d4abb844d8e8cc8b8c2694067b2b40de014bb4be94 +``` + +The snapshot builder MUST reject a source whose pinned hashes, dataset identity, data +version, basis identity, or basis checksum do not match. + +### 6.2 Scientific identity + +The consumer metadata MUST preserve at least: + +```text +profile data version: + 2.0.0 + +electronic method: + PBE0 + +SCF model: + self-consistent spherical fractional-occupation UKS + +relativity: + spin-free one-electron X2C + +basis: + dyall-v4z + +native radius unit: + bohr + +native density unit: + electron / bohr^3 + +charge scope: + neutral atoms only + +element coverage: + H through Lr, Z = 1..103 +``` + +The public documentation MUST explain that these are method-, basis-, state-, +and sphericalization-defined reference densities. They are not unique +basis-independent atomic observables. + +### 6.3 Neutral-state selection + +The snapshot builder MUST use upstream metadata, not column-name guessing alone, to +select profiles. + +Required selection: + +```text +charge == 0 +exactly one selected state for every Z from 1 through 103 +``` + +The snapshot builder MUST fail when: + +- an element is missing; +- more than one neutral state is selected for an element; +- a selected column is missing from the CSV; +- the selected metadata and column metadata disagree. + +The consumer data does not need to duplicate configuration, multiplicity, +occupation policy, state labels, SCF artifact paths, or the full upstream +metadata. + +The package metadata should state that detailed state and generation metadata +remain available in the exact `atomref-proatoms 2.0.0` archive. + +### 6.4 Radius truncation and bracketing + +The public validity domain is: + +```text +0 <= r <= 20 bohr +``` + +The upstream logarithmic grid does not contain exactly 20 bohr. The snapshot +SHOULD retain source rows through the first source point above 20 bohr so the +public endpoint remains bracketed by original data. + +For the pinned source, the expected facts are: + +```text +last source point below 20 bohr: + 19.865456344881434 bohr + +first source point above 20 bohr: + 20.1644204667093 bohr + +number of retained rows including the bracketing point: + 1127 +``` + +The point above 20 bohr is an internal interpolation bracket. It does not expand +the public validity domain. + +The snapshot validation MUST verify that all selected densities are finite and +strictly positive throughout the retained range. Thus the consumer package +never encounters the upstream far-tail zeros during valid use. + +### 6.5 Why the public limit is 20 bohr + +The 20-bohr boundary is a product decision, not a statement that the upstream +60-bohr data are invalid. + +The intended use is empirical or semi-empirical structure analysis, often on +experimental structures. At very remote distances: + +- absolute densities are extremely small; +- tail behavior is increasingly sensitive to computational details; +- small structural or numerical changes can dominate the value; +- the data no longer add useful information for the intended IAS helper. + +The limit also provides a simple numerical contract: + +- no tail extrapolation; +- no numerical underflow zeros in the packaged domain; +- no silent zero-fill behavior; +- no ambiguity about whether an out-of-range value is physical or artificial. + +### 6.6 Packaged representation + +The preferred representation is one compressed wide CSV in the existing package +data directory: + +```text +src/atomref/data/proatomic_density_neutral.csv.gz +``` + +Preferred logical structure: + +```text +r_bohr,z001,z002,...,z103 +``` + +or another equally simple deterministic Z-keyed header. + +Binding requirements: + +- one shared radius column; +- one density column per Z from 1 through 103; +- columns ordered by increasing Z; +- exact source decimal values retained where practical; +- deterministic row and line ordering; +- deterministic gzip output, including fixed gzip timestamp; +- no duplicated symbol columns; +- no ionic or configuration data; +- no second large metadata file. + +The element symbol mapping MUST come from the existing element registry. + +All interpretive metadata SHOULD live in the existing `registry.json`, with +license/citation text also added to `NOTICE.md` and public documentation. + +The agent MAY choose another standard-library-readable compressed representation +only when it is demonstrably simpler or safer and remains reproducibly +inspectable. Do not introduce a binary format merely for minor speed or size +improvements. + +### 6.7 Licensing and attribution + +The `atomref` code remains under its existing LGPL licensing. + +The imported `atomref-proatoms` data are CC BY 4.0. The release MUST: + +- identify the data license in registry metadata; +- update `NOTICE.md`; +- cite the concept and version-specific DOI; +- name the exact upstream dataset; +- state that the data are a neutral, truncated consumer snapshot; +- ensure the attribution is present in built distributions through packaged + metadata and/or configured license/notice files; +- test that the required attribution metadata is shipped. + +Do not silently present the data as covered only by the code license. + +## 7. Registry and architecture contract + +### 7.1 All packaged datasets use the same machinery + +Every packaged dataset MUST participate in the existing dataset-centered +operations: + +```python +list_quantities() +get_quantity_info(...) +list_dataset_ids(...) +list_dataset_infos(...) +get_dataset_info(...) +get_builtin_set(...) +``` + +`get_builtin_set(DatasetRef(...))` MUST load both existing scalar datasets and +the new radial dataset. + +A separate public loader that bypasses `get_builtin_set()` is not an acceptable +final architecture for `0.2.0`. + +The current scalar-only implementation is not a fundamental blocker. It +currently assumes: + +```text +element-domain + dense-by-Z CSV + one scalar column +``` + +The focused refactor should instead dispatch from explicit storage metadata. + +Preferred registry storage identifiers: + +```text +element_scalar_csv +element_radial_csv_gzip +``` + +or an equivalent pair of clear stable identifiers. + +The registry remains the single source of truth for: + +- quantity and set identity; +- aliases; +- coverage; +- scientific metadata; +- storage kind and filename; +- provenance and references; +- loading dispatch. + +### 7.2 Preferred loaded-set model + +Preferred public/internal types: + +```python +ElementScalarSet +ElementRadialSet +BuiltinSet = ElementScalarSet | ElementRadialSet +``` + +`ElementRadialSet` may be named `ProatomicDensitySet` if a truly generic radial +type would add abstraction without present value. The important contract is +that the object returned by `get_builtin_set()` is the actual loaded packaged +set and carries: + +- `ref`; +- `info`; +- element coverage; +- access to an immutable profile by symbol/Z; +- shared grid and profile data; +- no policy behavior. + +The exact union/type-alias placement is a preferred design. + +### 7.3 Scalar policy type narrowing + +The scalar policy core must continue to accept scalar datasets only. + +The current internal names: + +```python +DatasetLike +resolve_dataset_like(...) +``` + +are scalar in behavior despite generic names. During the focused refactor, +prefer clearer names such as: + +```python +ScalarDatasetLike +resolve_scalar_dataset_like(...) +``` + +These are internal and can be renamed without public breakage. + +Alternative: retain the old internal name but add an explicit type guard that +raises a clear `DatasetError` when a radial set is supplied. The clearer rename +is preferred. + +`get_radii_set()` and `get_xh_set()` should continue to return +`ElementScalarSet` and must validate/narrow the generic `get_builtin_set()` +result. + +The policy engine, transfer fitting, fallback, and placeholder behavior must not +be generalized to functions. + +### 7.4 Failure contingency + +If implementation reveals a genuine blocker to generic `get_builtin_set()` +dispatch: + +1. stop before adding a bypass as permanent architecture; +2. document the concrete blocker and affected call graph; +3. implement the smallest reviewed temporary adapter only with owner approval; +4. add a named follow-up item to `0.2.1` or the next patch; +5. protect the temporary behavior with tests; +6. do not claim that dataset machinery is unified until it actually is. + +The expected outcome is still to complete the generic dispatcher in `0.2.0`. + +### 7.5 Minimal implementation structure + +Preferred new files for `0.2.0`: + +```text +src/atomref/proatoms.py +src/atomref/data/proatomic_density_neutral.csv.gz +tools/build_proatomic_density_snapshot.py +tests/proatoms/test_dataset.py +tests/proatoms/test_density.py +tests/proatoms/test_ias.py +notebooks/04-proatomic-density-and-ias.ipynb +``` + +The exact test split is flexible. + +The generic radial set and storage dispatch may live in `registry.py`; density +evaluation and IAS analysis should initially live in `proatoms.py`. + +Do not add a separate: + +- `_density_data.py`; +- `ias.py`; +- profile-manifest JSON; +- schema directory; +- numerical-contract document; +- data-specific documentation directory; + +unless actual implementation evidence shows that the combined design is +unmanageable. + +### 7.6 Existing metadata classes + +`CoverageInfo` should be generalized in wording from “element-indexed scalar +dataset” to “element-indexed dataset.” Its fields are sufficient for H–Lr +coverage. + +`DatasetInfo.storage` is already a generic metadata mapping and should carry: + +- storage kind; +- filename; +- radius column; +- density-column convention; +- native coordinate unit; +- native density unit; +- public maximum radius; +- retained bracketing radius; +- interpolation contract identifier; +- source project, release, dataset ID, hashes, and license. + +Avoid adding fields to public metadata dataclasses unless the information cannot +be represented clearly in the existing structure. + +### 7.7 Caching + +`get_builtin_set()` is currently cached by `DatasetRef`. Preserve equivalent +caching for radial sets. + +The generic dispatcher may delegate to format-specific cached loaders, but +repeated calls for the same canonical dataset should return the same immutable +loaded-set object or equivalent shared cached data. + +## 8. Density API contract + +### 8.1 Public concept + +The API should use “proatomic density,” not the ambiguous abbreviation `ED`. + +Preferred public names: + +```python +DEFAULT_PROATOMIC_DENSITY_SET +ProatomicDensitySet +ProatomicDensityProfile + +list_proatomic_density_sets() +list_proatomic_density_set_infos() +get_proatomic_density_set() +get_proatomic_density_set_info() +get_proatomic_density_profile() +get_proatomic_density() +``` + +This list is a preferred first design. It MAY be reduced when an existing generic +registry helper already provides the same operation clearly. + +At minimum, the release MUST provide: + +```python +get_proatomic_density_profile(...) +get_proatomic_density(...) +``` + +and a clear way to inspect the exact dataset metadata. + +### 8.2 Preferred usage + +```python +import atomref as ar + +rho_o = ar.get_proatomic_density( + "O", + 0.75, + radius_unit="angstrom", + density_unit="electron/bohr^3", +) + +profile = ar.get_proatomic_density_profile("O") +rho_o_bohr = profile( + 1.5, + radius_unit="bohr", + density_unit="electron/bohr^3", +) +``` + +The profile object SHOULD be callable or expose one obvious evaluation method. + +### 8.3 Element handling + +The API MUST follow existing element behavior: + +- ordinary symbols are canonicalized; +- `D` and `T` use the hydrogen electronic profile; +- invalid symbols return `None`; +- elements beyond Lr return `None`; +- no neighboring-element or correlated fallback occurs. + +The canonical dataset map is keyed internally by atomic number. + +### 8.4 Scalar scope + +The initial public API evaluates one scalar radius at a time. + +It MUST NOT require NumPy and SHOULD NOT accept arbitrary iterables in `0.2.0`. + +Repeated evaluation should be supported efficiently through the cached profile +object. + +A future vectorized API may be added separately if real downstream profiling +shows it is useful. + +### 8.5 Units + +Radius coordinates and density values use independent unit parameters. + +Canonical radius units: + +```text +angstrom +bohr +``` + +Canonical density units: + +```text +electron/bohr^3 +electron/angstrom^3 +``` + +Preferred defaults: + +```text +radius_unit = "angstrom" +density_unit = "electron/bohr^3" +``` + +This reflects common structural input conventions while preserving the familiar +atomic-density scale used for values such as approximately `0.003` or `0.001` +electron/bohr³. + +Preferred density call: + +```python +rho = get_proatomic_density( + "O", + 0.75, + radius_unit="angstrom", + density_unit="electron/bohr^3", +) +``` + +The profile object should use the same independent arguments. + +Short aliases such as `e/bohr^3` MAY be accepted, but documentation and result +metadata should emit one canonical form. + +The implementation MUST use named, tested conversion constants. Do not scatter +conversion literals through the code. + +Changing `density_unit` must change only reported density values. It must not +change: + +- selected interpolation interval; +- radius geometry; +- local-minimum positions; +- global-minimum identity; +- dimensionless weights; +- weighted IAS position or spread. + +### 8.6 Error and missing-data behavior + +Binding behavior: + +| Situation | Result | +|---|---| +| Invalid or unsupported element | `None` | +| Missing packaged dataset | `DatasetError` | +| Unknown radius or density unit | `ValueError` | +| Negative radius | `ValueError` | +| Non-finite radius | `ValueError` | +| Radius above 20 bohr after unit conversion | `ValueError` | +| Valid radius and supported element | finite positive `float` | + +This follows the package distinction between: + +- absent scientific data: return `None`; +- malformed or out-of-contract request: raise. + +### 8.7 Lazy loading and caching + +The compressed data MUST load lazily. + +Repeated profile retrieval or evaluation MUST NOT repeatedly parse the file. + +The implementation SHOULD cache: + +- the loaded shared radius grid; +- the Z-indexed profile table or immutable profile views; +- dataset metadata resolution. + +The public API MUST not expose mutable references to cached shared data. + +## 9. Interpolation contract + +### 9.1 Supported numerical region + +The packaged retained interval contains strictly positive density values for all +103 profiles. + +Therefore, valid public interpolation does not need a zero-density branch. + +If a future imported snapshot contains a nonpositive value within the retained +bracketing region, import or load validation MUST fail rather than silently +changing interpolation behavior. + +### 9.2 Contract identifier + +Registry metadata and result provenance SHOULD use a stable identifier such as: + +```text +loglog_positive_bracketed_v1 +``` + +The exact string may change before implementation, but it must be stable within +the release and documented. + +### 9.3 Evaluation rules + +For one profile with source knots `(r_i, rho_i)`: + +1. Reject non-finite and negative query radii. +2. For `0 <= r <= r_min`, return `rho(r_min)`. +3. At an exact stored radius, return the exact stored density. +4. Between two positive neighboring knots, interpolate linearly in + `log(r)` and `log(rho)`. +5. Allow evaluation at exactly 20 bohr using the retained bracketing point. +6. Reject values above 20 bohr. +7. Perform no tail extrapolation and no zero fill. +8. Return a finite positive scalar in the selected output units. + +The origin rule is a finite-grid convention. Documentation must not describe it +as an exact nuclear-density evaluation. + +### 9.4 Implementation preference + +Use only the standard library, for example: + +- `bisect` for interval location; +- `math.log` and `math.exp`; +- immutable tuples or similarly simple storage. + +The implementation SHOULD avoid rebuilding logarithm arrays on every call. +Precomputed log-radius and, if useful, log-density values may be cached. + +### 9.5 Relationship to the upstream interpolation issue + +An issue should be opened in `atomref-proatoms` to replace its global +linear-fallback behavior with interval-local handling for profiles that contain +far-tail zeros. + +`atomref 0.2.0` does not need to wait for an upstream release because: + +- it imports pinned v2.0.0 values; +- it restricts its domain to 20 bohr; +- all retained values are strictly positive; +- it implements its own small dependency-free consumer interpolation. + +The two projects should still document compatible positive-positive log–log +behavior. + +## 10. IAS scientific contract + +### 10.1 Objective + +For atoms A and B separated by distance `R`, define `x` from A toward B: + +```text +A ---- x ----> B +0 R +``` + +The promolecular line density is: + +\[ +f(x; R) = \rho_A(x) + \rho_B(R - x) +\] + +for: + +\[ +0 \le x \le R +\] + +The IAS helper analyzes minima of this one-dimensional function. + +### 10.2 Interpretation + +The result is an **approximate neutral-proatom IAS coordinate**. + +It is not: + +- a QTAIM zero-flux surface; +- a molecular-density topological critical point; +- an environment-relaxed atomic boundary; +- an ionic, charged, or self-consistent stockholder boundary; +- evidence that the underlying isolated-atom partition is unique. + +The wording “approximate IAS position” or “promolecular density minimum along +the atom–atom vector” should be used consistently. + +### 10.3 Input domain and units + +Preferred public function: + +```python +estimate_ias_position( + atom_a, + atom_b, + distance, + *, + distance_unit="angstrom", + density_unit="electron/bohr^3", + set_id=DEFAULT_PROATOMIC_DENSITY_SET, + ... +) +``` + +Binding input rules: + +- both elements must have profiles; +- distance must be finite and strictly positive; +- distance must not exceed 20 bohr after conversion; +- the result coordinate is measured from atom A toward atom B; +- all reported positions and widths use `distance_unit`; +- component and summed densities use `density_unit`; +- D/T follow hydrogen behavior; +- missing element profiles return `None`; +- invalid distance or unit raises `ValueError`. + +Distance and density units are independent. Converting the density unit must not +change any reported position, tie, width, weight, spread, or status. + +### 10.4 Required result information + +The public result MUST expose: + +1. all distinct interior local minima found under the documented numerical + rules; +2. the density value and position of each minimum; +3. the global minimum or all globally tied minima; +4. a width measure for the global minimum basin, or for each tied global basin; +5. a depth-weighted center of the local-minimum positions; +6. a weighted positional spread or equivalent ambiguity measure; +7. source dataset and interpolation provenance; +8. enough input information to interpret the coordinate orientation; +9. whether a boundary candidate invalidates or weakens the ordinary interior + IAS interpretation. + +Do not return only one unexplained float. + +### 10.5 Preferred result types + +Preferred first design: + +```python +@dataclass(frozen=True, slots=True) +class IASMinimum: + position_from_a: float + position_from_b: float + fraction_from_a: float + rho_a: float + rho_b: float + rho_sum: float + width: float | None + width_left: float | None + width_right: float | None + width_is_clipped: bool + + +@dataclass(frozen=True, slots=True) +class IASResult: + atom_a: str + atom_b: str + distance: float + distance_unit: str + density_unit: str + local_minima: tuple[IASMinimum, ...] + global_minima: tuple[IASMinimum, ...] + weighted_position_from_a: float | None + weighted_fraction_from_a: float | None + weighted_spread: float | None + weighting_power: float + width_fraction: float + status: str + dataset_id: str + interpolation_contract: str +``` + +The exact field list is preferred, not binding. It may be simplified if all +required information remains explicit and typed. + +Useful convenience behavior MAY include: + +```python +result.weighted_position_from_b +result.has_unique_global_minimum +result.unique_global_minimum +``` + +Do not create a synthetic singular “global minimum” when multiple minima are +equal within the declared tolerance without preserving the tie. + +### 10.6 Local minima + +The implementation MUST deliberately search for multiple minima. + +It MUST NOT assume that the complete objective is globally unimodal. + +Possible sources of multiple candidates include: + +- shell structure; +- slope changes at profile interpolation knots; +- symmetric pairs; +- very short separations; +- shallow numerical splitting of one broad basin. + +All reported minima must be sorted by position from A. + +Adjacent numerical duplicates must be merged under documented position and +density tolerances. + +The exact candidate search and merge procedure is an open Stage 4 decision. + +### 10.7 Global minima and ties + +Let: + +\[ +f_{\min} = \min_i f_i +\] + +A candidate is globally tied when its value agrees with `f_min` under documented +relative and absolute tolerances. + +The result MUST retain every tied global candidate. + +For a homonuclear pair: + +- the complete objective is symmetric around `R/2`; +- the candidate set should be symmetric within tolerance; +- the weighted center should be `R/2` within numerical tolerance; +- the implementation must not discard symmetric off-center minima merely to + force a unique midpoint global minimum. + +### 10.8 Width measure + +The preferred minimum-width definition is the connected interval around a +minimum for which: + +\[ +f(x) \le (1 + \epsilon) f_i +\] + +where: + +- `f_i` is the density at that minimum; +- `epsilon` is a documented dimensionless rise fraction; +- preferred default `epsilon = 0.05`. + +The width is: + +```text +x_right - x_left +``` + +This gives a directly interpretable distance: + +- small width: narrow minimum; +- large width: broad minimum. + +The result should record: + +- the width; +- left and right crossing positions; +- the rise fraction; +- whether a crossing was clipped by a neighboring basin or domain boundary. + +This definition is preferred because it: + +- is unit invariant in density; +- does not require numerical second derivatives; +- remains meaningful at interpolation knots; +- is easy to explain to users. + +The agent MAY propose a better robust geometric measure during Stage 4, but must +justify it before implementation. + +### 10.9 Depth-weighted minima center + +The averaged coordinate must use dimensionless weights. + +Preferred weighting: + +\[ +w_i = \left(\frac{f_{\min}}{f_i}\right)^p +\] + +with preferred default: + +\[ +p = 1 +\] + +The weighted position is: + +\[ +x_{\mathrm{weighted}} += +\frac{\sum_i w_i x_i}{\sum_i w_i} +\] + +The weighted positional spread is: + +\[ +\sigma_x += +\sqrt{ +\frac{\sum_i w_i(x_i-x_{\mathrm{weighted}})^2} +{\sum_i w_i} +} +\] + +Properties: + +- the deepest minimum has weight 1; +- shallower minima have weights below 1; +- equal minima receive equal weights; +- converting density units does not change the result; +- a symmetric homonuclear candidate set averages to the midpoint; +- large spread indicates separated competing minima. + +Do not exponentiate a raw dimensional density difference such as +`exp(-(rho_i-rho_min))`. Its numerical result changes with density units unless +an additional scale is defined. + +The preferred weighting formula is a Stage 4 review point. Another +dimensionless, monotone depth weighting MAY replace it with explicit owner +approval or a strong documented reason. + +### 10.10 Boundary behavior + +The objective must also be evaluated at `x=0` and `x=R`. + +A global boundary minimum is not an ordinary interior IAS intersection. + +The result MUST make this visible, for example through statuses such as: + +```text +ok +multiple_global_minima +boundary_dominated +no_interior_minimum +``` + +The exact vocabulary may change. + +When interior minima exist but a boundary value is lower, the result may still +report the interior minima and their weighted center as diagnostics, but it must +not label the result unconditionally valid. + +### 10.11 Pair-reversal invariance + +For a result on `(A, B, R)` and the reversed result `(B, A, R)`: + +- every position `x` must correspond to `R - x`; +- objective density values must agree within tolerance; +- global-tie counts must agree; +- widths must agree; +- weighted position must transform to `R - x_weighted`; +- weighted spread must remain unchanged; +- status must remain equivalent. + +This is a binding acceptance invariant. + +## 11. IAS numerical implementation decision gate + +Before implementing the search, Stage 4 MUST add a short design note inside the +module docstring, tests, or this plan resolving: + +1. how the candidate mesh or interval partition is constructed; +2. how local minima are detected; +3. how candidate positions are refined; +4. how minima on interpolation knots are handled; +5. how adjacent numerical duplicates are merged; +6. how global ties are defined; +7. how width crossings are located; +8. which default tolerances are used; +9. what performance is expected for one pair and repeated pairs. + +### 11.1 Preferred first approach + +A reasonable first approach is: + +1. Construct a deterministic candidate grid from relevant A profile knots and + transformed B profile knots `R - r_j` within `[0, R]`. +2. Add interval midpoints or another minimal set of probes needed to avoid + missing an interior basin. +3. Evaluate the objective on this grid. +4. Identify candidate minima from neighboring values. +5. Refine only candidate neighborhoods with a small dependency-free bounded + minimizer such as golden-section search. +6. Evaluate exact knot candidates explicitly. +7. Merge candidates that represent the same basin. +8. Evaluate boundary values separately. +9. Compute ties, widths, weighted center, spread, and status. + +This approach is preferred because it uses the actual interpolation structure +rather than an arbitrary fixed global step. + +However, the repository owner expects to discuss or propose an alternative IAS +algorithm. Therefore this section is not binding implementation detail. + +### 11.2 Performance guardrail + +Correctness comes first, but the implementation is intended for structure +algorithms and must not be unnecessarily expensive. + +Add a lightweight benchmark or timing-oriented test outside fragile CI +thresholds. Record approximate local timings for: + +- first call including data load; +- cached density evaluation; +- one IAS pair; +- a modest batch of representative IAS pairs. + +Do not add a benchmarking dependency. + +If the preferred knot-based approach is too slow, optimize the implementation +without changing public semantics. Possible optimizations include: + +- cached logarithmic arrays; +- index-aware interval evaluation; +- reduced candidate probing; +- precomputed per-profile segment coefficients; +- a coarse-to-fine search validated against a slower reference. + +Do not optimize by assuming one global minimum. + +## 12. Documentation and notebook strategy + +### 12.1 Split feature documentation from the later overhaul + +`0.2.0` must contain enough documentation to use the new API safely: + +- accurate public docstrings for new objects; +- one concise density/IAS guide or equivalent section; +- exact provenance, units, range, and limitations; +- one executed feature notebook with plots; +- a changelog entry and working README mention. + +The broad documentation redesign belongs to `0.2.1` after the API is accepted. + +Do not move the README marketing rewrite, complete existing-notebook rewrite, +or all-module docstring conversion into the critical path of `0.2.0`. + +### 12.2 `0.2.0` feature notebook is mandatory + +Add: + +```text +notebooks/04-proatomic-density-and-ias.ipynb +``` + +The committed notebook MUST: + +- be executed; +- contain saved execution counts; +- contain saved text and plot outputs; +- execute from top to bottom in a clean environment; +- use short Markdown explanations before every logical code section; +- explain what the reader should notice rather than merely restating the code; +- avoid hidden state and manual edits to outputs; +- use deterministic examples where practical. + +Required content: + +1. package and dataset discovery through `get_builtin_set()` and registry + metadata; +2. profile retrieval and scalar evaluation with independent radius/density + units; +3. one or two radial-density plots, preferably with a logarithmic density axis; +4. one ordinary heteronuclear IAS example; +5. a plot of `rho_A(x)`, `rho_B(R-x)`, and their sum; +6. markers for local minima, globally tied minima, and the weighted position; +7. interpretation of minimum width and weighted spread; +8. at least one example that demonstrates why multiple minima are exposed; +9. a concise limitations section. + +Matplotlib may be an optional notebook/documentation dependency. It must not +become a runtime dependency. + +For `0.2.0`, the notebook may be linked from the docs/GitHub even if the current +Markdown exporter cannot faithfully embed plots. The saved `.ipynb` itself is +the source artifact. + +### 12.3 Current notebook behavior to replace in `0.2.1` + +The current repository tools: + +- execute code cells in a plain Python namespace; +- suppress or separately capture stdout; +- do not write execution counts or outputs into `.ipynb`; +- do not verify that committed outputs are current; +- export duplicate Markdown pages; +- do not preserve rich Jupyter output such as Matplotlib figures. + +They do not “unexecute” notebooks; they simply ignore the stored execution +state and run code transiently. + +`0.2.1` should replace this with actual Jupyter-kernel execution. + +Preferred compact workflow: + +1. one maintainer tool, preferably the existing `tools/check_notebooks.py`, + gains: + - `--write` to execute notebooks and save normalized outputs; + - `--check` to execute into temporary files and compare against committed + normalized notebooks; +2. use `nbclient`/`nbconvert` and `nbformat`, not `exec()` of extracted code + strings; +3. normalize nondeterministic execution metadata before comparison; +4. fail on cell errors; +5. keep outputs committed; +6. CI verifies that outputs are fresh. + +Avoid adding separate “execute,” “export,” and “check” tools when one tool with +two modes is sufficient. + +### 12.4 Direct notebook rendering in `0.2.1` + +Preferred documentation approach: + +- render actual `.ipynb` files directly with `mkdocs-jupyter`; +- use committed saved outputs; +- set documentation-build execution off, because freshness is checked + separately; +- include notebook source/download links; +- remove `docs/notebooks/*.md`; +- remove `tools/export_notebooks.py`; +- remove export-sync tests and replace them with notebook execution-state tests. + +This reduces duplicated files and allows plots and rich outputs to appear +naturally. + +A different direct-rendering solution may be used if it is materially better, +but do not keep both direct rendering and generated Markdown copies. + +### 12.5 Explanatory notebook writing standard + +Every shipped notebook should contain: + +- a title and a concise goal; +- prerequisites or expected knowledge; +- Markdown before each meaningful code section; +- explanation of important output after or before the cell; +- section headings that form a useful table of contents; +- a final “what this demonstrated” and limitations section. + +Not every one-line import needs its own essay. The standard is that a reader +should understand why each logical block exists and what to inspect in its +output. + +### 12.6 Full API reference with MkDocs + +Do not switch away from MkDocs solely to obtain Sphinx-like API pages. + +The existing `mkdocstrings[python]` stack can render: + +- complete function and method signatures; +- type annotations; +- parameter names, types, descriptions, and defaults; +- return types and descriptions; +- raised exceptions; +- class attributes; +- examples and cross-references. + +The `0.2.1` task is to configure and feed it correctly. + +Preferred `mkdocs.yml` options include: + +```yaml +plugins: + - mkdocstrings: + handlers: + python: + paths: [src] + options: + docstring_style: google + show_root_heading: true + show_source: false + show_signature: true + separate_signature: true + show_signature_annotations: true + signature_crossrefs: true + annotations_path: brief + show_docstring_parameters: true + show_docstring_returns: true + show_docstring_raises: true + show_docstring_attributes: true + show_docstring_examples: true +``` + +Exact presentation options may be tuned after inspecting the rendered site. + +All intended public functions, classes, methods, and dataclass attributes should +receive structured Google-style docstrings with, as applicable: + +```text +Args: +Returns: +Raises: +Attributes: +Examples: +Notes: +``` + +Type annotations remain the source of type information; docstrings explain +meaning, units, behavior, and constraints. + +### 12.7 README/home-page redesign in `0.2.1` + +`docs/index.md` remains the source of `README.md`. + +The home page should immediately answer: + +1. What is `atomref`? +2. Which scientific/software problems does it solve? +3. Why use it instead of embedding constants or ad hoc tables? +4. What can a user do in the first five lines of Python? +5. Which data and provenance guarantees are provided? + +The opening should communicate an identity close to: + +> `atomref` provides cited atomic values and frozen spherical free-atom +> electron densities through a small Python API for crystallographic, +> quantum-chemical, and molecular-structure algorithms. + +Show working radii, profile, and IAS examples near the top. Move detailed policy +terminology and nested-transfer mechanics lower. + +Avoid leading with negative positioning such as “not another periodic-table +encyclopedia.” + +### 12.8 Keep the durable docs compact + +The preferred durable structure after `0.2.1` is still small: + +```text +docs/index.md +docs/guide/proatomic_density.md +docs/dev/architecture.md +docs/api/*.md +notebooks/*.ipynb +``` + +Remove the duplicated public development-plan page. Do not create separate pages +for interpolation, IAS mathematics, provenance, licensing, and every result +field unless one combined guide becomes genuinely difficult to use. + +## 13. Staged implementation plan + +## `0.2.0` implementation + +### Stage 0 — Baseline verification + +#### Goal + +Confirm the supplied repository is healthy before modifying it. + +#### Actions + +Inspect at minimum: + +- `src/atomref/registry.py`; +- `src/atomref/policy.py`; +- `src/atomref/__init__.py`; +- `src/atomref/data/registry.json`; +- package-data configuration; +- registry, public-API, package-data, and release tests; +- existing notebooks and notebook tools; +- `mkdocs.yml`. + +Record: + +- current version; +- baseline tests; +- current public API; +- current built-in dataset return type; +- current notebook execution/output state. + +Run: + +```bash +python -m pytest -q +python tools/check_registry.py +python tools/check_notebooks.py +python tools/export_notebooks.py --check +python tools/gen_readme.py --check +``` + +When dependencies are available: + +```bash +flake8 src tests tools +mkdocs build --strict +``` + +#### Constraints + +- Do not change code in Stage 0. +- Report baseline failures before attributing later failures to new work. + +#### Completion criteria + +- baseline recorded; +- relevant call graph understood; +- no unexplained existing failures. + +#### Suggested commit + +No commit. + +--- + +### Stage 1 — Generalize packaged-dataset loading + +#### Goal + +Make `get_builtin_set()` the unified entry point for scalar and radial packaged +datasets before adding the new data. + +#### Required changes + +1. Add an explicit storage-kind discriminator to existing registry entries. +2. Add the radial storage kind to registry validation. +3. Introduce the minimal loaded radial-set type. +4. Change `get_builtin_set()` to dispatch by storage kind. +5. Preserve caching and alias resolution. +6. Narrow scalar policy and convenience code explicitly to + `ElementScalarSet`. +7. Update type annotations, public exports, tests, and architecture comments. +8. Generalize `CoverageInfo` wording. + +#### Required tests + +- every existing scalar dataset still loads through `get_builtin_set()`; +- the returned scalar type remains `ElementScalarSet`; +- aliases still resolve before caching/loading; +- an unknown storage kind raises `DatasetError`; +- scalar policy code rejects a radial test fixture clearly; +- generic discovery/metadata functions do not care about payload kind; +- a small synthetic radial fixture can be dispatched and queried; +- existing scalar policy results are unchanged. + +#### Do not do + +- do not add radial policy behavior; +- do not generalize transfer fitting; +- do not build a registry framework beyond the two actual storage kinds; +- do not bypass `get_builtin_set()` from the later proatomic API. + +#### Completion criteria + +- generic built-in dataset dispatch is working; +- scalar behavior is unchanged; +- radial sets can participate in the same discovery and loading path. + +#### Suggested commit + +```text +refactor: generalize built-in dataset loading +``` + +--- + +### Stage 2 — Reproducible neutral profile snapshot + +#### Goal + +Add the exact minimal neutral H–Lr data product without runtime dependence on +the generator project. + +#### Required changes + +1. Add `tools/build_proatomic_density_snapshot.py`. +2. Add one compressed package-data file. +3. Add the `proatomic_density` quantity and dataset metadata to `registry.json`. +4. Update `NOTICE.md` with CC BY attribution and exact DOIs. +5. Update package build inclusion for `.gz`. +6. Extend registry, package-data, and distribution checks. +7. Add focused source/data-integrity tests. +8. Document the maintainer tool briefly in `tools/README.md`. + +#### Why the builder belongs in `tools/` + +This script is not for normal package users. It is a maintainer tool that makes +the committed scientific snapshot auditable and reproducible. + +Without it, future maintainers and agents would have to trust an opaque +hand-edited compressed table and manually repeat: + +- hash verification; +- neutral-state selection; +- H–Lr coverage checks; +- 20-bohr truncation/bracketing; +- column ordering; +- deterministic compression. + +The script should therefore be named as a snapshot builder, not as a public +“import” feature. + +#### Snapshot-builder requirements + +The builder MUST: + +- accept a local upstream dataset directory or explicit CSV/metadata paths; +- perform no network access; +- verify source hashes and identities; +- select exactly one neutral profile per Z=1..103 from metadata; +- preserve source radius and density decimal values; +- retain rows through the first source point above 20 bohr; +- verify expected retained row count and bracketing values; +- verify finite, strictly increasing positive radii; +- verify finite, strictly positive densities in the retained table; +- verify every profile is monotonically non-increasing within a documented + numerical tolerance; +- write columns in increasing Z order; +- create deterministic gzip output; +- support a check mode and a write mode; +- print a concise source/output summary. + +Preferred commands: + +```bash +python tools/build_proatomic_density_snapshot.py \ + --source-dir ../atomref-proatoms/data/profiles/pbe0_sfx2c_dyallv4z_h-lr_spherical_v2 \ + --write + +python tools/build_proatomic_density_snapshot.py \ + --source-dir ../atomref-proatoms/data/profiles/pbe0_sfx2c_dyallv4z_h-lr_spherical_v2 \ + --check +``` + +The CLI may be simplified. + +#### Registry requirements + +Add one quantity: + +```text +proatomic_density +``` + +Add one atomref-side set ID clearly identifying: + +- neutral scope; +- H–Lr coverage; +- PBE0/sf-X2C/dyall-v4z; +- upstream v2 lineage. + +The exact ID remains a review point. + +The new dataset MUST load through: + +```python +get_builtin_set(DatasetRef("proatomic_density", set_id)) +``` + +and return the radial loaded-set type. + +#### Data tests + +Tests MUST verify: + +- the compressed file is packaged; +- exactly 103 profile columns exist; +- columns map to Z=1..103 without gaps; +- row count is 1127; +- radii are strictly increasing; +- the retained bracketing point is above 20 bohr; +- all densities are finite and positive; +- all profiles are monotone non-increasing within tolerance; +- source and license metadata are present; +- generic `get_builtin_set()` loads the radial set; +- wheel and sdist contain the data and attribution metadata. + +Source-dependent byte regeneration may be a maintainer check when the upstream +archive is not present in CI. CI must validate the committed snapshot +internally. + +#### Do not do + +- do not package all 501 states; +- do not package the full upstream metadata; +- do not package configurations or multiplicities; +- do not add the upstream generator as a dependency; +- do not download data at runtime; +- do not add a second registry. + +#### Completion criteria + +- reproducible neutral snapshot committed; +- generic dataset loader returns it; +- provenance and licensing recorded; +- distribution tests pass. + +#### Suggested commit + +```text +feat(data): add neutral proatomic density snapshot +``` + +--- + +### Stage 3 — Proatomic density evaluation + +#### Goal + +Expose a small typed dependency-free profile API with stable interpolation and +independent units. + +#### Required changes + +1. Add `src/atomref/proatoms.py`. +2. Add immutable profile access/evaluation objects or an equally clear API. +3. Use the radial object returned by `get_builtin_set()`. +4. Implement radius and density unit conversion independently. +5. Implement the interpolation contract. +6. Add lazy/shared cached numerical data as needed. +7. Add public exports and public-API tests. +8. Add density tests. +9. Add minimum accurate API docstrings and feature documentation. + +#### Density acceptance tests + +##### Dataset and profile access + +- H, C, O, Fe, La, U, and Lr profiles load. +- Rf and Og return `None`. +- D and T produce the H profile. +- invalid symbols return `None`. +- aliases/set IDs resolve through registry conventions. +- repeated calls reuse cached set/profile data. +- the public profile/set objects cannot mutate shared package data. + +##### Radius behavior + +- `r=0` equals the first stored value. +- `r=r_min` equals the exact stored value. +- selected interior knots reproduce exact source values. +- `r=20 bohr` evaluates successfully. +- values above 20 bohr raise. +- negative, `NaN`, and infinite radii raise. +- unknown radius units raise. + +##### Interpolation + +- a synthetic power-law profile is reproduced by log–log interpolation; +- interpolation is continuous at stored knots within tolerance; +- interpolated densities remain positive; +- representative profiles remain non-increasing; +- no linear fallback or zero-fill path exists in the supported domain. + +##### Units + +- equivalent bohr/ångström coordinates evaluate the same physical point; +- electron/bohr³ and electron/ų outputs convert correctly; +- default radius unit is ångström; +- default density unit is electron/bohr³; +- changing density unit does not change interval selection or provenance; +- unknown density units raise. + +##### Compatibility + +- all existing scalar tests pass unchanged; +- `get_radii_set()` and `get_xh_set()` retain scalar return types; +- policy code rejects radial sets explicitly; +- all packaged sets remain accessible through `get_builtin_set()`. + +#### Completion criteria + +- density evaluation works for neutral H–Lr; +- unit, range, and interpolation behavior is tested; +- API is typed and documented; +- runtime remains dependency-free. + +#### Suggested commit + +```text +feat: add proatomic density evaluation +``` + +--- + +### Stage 4 — IAS minima analysis + +#### Goal + +Implement pairwise promolecular line-density analysis and return raw minima plus +a useful averaged IAS estimate. + +#### Stage 4A — resolve the numerical design + +Before coding the public function: + +1. review Sections 10 and 11; +2. compare the preferred search with any owner-proposed alternative; +3. prototype candidate algorithms on representative pairs; +4. compare against a slower dense reference scan; +5. select merge, tie, width, and weighting tolerances; +6. record the chosen behavior in tests/docstrings and update this plan if + necessary. + +Representative development pairs should include: + +```text +H-H +C-C +C-O +Fe-O +La-O +U-O +H-heavy-element +a pair with multiple shallow candidates +a pair with globally tied or symmetry-related candidates +a short-distance boundary-dominated case +``` + +#### Stage 4B — implement result types and search + +Implement immutable results exposing all binding information from Section 10. + +The API MUST not hide: + +- multiple minima; +- ties; +- boundary domination; +- weighting definition; +- width definition; +- dataset and unit provenance. + +#### Stage 4C — validation + +Required tests cover: + +- independent dense-reference agreement; +- pair reversal; +- homonuclear symmetry; +- narrow and broad minima; +- width clipping; +- dimensionless weighting; +- density-unit invariance; +- boundary-dominated and no-interior-minimum cases; +- invalid distance and units; +- missing elements; +- representative element classes; +- practical cached performance. + +#### Completion criteria + +- all relevant local minima are inspectable; +- global ties are retained; +- width and weighted center/spread are defined and tested; +- reversal and symmetry hold; +- invalid/boundary situations are explicit; +- no exact molecular-topology claim is made. + +#### Suggested commit + +```text +feat: add approximate IAS position analysis +``` + +--- + +### Stage 5 — Executed feature notebook and `0.2.0` release + +#### Goal + +Demonstrate the accepted functionality, complete minimum documentation, and +release `0.2.0` without performing the full documentation overhaul. + +#### Required notebook work + +Add and execute: + +```text +notebooks/04-proatomic-density-and-ias.ipynb +``` + +It MUST satisfy Section 12.2. + +For `0.2.0`, extend existing notebook validation minimally so CI verifies: + +- the notebook exists; +- all nonempty code cells have execution counts; +- expected output-bearing cells contain saved outputs; +- the code executes cleanly; +- required Markdown sections are present. + +Do not build a second large notebook pipeline during this stage. + +#### Minimum documentation + +- add the new notebook to the notebook overview; +- link the saved notebook from docs; +- add accurate density/IAS guide material; +- add the new module API page; +- mention the feature on the home page without the full marketing rewrite; +- document exact provenance, units, 20-bohr domain, and limitations; +- update architecture only where needed for generic datasets; +- update changelog and release metadata. + +#### Project metadata + +Update: + +- version to `0.2.0`; +- package keywords for proatomic density/electron density/IAS; +- build inclusion for `.gz`; +- installed-wheel smoke tests. + +#### Installed-wheel smoke test + +At minimum: + +```python +import atomref as ar + +ref = ar.DatasetRef("proatomic_density", "") +dataset = ar.get_builtin_set(ref) + +rho = ar.get_proatomic_density( + "O", + 0.75, + radius_unit="angstrom", + density_unit="electron/bohr^3", +) +assert rho is not None and rho > 0 + +result = ar.estimate_ias_position("C", "O", 1.43) +assert result is not None +``` + +#### Final commands + +Run all current checks, including: + +```bash +python tools/check_registry.py +python tools/check_notebooks.py +python tools/export_notebooks.py --check +python tools/gen_readme.py --check +flake8 src tests tools +python -m pytest -q +mkdocs build --strict +python tools/release_check.py +``` + +If the feature notebook is not compatible with the current Markdown exporter +because it contains plots, the exporter may omit that notebook for `0.2.0` while +the docs link directly to the saved `.ipynb`. Record this temporary state for +Stage 6 rather than implementing a plot-specific Markdown exporter. + +#### Completion criteria + +- all `0.2.0` acceptance criteria pass; +- feature notebook is executed and saved; +- wheel/sdist work; +- minimal docs are accurate; +- no broad docs rewrite has obscured scientific review. + +#### Suggested commits + +```text +docs: add proatomic density and IAS notebook +chore: prepare atomref 0.2.0 +``` + +--- + +## `0.2.1` documentation and discoverability work + +Do not start these stages until `0.2.0` is accepted. + +### Stage 6 — Notebook infrastructure and content overhaul + +#### Goal + +Make notebooks true executable documentation with saved, verifiable rich +outputs while reducing duplicate files and tools. + +#### Required changes + +1. Add notebook/documentation optional dependencies: + - `nbformat`; + - `nbclient` or `nbconvert`; + - `ipykernel`; + - `matplotlib`; + - `mkdocs-jupyter`. +2. Rewrite one existing notebook tool to support execution/update/check modes. +3. Execute and save all notebooks. +4. Add explanatory Markdown before every logical code section. +5. Render actual `.ipynb` files directly in MkDocs. +6. Remove generated `docs/notebooks/*.md`. +7. Remove `tools/export_notebooks.py`. +8. Remove export-sync tests and CI steps. +9. Add normalized execution-freshness checks. +10. Ensure plots render on the documentation site. + +#### Preferred command model + +```bash +python tools/check_notebooks.py --write +python tools/check_notebooks.py --check +``` + +The exact name may remain `check_notebooks.py` even though write mode is +supported, to avoid another file. + +#### Completion criteria + +- every notebook is executed and saved; +- re-execution reproduces committed normalized outputs; +- plots are visible in MkDocs; +- no generated Markdown notebook copies remain; +- notebook prose is suitable for learning, not only smoke testing; +- the number of notebook tools/files is reduced. + +#### Suggested commit + +```text +docs: make notebooks executable documentation +``` + +--- + +### Stage 7 — Complete API docs, README, and `0.2.1` release + +#### Goal + +Make the documentation immediately useful to both new users and developers. + +#### Required changes + +1. Configure `mkdocstrings` for full typed signatures and structured sections. +2. Rewrite all intended public docstrings in one consistent style. +3. Verify every public parameter, return value, raised error, unit, and + important attribute appears in rendered API docs. +4. Review API page member selection. +5. Rewrite `docs/index.md` for product positioning and regenerate `README.md`. +6. Improve quickstart flow. +7. Remove `docs/dev/dev_plan.md` and its navigation item. +8. Ensure architecture and data-curation docs match the final code. +9. Update changelog and version to `0.2.1`. +10. Simplify CI commands after removing notebook Markdown export. + +#### API documentation acceptance + +For every public function/class added or retained: + +- signature is visible; +- annotations are visible; +- parameters include type, meaning, and default; +- return value is described; +- raised errors are described; +- units and valid ranges are explicit where relevant; +- dataclass attributes are visible; +- examples are present for non-obvious APIs; +- cross-references work. + +#### README acceptance + +A new visitor should discover within the initial screen: + +- the package purpose; +- radii, profile, and IAS capabilities; +- the dependency-free runtime; +- a compact working example; +- provenance as a central value; +- links to installation, notebooks, datasets, and API. + +#### Final checks + +```bash +python tools/check_registry.py +python tools/check_notebooks.py --check +python tools/gen_readme.py --check +flake8 src tests tools +python -m pytest -q +mkdocs build --strict +python tools/release_check.py +``` + +#### Suggested commits + +```text +docs: complete typed API reference +docs: reposition atomref documentation +chore: prepare atomref 0.2.1 +``` + +## 14. Release acceptance checklists + +### 14.1 `0.2.0` scope and compatibility + +- [ ] Version is `0.2.0`. +- [ ] Existing `0.1.x` public APIs remain available. +- [ ] Existing scalar values and default policies are unchanged. +- [ ] Runtime dependencies remain empty. +- [ ] Runtime performs no network access. +- [ ] No ionic or profile-correlation behavior is exposed. +- [ ] `get_builtin_set()` loads every packaged scalar and radial dataset. +- [ ] Scalar policy code rejects radial sets clearly. +- [ ] Registry discovery and metadata work identically across payload kinds. + +### 14.2 `0.2.0` data + +- [ ] Source profiles hash matches the pinned `profiles.csv`. +- [ ] Source metadata hash matches the pinned `metadata.json`. +- [ ] Upstream dataset ID and version match. +- [ ] Basis ID and basis hash match. +- [ ] Exactly 103 neutral profiles are selected. +- [ ] Coverage is exactly Z=1..103. +- [ ] Retained row count is 1127. +- [ ] Public limit is exactly 20 bohr. +- [ ] First retained point above 20 bohr brackets the endpoint. +- [ ] All retained densities are finite and positive. +- [ ] All retained profiles are monotone non-increasing within tolerance. +- [ ] Compressed output is deterministic. +- [ ] Snapshot is built by the maintainer tool, not hand-edited. +- [ ] Wheel and sdist contain the profile data. +- [ ] Registry and notice contain CC BY attribution and exact DOIs. + +### 14.3 `0.2.0` density API + +- [ ] Profile retrieval works for H–Lr. +- [ ] D/T map to H. +- [ ] Unsupported elements return `None`. +- [ ] Radius units and density units are independent. +- [ ] Default radius unit is ångström. +- [ ] Default density unit is electron/bohr³. +- [ ] Electron/ų output is available explicitly. +- [ ] `r=0` uses the first-grid value. +- [ ] Exact knots reproduce stored values. +- [ ] Positive-segment log–log interpolation is tested. +- [ ] `r=20 bohr` is valid. +- [ ] `r>20 bohr` raises. +- [ ] Negative and non-finite radii raise. +- [ ] Loading is lazy/cached through generic dataset machinery. +- [ ] Shared package data cannot be mutated through public objects. + +### 14.4 `0.2.0` IAS API + +- [ ] Distance is measured from A toward B and documented. +- [ ] Distance must be `0 < R <= 20 bohr`. +- [ ] Distance and density units are independent. +- [ ] All distinct interior local minima are exposed. +- [ ] Each minimum includes position and density decomposition. +- [ ] Global ties are retained. +- [ ] Minimum width is defined, dimensioned, and tested. +- [ ] Weighted center uses dimensionless weights. +- [ ] Weighted spread or equivalent ambiguity measure is exposed. +- [ ] Density-unit conversion does not change IAS geometry. +- [ ] Pair reversal transforms positions correctly. +- [ ] Homonuclear weighted centers equal `R/2`. +- [ ] Boundary-dominated cases are explicit. +- [ ] Missing profiles return `None`. +- [ ] Invalid distances and units raise. +- [ ] Dataset/interpolation provenance is present. +- [ ] Representative results agree with an independent reference search. +- [ ] The algorithm does not assume global unimodality. + +### 14.5 `0.2.0` notebook and packaging + +- [ ] Proatomic density/IAS notebook exists. +- [ ] It is executed and saved with outputs. +- [ ] It contains explanatory Markdown before logical code sections. +- [ ] It contains at least two informative plots. +- [ ] It demonstrates local/global/weighted minima. +- [ ] It executes cleanly in CI. +- [ ] Minimal public docs describe source, units, range, and limitations. +- [ ] README is regenerated and mentions the feature accurately. +- [ ] Strict docs build passes. +- [ ] Full release check passes. +- [ ] Installed wheel loads the radial set, evaluates density, and performs IAS + analysis. + +### 14.6 `0.2.1` notebook documentation + +- [ ] All shipped notebooks are executed and saved. +- [ ] Re-execution freshness is checked through a Jupyter kernel. +- [ ] Notebook plots and rich outputs render directly in MkDocs. +- [ ] Logical code sections have explanatory Markdown. +- [ ] Generated notebook Markdown copies are removed. +- [ ] The separate notebook exporter is removed. +- [ ] One maintained notebook tool handles update and check modes. +- [ ] CI and docs workflows use the simplified process. + +### 14.7 `0.2.1` API and README documentation + +- [ ] Full typed signatures render. +- [ ] All public parameters, defaults, returns, raises, and attributes render. +- [ ] Public docstrings use one structured style. +- [ ] API cross-references work. +- [ ] README/home page explains value immediately. +- [ ] Working radii, density, and IAS examples appear near the top. +- [ ] Detailed policy terminology is moved below first-use content. +- [ ] Duplicate development-plan docs are removed. +- [ ] Strict docs and full release checks pass. + +## 15. Optional architecture review after `0.2.0` + +Do not implement this section automatically during the feature release. + +After the density and IAS core is accepted, review actual friction encountered. + +Possible small follow-up work only when justified by evidence: + +- replace the hard-coded `[None] * 119` scalar allocation with a size derived + from the element table; +- improve storage-kind validation if Stage 1 reveals weak points; +- add a static type-checking CI job; +- optimize repeated IAS evaluation if profiling shows a bottleneck; +- add scalar batch evaluation if downstream use proves it necessary; +- improve mixed-license artifact checks. + +If generic `get_builtin_set()` dispatch could not be completed in `0.2.0`, that +becomes the highest-priority architecture task before expanding the profile API. +A temporary bypass must not silently become permanent. + +Do not schedule a refactor release merely because it was anticipated. If the +feature integrates cleanly, complete `0.2.1` docs and stop. + +## 16. Future compatibility and non-actionable roadmap + +Everything in this section is for future reference. An agent implementing +`0.2.0` or `0.2.1` MUST NOT begin this work without an explicit scope change. + +### 16.1 Ions and additional atomic properties + +The initial radial dataset is neutral only. + +To avoid blocking future ionic datasets: + +- keep dataset identity explicit; +- state charge scope in metadata; +- do not imply that one profile per element is universal; +- do not encode state selection into the element parser; +- allow a later keyword-only dataset/state selector without breaking current + calls; +- preserve the ability for a radial set to have a richer key model in a future + release. + +Do not add now: + +- a `charge` argument that only accepts zero; +- an unused `AtomicStateKey` class; +- ionic configuration metadata copied from upstream; +- provisional automatic ion-selection rules. + +Future ions require an evidence-aware state-selection design, especially for +anions and heavy elements. They should be introduced as explicitly identified +datasets rather than guessed through scalar transfer policies. + +Additional scalar or radial properties should continue to use the same registry +and `get_builtin_set()` machinery whenever their key space and payload can be +represented clearly. + +### 16.2 Future three-dimensional grid-density generation + +A future release may add functions that sample radial proatomic profiles onto a +three-dimensional grid. + +Conceptual workflows: + +```python +single = proatomic_density_grid( + element, + position, + grid=..., + ... +) + +total = promolecular_density_grid( + atoms, + grid=..., + ... +) +``` + +The exact API is intentionally unresolved. + +Potential supported nonperiodic grid description: + +- origin vector; +- integer shape `(nx, ny, nz)`; +- three grid-step vectors, allowing non-orthogonal grids; +- coordinate unit; +- density output unit; +- radial cutoff not exceeding the profile domain. + +Potential periodic extension: + +- periodic cell vectors; +- atomic Cartesian or fractional coordinates; +- explicit periodic axes; +- summation over every periodic image intersecting the radial cutoff; +- correct handling of skew cells and grids; +- no assumption that wrapping an atom once is equivalent to image summation. + +Important design questions: + +1. Should the API require NumPy through an optional extra? +2. Should it accept/output an array protocol rather than nested Python lists? +3. How should memory allocation and chunking work for large crystallographic + grids? +4. Should the single-atom function be public if the multi-atom implementation + needs a more efficient shared kernel? +5. How are cell, grid, and atomic coordinate units represented? +6. How are periodic images enumerated robustly for triclinic cells? +7. Is the 20-bohr radial cutoff always sufficient for the intended grid use? +8. Should atomic contributions be returned separately or only summed? +9. How are spin-resolved or ionic profiles represented later? + +Recommended direction: + +- stabilize and validate scalar radial evaluation and IAS first; +- gather requirements from actual crystallographic grid formats; +- design nonperiodic and periodic behavior together; +- use an optional NumPy-based extra rather than making NumPy a core runtime + dependency; +- implement one optimized multi-atom kernel and let any single-atom convenience + wrapper call the same core where efficient; +- add independent tests against direct pointwise profile sums. + +This is a plausible and valuable `atomref` capability because it converts the +package’s reference radial functions into spatial promolecular densities. +Nevertheless, premature implementation would risk choosing the wrong array, +periodicity, and grid conventions. + +### 16.3 Other possible future work + +Future candidates, only after demonstrated need: + +- vectorized one-dimensional profile evaluation; +- user-supplied radial sets with the same interface; +- ionic profile datasets and state selectors; +- additional reference functions; +- direct helpers for stockholder initialization; +- optional integration utilities for common crystallographic grid formats. + +Do not turn this list into active tasks during `0.2.0` or `0.2.1`. + +## 17. Suggested commit sequence + +Preferred linear history for `0.2.0`: + +```text +refactor: generalize built-in dataset loading +feat(data): add neutral proatomic density snapshot +feat: add proatomic density evaluation +feat: add approximate IAS position analysis +docs: add proatomic density and IAS notebook +chore: prepare atomref 0.2.0 +``` + +Preferred linear history for `0.2.1`: + +```text +docs: make notebooks executable documentation +docs: complete typed API reference +docs: reposition atomref documentation +chore: prepare atomref 0.2.1 +``` + +Commits may be combined when changes are inseparable, but do not combine an +entire release into one opaque commit. + +Do not rewrite accepted history. + +## 18. Agent completion report template + +Use this template after each stage: + +```text +Stage: + + +Implemented: + - ... + +Files added: + - ... + +Files removed: + - ... + +Files changed: + - ... + +Validation: + - : + +Plan deviations: + - none + or + - proposed design: + - issue found: + - replacement: + - justification: + - protecting tests: + +Open questions: + - ... + +Suggested commit: + + +Next step: + +``` + +## 19. Open review points for this draft + +Review before the corresponding implementation stage: + +1. Final atomref-side density dataset ID. +2. Final radial loaded-set class name: + - `ElementRadialSet`; + - or `ProatomicDensitySet`. +3. Exact generic `get_builtin_set()` return annotation and scalar narrowing + helper names. +4. Final public module and density/IAS function names. +5. Canonical density unit strings and accepted aliases. +6. Exact IAS candidate-search algorithm. +7. Candidate merge and global-tie tolerances. +8. Whether the 5%-rise width is the preferred width definition. +9. Final dimensionless weighting function and default power. +10. Exact result dataclass fields and status vocabulary. +11. Whether weighted position or global-minimum position is the primary + convenience coordinate. +12. Whether the `0.2.0` feature notebook is linked only as `.ipynb` or given a + temporary static docs representation before `0.2.1`. +13. Final `0.2.1` notebook renderer and normalization details. +14. Final MkDocs/mkdocstrings visual settings after a local rendered preview. +15. The future grid-density requirements once actual target formats are + collected. + +These are review points, not permission to weaken the binding scientific, +provenance, range, dataset-unification, unit, or compatibility contracts. + +## 20. Plan closeout and replanning record + +This section is intentionally left as a template while the plan is `ACTIVE`. +The implementation agent MUST complete the appropriate subsection before +changing the lifecycle state to `COMPLETE_AWAITING_REVIEW` or +`REPLAN_REQUIRED`. + +### 20.1 Completion record + +```text +Lifecycle state: + COMPLETE_AWAITING_REVIEW + +Completed releases/stages: + - ... + +Final commits or pull requests: + - ... + +Acceptance checks: + - : + +Binding requirements not implemented: + - none + or + - + +Plan deviations: + - none + or + - + +Future-reference items intentionally not touched: + - ... + +Known limitations remaining within the accepted release: + - ... + +Implementation agent conclusion: + - + +External review required: + - verify repository diff and history + - verify acceptance checklist + - verify scientific and unit contracts + - verify documentation and packaged artifacts + - either set state to CLOSED or return the plan to ACTIVE with corrections +``` + +### 20.2 Replanning-required record + +```text +Lifecycle state: + REPLAN_REQUIRED + +Blocked stage: + - ... + +Observed problem: + - ... + +Why the current plan cannot safely continue: + - ... + +Evidence and reproduction: + - ... + +Approaches attempted: + - ... + +Partial changes present: + - ... + +Repository/branch safety state: + - clean / partial changes retained / revert recommended / other + +Decisions requiring a new plan: + - ... + +Suggested replanning questions: + - ... + +Implementation agent conclusion: + - stop implementation and request external review/replanning +``` + +### 20.3 External reviewer disposition + +```text +Reviewer: + - ... + +Review date: + - ... + +Disposition: + - CLOSED — completed + - CLOSED — superseded/abandoned + - ACTIVE — corrections required under this plan + - REPLAN_REQUIRED — a replacement plan must be prepared + +Review notes: + - ... +``` -- more radii sets -- uncertainty and confidence flags -- ion-specific or atom-type-specific domains -- density-derived radii and related reference transforms From fd4bdcfb0bc864dbf5b3c502e82eb7c593f69de4 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Sun, 12 Jul 2026 19:22:44 +0300 Subject: [PATCH 02/23] refactor: generalize built-in dataset loading --- DEV_PLAN.md | 69 ++++-- docs/api/registry.md | 9 +- docs/dev/architecture.md | 21 +- src/atomref/__init__.py | 4 + src/atomref/data/registry.json | 9 + src/atomref/policy.py | 31 +-- src/atomref/radii.py | 4 +- src/atomref/registry.py | 268 +++++++++++++++++++++- src/atomref/transfer.py | 8 +- src/atomref/xh.py | 4 +- tests/meta/test_public_api.py | 2 + tests/meta/test_registry_integrity.py | 38 ++- tests/registry/test_registry.py | 318 ++++++++++++++++++++++++++ tools/check_registry.py | 55 ++++- 14 files changed, 759 insertions(+), 81 deletions(-) diff --git a/DEV_PLAN.md b/DEV_PLAN.md index aa29af4..6166462 100644 --- a/DEV_PLAN.md +++ b/DEV_PLAN.md @@ -555,13 +555,22 @@ The limit also provides a simple numerical contract: ### 6.6 Packaged representation -The preferred representation is one compressed wide CSV in the existing package -data directory: +The consumer representation is one deterministic single-member ZIP archive in +the existing package data directory: ```text -src/atomref/data/proatomic_density_neutral.csv.gz +src/atomref/data/proatomic_density_neutral.zip ``` +The archive MUST contain exactly one regular file named: + +```text +proatomic_density_neutral.csv +``` + +It remains a container for a plain wide CSV, not a binary scientific-data +format. + Preferred logical structure: ```text @@ -576,8 +585,15 @@ Binding requirements: - one density column per Z from 1 through 103; - columns ordered by increasing Z; - exact source decimal values retained where practical; -- deterministic row and line ordering; -- deterministic gzip output, including fixed gzip timestamp; +- deterministic CSV bytes, row ordering, column ordering, and LF line endings; +- exactly one archive member named `proatomic_density_neutral.csv`; +- no directory entries or additional archive members; +- fixed member timestamp `(1980, 1, 1, 0, 0, 0)`; +- normalized Unix platform metadata (`create_system = 3`) and regular-file + mode metadata (`external_attr = 0o100644 << 16`, `internal_attr = 0`); +- empty archive and member comments; +- no uncontrolled ZIP extra fields; +- fixed `ZIP_DEFLATED` compression with explicit compression level 9; - no duplicated symbol columns; - no ionic or configuration data; - no second large metadata file. @@ -587,10 +603,9 @@ The element symbol mapping MUST come from the existing element registry. All interpretive metadata SHOULD live in the existing `registry.json`, with license/citation text also added to `NOTICE.md` and public documentation. -The agent MAY choose another standard-library-readable compressed representation -only when it is demonstrably simpler or safer and remains reproducibly -inspectable. Do not introduce a binary format merely for minor speed or size -improvements. +This owner-approved ZIP representation is binding for the `0.2.x` consumer +snapshot. Do not substitute another container or introduce a binary scientific +data format. ### 6.7 Licensing and attribution @@ -644,7 +659,7 @@ Preferred registry storage identifiers: ```text element_scalar_csv -element_radial_csv_gzip +element_radial_csv_zip ``` or an equivalent pair of clear stable identifiers. @@ -655,7 +670,7 @@ The registry remains the single source of truth for: - aliases; - coverage; - scientific metadata; -- storage kind and filename; +- storage kind, filename, and archive member where applicable; - provenance and references; - loading dispatch. @@ -735,7 +750,7 @@ Preferred new files for `0.2.0`: ```text src/atomref/proatoms.py -src/atomref/data/proatomic_density_neutral.csv.gz +src/atomref/data/proatomic_density_neutral.zip tools/build_proatomic_density_snapshot.py tests/proatoms/test_dataset.py tests/proatoms/test_density.py @@ -770,6 +785,7 @@ coverage. - storage kind; - filename; +- archive member name where applicable; - radius column; - density-column convention; - native coordinate unit; @@ -1757,7 +1773,7 @@ the generator project. 2. Add one compressed package-data file. 3. Add the `proatomic_density` quantity and dataset metadata to `registry.json`. 4. Update `NOTICE.md` with CC BY attribution and exact DOIs. -5. Update package build inclusion for `.gz`. +5. Update package build inclusion for `.zip`. 6. Extend registry, package-data, and distribution checks. 7. Add focused source/data-integrity tests. 8. Document the maintainer tool briefly in `tools/README.md`. @@ -1796,7 +1812,7 @@ The builder MUST: - verify every profile is monotonically non-increasing within a documented numerical tolerance; - write columns in increasing Z order; -- create deterministic gzip output; +- create the deterministic single-member ZIP defined in Section 6.6; - support a check mode and a write mode; - print a concise source/output summary. @@ -1831,6 +1847,10 @@ Add one atomref-side set ID clearly identifying: The exact ID remains a review point. +Its storage metadata MUST declare `element_radial_csv_zip`, filename +`proatomic_density_neutral.zip`, and member +`proatomic_density_neutral.csv`. + The new dataset MUST load through: ```python @@ -2088,7 +2108,7 @@ Update: - version to `0.2.0`; - package keywords for proatomic density/electron density/IAS; -- build inclusion for `.gz`; +- build inclusion for `.zip`; - installed-wheel smoke tests. #### Installed-wheel smoke test @@ -2276,14 +2296,14 @@ chore: prepare atomref 0.2.1 ### 14.1 `0.2.0` scope and compatibility - [ ] Version is `0.2.0`. -- [ ] Existing `0.1.x` public APIs remain available. -- [ ] Existing scalar values and default policies are unchanged. -- [ ] Runtime dependencies remain empty. -- [ ] Runtime performs no network access. -- [ ] No ionic or profile-correlation behavior is exposed. +- [x] Existing `0.1.x` public APIs remain available. +- [x] Existing scalar values and default policies are unchanged. +- [x] Runtime dependencies remain empty. +- [x] Runtime performs no network access. +- [x] No ionic or profile-correlation behavior is exposed. - [ ] `get_builtin_set()` loads every packaged scalar and radial dataset. -- [ ] Scalar policy code rejects radial sets clearly. -- [ ] Registry discovery and metadata work identically across payload kinds. +- [x] Scalar policy code rejects radial sets clearly. +- [x] Registry discovery and metadata work identically across payload kinds. ### 14.2 `0.2.0` data @@ -2298,9 +2318,10 @@ chore: prepare atomref 0.2.1 - [ ] First retained point above 20 bohr brackets the endpoint. - [ ] All retained densities are finite and positive. - [ ] All retained profiles are monotone non-increasing within tolerance. -- [ ] Compressed output is deterministic. +- [ ] ZIP output satisfies the deterministic single-member contract in Section + 6.6. - [ ] Snapshot is built by the maintainer tool, not hand-edited. -- [ ] Wheel and sdist contain the profile data. +- [ ] Wheel and sdist contain `proatomic_density_neutral.zip`. - [ ] Registry and notice contain CC BY attribution and exact DOIs. ### 14.3 `0.2.0` density API diff --git a/docs/api/registry.md b/docs/api/registry.md index 9c41653..1d20375 100644 --- a/docs/api/registry.md +++ b/docs/api/registry.md @@ -3,8 +3,8 @@ This module contains the packaged data model. If you want to understand how `atomref` classifies datasets, how aliases are -resolved, or how built-in CSV tables are turned into typed in-memory objects, -this is the key module to read. +resolved, or how built-in scalar CSV and radial ZIP/CSV payloads become typed +in-memory objects, this is the key module to read. The most important registry ideas are: @@ -16,4 +16,9 @@ In the current runtime, the implemented lookup domain is `element`. The registry still stores `domain` explicitly because the metadata design is meant to stay reusable as the package grows. +`get_builtin_set()` dispatches `element_scalar_csv` and +`element_radial_csv_zip` storage and returns the `BuiltinSet` union. Policy +consumers explicitly narrow that result to `ElementScalarSet`; radial profiles +do not participate in scalar policy or transfer behavior. + ::: atomref.registry diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 680b755..6c8c3fd 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -5,8 +5,8 @@ Publicly, `atomref` is still radii-first, with a small provisional X–H helper. Internally, the package is built around four layers: 1. **elements** — stable element metadata and symbol canonicalization, -2. **registry** — curated quantity and dataset metadata plus packaged data - loading, +2. **registry** — curated quantity and dataset metadata plus generic packaged + scalar/radial data loading, 3. **policy core** — generic value selection with overrides, transfers, fallbacks, blocked keys, and provenance, 4. **quantity wrappers** — convenience APIs such as `atomref.radii` and @@ -32,11 +32,20 @@ implements only one domain: - `element` -That means: +Packaged element data currently use two explicit storage kinds: -- packaged built-in sets are currently element-indexed scalar tables, -- `ValuePolicy` resolves element symbols, -- transfer fitting is performed over element-wise overlap. +- `element_scalar_csv` for dense-by-Z scalar tables, +- `element_radial_csv_zip` for a single-member ZIP containing shared-grid + radial profiles. + +`get_builtin_set()` dispatches both kinds and returns the `BuiltinSet` union of +`ElementScalarSet` and `ElementRadialSet`. Scalar consumers narrow that union +through `resolve_scalar_dataset_like()`. + +The policy and transfer machinery remains intentionally scalar-only: +`ValuePolicy` resolves element scalars and transfer fitting uses element-wise +scalar overlap. Radial profiles receive no `ValuePolicy`, substitution, or +linear-transfer behavior. The metadata keeps `domain` explicit now so later versions can extend the data model without having to reinterpret existing registry entries. diff --git a/src/atomref/__init__.py b/src/atomref/__init__.py index fb569b3..9d37135 100644 --- a/src/atomref/__init__.py +++ b/src/atomref/__init__.py @@ -36,9 +36,11 @@ lookup_xh_bond_length, ) from .registry import ( + BuiltinSet, CoverageInfo, DatasetInfo, DatasetRef, + ElementRadialSet, ElementScalarSet, QuantityInfo, Reference, @@ -58,9 +60,11 @@ "get_element", "iter_elements", "is_valid_element_symbol", + "BuiltinSet", "CoverageInfo", "DatasetInfo", "DatasetRef", + "ElementRadialSet", "ElementScalarSet", "QuantityInfo", "Reference", diff --git a/src/atomref/data/registry.json b/src/atomref/data/registry.json index e6e4469..55bec2e 100644 --- a/src/atomref/data/registry.json +++ b/src/atomref/data/registry.json @@ -40,6 +40,7 @@ "phase_context": "condensed_phase", "method_summary": "Derived from crystallographic bond distances (primarily single bonds) across the periodic table.", "storage": { + "kind": "element_scalar_csv", "format": "dense_by_z_csv", "filename": "covalent.csv", "column": "cordero2008" @@ -78,6 +79,7 @@ "phase_context": "mixed_or_legacy", "method_summary": null, "storage": { + "kind": "element_scalar_csv", "format": "dense_by_z_csv", "filename": "covalent.csv", "column": "csd_legacy_cov" @@ -121,6 +123,7 @@ "phase_context": "mixed_or_legacy", "method_summary": "Bondi compiled van der Waals radii from a combination of experimental sources (e.g., crystal structures, liquid-state properties, gas kinetic data) to reproduce molecular/atomic volumes and sizes. This set is widely used as a historical reference and in many computational chemistry defaults.", "storage": { + "kind": "element_scalar_csv", "format": "dense_by_z_csv", "filename": "van_der_waals.csv", "column": "bondi1964" @@ -206,6 +209,7 @@ "phase_context": "condensed_phase", "method_summary": "Rowland & Taylor analyzed distributions of intermolecular nonbonded contact distances in organic crystal structures from the Cambridge Structural Database (CSD). They fitted/estimated characteristic contact distances and solved for per-element radii by least-squares analysis over many element-pair distance distributions.", "storage": { + "kind": "element_scalar_csv", "format": "dense_by_z_csv", "filename": "van_der_waals.csv", "column": "rowland_taylor1996" @@ -255,6 +259,7 @@ "phase_context": "condensed_phase", "method_summary": null, "storage": { + "kind": "element_scalar_csv", "format": "dense_by_z_csv", "filename": "van_der_waals.csv", "column": "alvarez2013" @@ -301,6 +306,7 @@ "phase_context": "condensed_phase", "method_summary": "Chernyshov et al. introduce a line-of-sight (LoS) criterion to identify 'direct' interatomic contacts in complex molecular crystals. vdW radii are then inferred from statistically analyzed contact-distance distributions for specific atom types, yielding radii (including R_half and R_max variants) intended to better reflect steric/anistropic effects than simple distance-based heuristics.", "storage": { + "kind": "element_scalar_csv", "format": "dense_by_z_csv", "filename": "van_der_waals.csv", "column": "chernyshov2020" @@ -351,6 +357,7 @@ "phase_context": "mixed_or_legacy", "method_summary": null, "storage": { + "kind": "element_scalar_csv", "format": "dense_by_z_csv", "filename": "van_der_waals.csv", "column": "csd_legacy_vdw" @@ -405,6 +412,7 @@ "phase_context": "isolated_atom", "method_summary": "Rahm et al. computed relativistic all-electron DFT electron densities (close to the basis-set limit) for isolated atoms and ions. Radii are defined by an electron-density threshold, producing a consistent, theory-based size measure that correlates well with structural van der Waals radii derived from crystal structures.", "storage": { + "kind": "element_scalar_csv", "format": "dense_by_z_csv", "filename": "van_der_waals.csv", "column": "rahm2016" @@ -452,6 +460,7 @@ "phase_context": "condensed_phase", "method_summary": "Sparse parent-element target set for hydrogen normalisation. ConQuest moves H along the experimentally determined X-H vector to these neutron-derived distances.", "storage": { + "kind": "element_scalar_csv", "format": "dense_by_z_csv", "filename": "xh_bond_length.csv", "column": "csd_legacy_xh_cno" diff --git a/src/atomref/policy.py b/src/atomref/policy.py index 79cc9f3..6e6b960 100644 --- a/src/atomref/policy.py +++ b/src/atomref/policy.py @@ -17,12 +17,11 @@ ) from .errors import PolicyError from .registry import ( - DatasetLike, DatasetRef, ElementScalarSet, + ScalarDatasetLike, _is_placeholder_value, - get_builtin_set, - resolve_dataset_like, + resolve_scalar_dataset_like, ) from .transfer import ( LinearFit, @@ -86,7 +85,7 @@ class ValuePolicy(Generic[K]): element symbols and validated as finite floats. """ - base: DatasetLike + base: ScalarDatasetLike transfers: tuple[TransferModel, ...] = () overrides: Mapping[K, float] = field(default_factory=dict) fallback: float | None = None @@ -102,7 +101,7 @@ def __post_init__(self) -> None: _coerce_policy_float(self.fallback, what="policy fallback"), ) - base_set = resolve_dataset_like(self.base) + base_set = resolve_scalar_dataset_like(self.base) if base_set.info.domain != "element": return @@ -210,7 +209,7 @@ def _normalize_element_symbol(symbol: str | None) -> str | None: def _resolve_target_ref(policy: ValuePolicy[object]) -> DatasetRef: """Return the target dataset reference implied by a policy base.""" - return resolve_dataset_like(policy.base).ref + return resolve_scalar_dataset_like(policy.base).ref def _policy_resolution_tokens( @@ -260,13 +259,13 @@ def _coerce_nested_policy( def _materialize_transfer_source( - source: DatasetLike | SupportsValuePolicy | ValuePolicy[str], + source: ScalarDatasetLike | SupportsValuePolicy | ValuePolicy[str], ) -> _ResolvedElementSource: """Materialize any element-domain transfer source into dense by-Z arrays.""" nested_policy, nested_owner = _coerce_nested_policy(source) if nested_policy is None: - dataset = resolve_dataset_like(source) + dataset = resolve_scalar_dataset_like(source) placeholders = tuple( False if value is None @@ -317,13 +316,13 @@ def _materialize_transfer_source( def _lookup_transfer_source_value( symbol: str, - source: DatasetLike | SupportsValuePolicy | ValuePolicy[str], + source: ScalarDatasetLike | SupportsValuePolicy | ValuePolicy[str], ) -> tuple[_TransferSourceValue | None, str | None]: """Resolve one element value from a transfer source or nested policy.""" nested_policy, nested_owner = _coerce_nested_policy(source) if nested_policy is None: - source_set = resolve_dataset_like(source) + source_set = resolve_scalar_dataset_like(source) value = source_set.get(symbol) if value is None: return None, f"no value in {source_set.ref.set_id}" @@ -493,7 +492,7 @@ def _fit_linear_transfer_cached( """Cache fits between two packaged datasets for repeated reuse.""" return _fit_linear_transfer( - get_builtin_set(base_ref), + resolve_scalar_dataset_like(base_ref), _materialize_transfer_source(predictor_ref), min_points=min_points, exclude_placeholders=exclude_placeholders, @@ -502,7 +501,9 @@ def _fit_linear_transfer_cached( ) -def _fit_transfer_model(base: DatasetLike, transfer: TransferModel) -> LinearFit | None: +def _fit_transfer_model( + base: ScalarDatasetLike, transfer: TransferModel +) -> LinearFit | None: """Return the fit object for a transfer model when it needs one.""" if not isinstance(transfer, LinearTransfer): @@ -523,7 +524,7 @@ def _fit_transfer_model(base: DatasetLike, transfer: TransferModel) -> LinearFit transfer.fit_max_depth, ) return _fit_linear_transfer( - resolve_dataset_like(base), + resolve_scalar_dataset_like(base), _materialize_transfer_source(predictor), min_points=transfer.min_points, exclude_placeholders=transfer.exclude_placeholders, @@ -572,7 +573,7 @@ def _apply_substitution_transfer( def _apply_linear_transfer( symbol: str, *, - base: DatasetLike, + base: ScalarDatasetLike, target: DatasetRef, transfer: LinearTransfer, ) -> tuple[LookupResult | None, str | None]: @@ -661,7 +662,7 @@ def _resolve_value( stack_token = _ACTIVE_POLICY_TOKENS.set(active_tokens + resolution_tokens) try: target = _resolve_target_ref(policy) - base_set = resolve_dataset_like(policy.base) + base_set = resolve_scalar_dataset_like(policy.base) if base_set.info.domain != "element": raise PolicyError( "the resolver currently supports only element-domain datasets" diff --git a/src/atomref/radii.py b/src/atomref/radii.py index b33877f..2855fc1 100644 --- a/src/atomref/radii.py +++ b/src/atomref/radii.py @@ -20,10 +20,10 @@ DatasetInfo, DatasetRef, ElementScalarSet, - get_builtin_set, get_dataset_info, list_dataset_ids, list_dataset_infos, + resolve_scalar_dataset_like, ) from .transfer import LinearFit, LinearTransfer, SubstitutionTransfer, TransferModel @@ -204,7 +204,7 @@ def get_radii_set_info(kind: RadiiKind, set_id: str) -> DatasetInfo: def get_radii_set(kind: RadiiKind, set_id: str) -> RadiiSet: """Load one packaged radii set as an :class:`ElementScalarSet`.""" - return get_builtin_set(DatasetRef(_quantity_for_kind(kind), set_id)) + return resolve_scalar_dataset_like(DatasetRef(_quantity_for_kind(kind), set_id)) def _validate_policy_kind(policy: RadiiPolicy, *, expected: RadiiKind) -> None: diff --git a/src/atomref/registry.py b/src/atomref/registry.py index b17b941..e7c663f 100644 --- a/src/atomref/registry.py +++ b/src/atomref/registry.py @@ -1,4 +1,4 @@ -"""Dataset registry and packaged element-scalar set loading.""" +"""Dataset registry and packaged element-set loading.""" from __future__ import annotations @@ -7,10 +7,14 @@ import csv from functools import lru_cache from importlib import resources +import io import json import math +import stat from types import MappingProxyType import unicodedata +import zipfile +import zlib from .elements import canonicalize_element_symbol, get_element, iter_elements from .errors import DatasetError @@ -47,7 +51,7 @@ class Reference: @dataclass(frozen=True, slots=True) class CoverageInfo: - """Coverage summary for an element-indexed scalar dataset.""" + """Coverage summary for an element-indexed dataset.""" n_values: int z_min: int | None = None @@ -202,7 +206,35 @@ def get(self, symbol: str | None) -> float | None: return self.values_by_z[elem.z] -DatasetLike = DatasetRef | ElementScalarSet +@dataclass(frozen=True, slots=True) +class ElementRadialSet: + """Element-indexed radial profiles sampled on one shared grid.""" + + ref: DatasetRef + info: DatasetInfo + radii: tuple[float, ...] + profiles_by_z: tuple[tuple[float, ...] | None, ...] + + def get(self, element: str | int | None) -> tuple[float, ...] | None: + """Return the immutable profile for an element symbol or atomic number.""" + + if isinstance(element, int) and not isinstance(element, bool): + z = element + elif isinstance(element, str) or element is None: + sym = _normalize_element_domain_symbol(element) + elem = get_element(sym) + if elem is None: + return None + z = elem.z + else: + return None + if z <= 0 or z >= len(self.profiles_by_z): + return None + return self.profiles_by_z[z] + + +BuiltinSet = ElementScalarSet | ElementRadialSet +ScalarDatasetLike = DatasetRef | ElementScalarSet _DASH_TRANSLATION = str.maketrans( @@ -570,13 +602,159 @@ def _load_csv_columns(filename: str) -> dict[str, tuple[float | None, ...]]: @lru_cache(maxsize=None) -def get_builtin_set(ref: DatasetRef) -> ElementScalarSet: - """Load a packaged dataset as an :class:`ElementScalarSet`.""" +def _load_radial_csv_zip( + filename: str, + member: str, + radius_column: str, + density_columns: tuple[tuple[int, str], ...], +) -> tuple[tuple[float, ...], tuple[tuple[float, ...] | None, ...]]: + """Load shared-grid profiles from a single-member ZIP containing CSV.""" + + try: + archive_bytes = _read_package_data_bytes(filename) + with zipfile.ZipFile(io.BytesIO(archive_bytes), mode="r") as archive: + members = archive.infolist() + if len(members) != 1: + raise DatasetError( + f"radial ZIP archive {filename!r} must contain exactly one member" + ) + member_info = members[0] + if member_info.is_dir(): + raise DatasetError( + f"radial ZIP archive {filename!r} contains a directory entry" + ) + unix_mode = (member_info.external_attr >> 16) & 0xFFFF + file_type = stat.S_IFMT(unix_mode) + if ( + member_info.create_system == 3 + and file_type not in (0, stat.S_IFREG) + ): + raise DatasetError( + f"radial ZIP archive {filename!r} member is not a regular file" + ) + if member_info.filename != member: + raise DatasetError( + f"radial ZIP archive {filename!r} does not contain the declared " + f"member {member!r}" + ) + if member_info.flag_bits & 0x1: + raise DatasetError( + f"radial ZIP archive {filename!r} contains an encrypted member" + ) + csv_bytes = archive.read(member_info) + except DatasetError: + raise + except ( + OSError, + RuntimeError, + NotImplementedError, + zipfile.BadZipFile, + zipfile.LargeZipFile, + zlib.error, + ) as exc: + raise DatasetError(f"invalid radial ZIP archive: {filename!r}") from exc + + try: + csv_text = csv_bytes.decode("utf-8") + except UnicodeDecodeError as exc: + raise DatasetError( + f"invalid UTF-8 CSV member {member!r} in radial ZIP {filename!r}" + ) from exc + + radii: list[float] = [] + profiles: dict[int, list[float]] = {z: [] for z, _ in density_columns} + expected_columns = (radius_column, *(name for _, name in density_columns)) + try: + with io.StringIO(csv_text, newline="") as text_handle: + reader = csv.DictReader(text_handle) + if ( + reader.fieldnames is None + or tuple(reader.fieldnames) != expected_columns + ): + raise DatasetError( + f"invalid radial CSV columns in {member!r} from {filename!r}" + ) + for row_number, row in enumerate(reader, start=2): + radii.append( + _coerce_finite_float( + row.get(radius_column), + what=( + f"radius in {filename!r} row {row_number} " + f"column {radius_column!r}" + ), + ) + ) + for z, column in density_columns: + profiles[z].append( + _coerce_finite_float( + row.get(column), + what=( + f"profile value in {filename!r} row {row_number} " + f"column {column!r}" + ), + ) + ) + except DatasetError: + raise + except (csv.Error, ValueError) as exc: + raise DatasetError( + f"invalid radial CSV member {member!r} in {filename!r}" + ) from exc + + n_z = max(elem.z for elem in iter_elements()) + profiles_by_z: list[tuple[float, ...] | None] = [None] * (n_z + 1) + for z, values in profiles.items(): + if z <= 0 or z > n_z: + raise DatasetError(f"invalid atomic number in {filename!r}: {z}") + profiles_by_z[z] = tuple(values) + return tuple(radii), tuple(profiles_by_z) + + +def _read_package_data_bytes(filename: str) -> bytes: + """Read a packaged data resource without requiring a filesystem path.""" + + path = resources.files("atomref.data").joinpath(filename) + with path.open("rb") as handle: + return handle.read() + + +def _radial_density_columns(info: DatasetInfo) -> tuple[tuple[int, str], ...]: + """Return expected ``(Z, column)`` pairs from radial storage metadata.""" + + if not isinstance(info.storage, Mapping) or info.coverage is None: + raise DatasetError(f"invalid radial storage metadata for dataset: {info.ref!r}") + pattern = info.storage.get("density_column_pattern") + if not isinstance(pattern, str) or "{z" not in pattern: + raise DatasetError(f"invalid radial storage metadata for dataset: {info.ref!r}") + + coverage = info.coverage + if coverage.covered_z: + covered_z = coverage.covered_z + elif coverage.z_min is not None and coverage.z_max is not None: + covered_z = tuple(range(coverage.z_min, coverage.z_max + 1)) + else: + raise DatasetError( + f"invalid radial coverage metadata for dataset: {info.ref!r}" + ) + if len(covered_z) != coverage.n_values: + raise DatasetError( + f"invalid radial coverage metadata for dataset: {info.ref!r}" + ) + + try: + return tuple((z, pattern.format(z=z)) for z in covered_z) + except (KeyError, ValueError) as exc: + raise DatasetError( + f"invalid radial density-column pattern for dataset: {info.ref!r}" + ) from exc + + +def _load_element_scalar_set(info: DatasetInfo) -> ElementScalarSet: + """Load one dense-by-Z scalar CSV dataset.""" - info = get_dataset_info(ref) if info.domain != "element": raise DatasetError( - f"only element-domain datasets are currently supported: {info.ref!r}" + f"element scalar storage requires an element domain: {info.ref!r}" ) if not isinstance(info.storage, Mapping): raise DatasetError(f"missing storage metadata for dataset: {info.ref!r}") @@ -593,12 +771,82 @@ def get_builtin_set(ref: DatasetRef) -> ElementScalarSet: return ElementScalarSet(ref=info.ref, info=info, values_by_z=table[column]) -def resolve_dataset_like(dataset: DatasetLike) -> ElementScalarSet: - """Resolve either a packaged reference or a custom set to a loaded set.""" +def _load_element_radial_set(info: DatasetInfo) -> ElementRadialSet: + """Load one shared-grid radial CSV from a single-member ZIP archive.""" + + if info.domain != "element": + raise DatasetError( + f"element radial storage requires an element domain: {info.ref!r}" + ) + if not isinstance(info.storage, Mapping): + raise DatasetError(f"missing storage metadata for dataset: {info.ref!r}") + filename = info.storage.get("filename") + member = info.storage.get("member") + radius_column = info.storage.get("radius_column") + if ( + not isinstance(filename, str) + or not isinstance(member, str) + or not member + or not isinstance(radius_column, str) + ): + raise DatasetError(f"invalid radial storage metadata for dataset: {info.ref!r}") + + density_columns = _radial_density_columns(info) + radii, profiles_by_z = _load_radial_csv_zip( + filename, + member, + radius_column, + density_columns, + ) + return ElementRadialSet( + ref=info.ref, + info=info, + radii=radii, + profiles_by_z=profiles_by_z, + ) + + +@lru_cache(maxsize=None) +def _load_builtin_set(ref: DatasetRef) -> BuiltinSet: + """Load a canonical packaged dataset by its declared storage kind.""" + + info = get_dataset_info(ref) + if not isinstance(info.storage, Mapping): + raise DatasetError(f"missing storage metadata for dataset: {info.ref!r}") + storage_kind = info.storage.get("kind") + if storage_kind == "element_scalar_csv": + return _load_element_scalar_set(info) + if storage_kind == "element_radial_csv_zip": + return _load_element_radial_set(info) + raise DatasetError( + f"unknown storage kind {storage_kind!r} for dataset: {info.ref!r}" + ) + + +def get_builtin_set(ref: DatasetRef) -> BuiltinSet: + """Load a scalar or radial packaged dataset through the shared registry.""" + + canonical_ref = get_dataset_info(ref).ref + return _load_builtin_set(canonical_ref) + + +def resolve_scalar_dataset_like( + dataset: ScalarDatasetLike | ElementRadialSet, +) -> ElementScalarSet: + """Resolve a packaged reference or custom set, requiring scalar payload.""" if isinstance(dataset, ElementScalarSet): return dataset - return get_builtin_set(dataset) + if isinstance(dataset, ElementRadialSet): + raise DatasetError( + f"dataset {dataset.ref!r} has a radial payload; scalar dataset required" + ) + loaded = get_builtin_set(dataset) + if not isinstance(loaded, ElementScalarSet): + raise DatasetError( + f"dataset {loaded.ref!r} has a radial payload; scalar dataset required" + ) + return loaded def _is_placeholder_value(info: DatasetInfo, value: float) -> bool: diff --git a/src/atomref/transfer.py b/src/atomref/transfer.py index 9adb0ce..62b5724 100644 --- a/src/atomref/transfer.py +++ b/src/atomref/transfer.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Literal, Protocol, runtime_checkable from .errors import PolicyError -from .registry import DatasetLike +from .registry import ScalarDatasetLike if TYPE_CHECKING: # pragma: no cover - typing only from .policy import ValuePolicy @@ -74,7 +74,7 @@ class SubstitutionTransfer: The selected value is copied from the source rather than inferred. """ - source: DatasetLike | SupportsValuePolicy | ValuePolicy[str] + source: ScalarDatasetLike | SupportsValuePolicy | ValuePolicy[str] @dataclass(frozen=True, slots=True) @@ -96,7 +96,9 @@ class LinearTransfer: enough to allow one additional completion step at prediction time. """ - predictors: tuple[DatasetLike | SupportsValuePolicy | ValuePolicy[str], ...] + predictors: tuple[ + ScalarDatasetLike | SupportsValuePolicy | ValuePolicy[str], ... + ] min_points: int = 2 exclude_placeholders: bool = True fit_sources: tuple[TransferValueSource, ...] = _DEFAULT_LINEAR_FIT_SOURCES diff --git a/src/atomref/xh.py b/src/atomref/xh.py index 5018d99..29abf2c 100644 --- a/src/atomref/xh.py +++ b/src/atomref/xh.py @@ -18,10 +18,10 @@ DatasetInfo, DatasetRef, ElementScalarSet, - get_builtin_set, get_dataset_info, list_dataset_ids, list_dataset_infos, + resolve_scalar_dataset_like, ) from .transfer import LinearTransfer, TransferModel @@ -129,7 +129,7 @@ def get_xh_set_info(set_id: str) -> DatasetInfo: def get_xh_set(set_id: str) -> XHSet: """Load one packaged X-H set as an :class:`ElementScalarSet`.""" - return get_builtin_set(DatasetRef(_QUANTITY, set_id)) + return resolve_scalar_dataset_like(DatasetRef(_QUANTITY, set_id)) def lookup_xh_bond_length( diff --git a/tests/meta/test_public_api.py b/tests/meta/test_public_api.py index f3583a1..17a81c9 100644 --- a/tests/meta/test_public_api.py +++ b/tests/meta/test_public_api.py @@ -4,9 +4,11 @@ REQUIRED_PUBLIC_NAMES = { + 'BuiltinSet', 'Element', 'DatasetRef', 'DatasetInfo', + 'ElementRadialSet', 'ElementScalarSet', 'QuantityInfo', 'LookupResult', diff --git a/tests/meta/test_registry_integrity.py b/tests/meta/test_registry_integrity.py index a32b44c..ebe72e6 100644 --- a/tests/meta/test_registry_integrity.py +++ b/tests/meta/test_registry_integrity.py @@ -8,6 +8,18 @@ _ALLOWED_USAGE_ROLES = {"target", "support"} +_PRE_STAGE_2_SCALAR_REFS = ( + ar.DatasetRef("covalent_radius", "cordero2008"), + ar.DatasetRef("covalent_radius", "csd_legacy_cov"), + ar.DatasetRef("van_der_waals_radius", "bondi1964"), + ar.DatasetRef("van_der_waals_radius", "rowland_taylor1996"), + ar.DatasetRef("van_der_waals_radius", "alvarez2013"), + ar.DatasetRef("van_der_waals_radius", "chernyshov2020"), + ar.DatasetRef("van_der_waals_radius", "csd_legacy_vdw"), + ar.DatasetRef("atomic_radius", "rahm2016"), + ar.DatasetRef("xh_bond_length", "csd_legacy_xh_cno"), +) + def test_dataset_aliases_are_unique_within_each_quantity() -> None: for quantity in ar.list_quantities(): @@ -35,21 +47,32 @@ def test_every_built_in_dataset_loads_and_matches_coverage_metadata() -> None: assert info.references assert info.coverage is not None + if isinstance(dataset, ar.ElementScalarSet): + values_by_z = dataset.values_by_z + else: + assert isinstance(dataset, ar.ElementRadialSet) + values_by_z = dataset.profiles_by_z + max_z = ( info.coverage.z_max if info.coverage.z_max is not None - else len(dataset.values_by_z) - 1 + else len(values_by_z) - 1 ) covered_z = tuple( z - for z, value in enumerate(dataset.values_by_z) + for z, value in enumerate(values_by_z) if z > 0 and value is not None and z <= max_z ) covered_set = set(covered_z) missing_z = tuple(z for z in range(1, max_z + 1) if z not in covered_set) - has_placeholders = info.placeholder_value is not None and any( - value is not None and abs(value - info.placeholder_value) < 1e-12 - for value in dataset.values_by_z[1 : max_z + 1] + has_placeholders = ( + isinstance(dataset, ar.ElementScalarSet) + and info.placeholder_value is not None + and any( + value is not None + and abs(value - info.placeholder_value) < 1e-12 + for value in values_by_z[1 : max_z + 1] + ) ) coverage = asdict(info.coverage) @@ -63,6 +86,11 @@ def test_every_built_in_dataset_loads_and_matches_coverage_metadata() -> None: assert tuple(coverage["missing_z"]) == missing_z +def test_pre_stage_2_packaged_datasets_remain_scalar() -> None: + for ref in _PRE_STAGE_2_SCALAR_REFS: + assert isinstance(get_builtin_set(ref), ar.ElementScalarSet) + + def test_non_atomic_quantities_have_at_least_one_target_dataset() -> None: by_role: dict[str, list[str]] = defaultdict(list) for quantity in ar.list_quantities(): diff --git a/tests/registry/test_registry.py b/tests/registry/test_registry.py index d497d9f..e9677f9 100644 --- a/tests/registry/test_registry.py +++ b/tests/registry/test_registry.py @@ -1,14 +1,140 @@ from __future__ import annotations +from dataclasses import FrozenInstanceError from importlib import resources +import io from types import MappingProxyType +import warnings +import zipfile import pytest import atomref as ar from atomref.errors import DatasetError +import atomref.registry as registry from atomref.registry import get_builtin_set +_RADIAL_ARCHIVE = 'synthetic_radial.zip' +_RADIAL_MEMBER = 'proatomic_density_neutral.csv' +_RADIAL_CSV = b'r_bohr,z001,z008\n0.0,1.0,8.0\n0.5,0.5,4.0\n' + + +def _make_zip( + entries: tuple[tuple[str, bytes], ...], + *, + external_attr: int = 0o100644 << 16, +) -> bytes: + buffer = io.BytesIO() + with zipfile.ZipFile( + buffer, + mode='w', + compression=zipfile.ZIP_DEFLATED, + compresslevel=9, + ) as archive: + with warnings.catch_warnings(): + warnings.simplefilter('ignore', UserWarning) + for name, payload in entries: + member = zipfile.ZipInfo( + filename=name, + date_time=(1980, 1, 1, 0, 0, 0), + ) + member.compress_type = zipfile.ZIP_DEFLATED + member.create_system = 3 + member.external_attr = external_attr + member.internal_attr = 0 + member.extra = b'' + member.comment = b'' + archive.writestr( + member, + payload, + compress_type=zipfile.ZIP_DEFLATED, + compresslevel=9, + ) + archive.comment = b'' + return buffer.getvalue() + + +def _mark_zip_member_encrypted(archive_bytes: bytes) -> bytes: + encrypted = bytearray(archive_bytes) + for signature, flag_offset in ((b'PK\x03\x04', 6), (b'PK\x01\x02', 8)): + start = encrypted.index(signature) + flag_offset + flags = int.from_bytes(encrypted[start : start + 2], 'little') | 0x1 + encrypted[start : start + 2] = flags.to_bytes(2, 'little') + return bytes(encrypted) + + +def _corrupt_deflated_member_data(archive_bytes: bytes) -> bytes: + corrupted = bytearray(archive_bytes) + with zipfile.ZipFile(io.BytesIO(archive_bytes), mode='r') as archive: + member = archive.infolist()[0] + header = member.header_offset + name_length = int.from_bytes(corrupted[header + 26 : header + 28], 'little') + extra_length = int.from_bytes(corrupted[header + 28 : header + 30], 'little') + data_start = header + 30 + name_length + extra_length + corrupted[data_start] = (corrupted[data_start] & 0xF8) | 0x07 + return bytes(corrupted) + + +@pytest.fixture +def install_synthetic_radial_registry(monkeypatch: pytest.MonkeyPatch): + real_registry_loader = registry._load_registry_json + + def clear_caches() -> None: + registry._load_builtin_set.cache_clear() + registry._load_radial_csv_zip.cache_clear() + registry._load_csv_columns.cache_clear() + real_registry_loader.cache_clear() + + def install(archive_bytes: bytes, *, member: str = _RADIAL_MEMBER) -> None: + raw_registry = { + 'quantities': { + 'synthetic_profile': { + 'domain': 'element', + 'units': 'arbitrary', + 'description': 'Synthetic radial profiles for loader tests.', + } + }, + 'datasets': { + 'synthetic_profile': { + 'synthetic': { + 'name': 'Synthetic radial profiles', + 'aliases': ['synthetic alias'], + 'storage': { + 'kind': 'element_radial_csv_zip', + 'filename': _RADIAL_ARCHIVE, + 'member': member, + 'radius_column': 'r_bohr', + 'density_column_pattern': 'z{z:03d}', + }, + 'coverage': { + 'n_values': 2, + 'z_min': 1, + 'z_max': 8, + 'covered_z': [1, 8], + }, + } + } + }, + } + + def read_package_data_bytes(filename: str) -> bytes: + assert filename == _RADIAL_ARCHIVE + return archive_bytes + + clear_caches() + monkeypatch.setattr(registry, '_load_registry_json', lambda: raw_registry) + monkeypatch.setattr( + registry, + '_read_package_data_bytes', + read_package_data_bytes, + ) + + clear_caches() + try: + yield install + finally: + clear_caches() + def test_packaged_data_files_exist() -> None: pkg = 'atomref.data' @@ -33,9 +159,201 @@ def test_rahm_is_registered_as_atomic_radius() -> None: def test_builtin_set_loading_works() -> None: ds = get_builtin_set(ar.DatasetRef('covalent_radius', 'cordero2008')) + assert isinstance(ds, ar.ElementScalarSet) assert ds.get('C') == 0.76 +def test_aliases_resolve_before_builtin_set_caching() -> None: + canonical = get_builtin_set(ar.DatasetRef('covalent_radius', 'cordero2008')) + alias = get_builtin_set( + ar.DatasetRef('covalent_radius', 'Cordero-Alvarez covalent radii') + ) + assert alias is canonical + + +def test_synthetic_radial_set_uses_generic_registry_and_loader( + install_synthetic_radial_registry, +) -> None: + archive_bytes = _make_zip(((_RADIAL_MEMBER, _RADIAL_CSV),)) + with zipfile.ZipFile(io.BytesIO(archive_bytes), mode='r') as archive: + assert archive.comment == b'' + assert len(archive.infolist()) == 1 + member = archive.infolist()[0] + assert member.filename == _RADIAL_MEMBER + assert member.date_time == (1980, 1, 1, 0, 0, 0) + assert member.compress_type == zipfile.ZIP_DEFLATED + assert member.create_system == 3 + assert member.external_attr == 0o100644 << 16 + assert member.internal_attr == 0 + assert member.extra == b'' + assert member.comment == b'' + install_synthetic_radial_registry(archive_bytes) + + assert ar.list_quantities() == ('synthetic_profile',) + quantity_info = ar.get_quantity_info('synthetic_profile') + assert quantity_info.domain == 'element' + assert ar.list_dataset_ids('synthetic_profile') == ('synthetic',) + assert tuple( + item.ref.set_id for item in ar.list_dataset_infos('synthetic_profile') + ) == ('synthetic',) + info = ar.get_dataset_info(ar.DatasetRef('synthetic_profile', 'synthetic alias')) + assert info.storage is not None + assert info.storage['kind'] == 'element_radial_csv_zip' + assert info.storage['member'] == _RADIAL_MEMBER + + via_alias = get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic alias')) + dataset = get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + assert isinstance(dataset, ar.ElementRadialSet) + assert via_alias is dataset + assert dataset.radii == (0.0, 0.5) + assert dataset.get('H') == (1.0, 0.5) + assert dataset.get(8) == (8.0, 4.0) + assert dataset.get('He') is None + + +def test_radial_zip_rejects_missing_declared_member( + install_synthetic_radial_registry, +) -> None: + install_synthetic_radial_registry( + _make_zip((('different.csv', _RADIAL_CSV),)) + ) + with pytest.raises(DatasetError, match='does not contain the declared member'): + get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + + +def test_radial_zip_accepts_member_without_file_type_bits( + install_synthetic_radial_registry, +) -> None: + archive_bytes = _make_zip( + ((_RADIAL_MEMBER, _RADIAL_CSV),), + external_attr=0o600 << 16, + ) + install_synthetic_radial_registry(archive_bytes) + dataset = get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + assert isinstance(dataset, ar.ElementRadialSet) + assert dataset.get('H') == (1.0, 0.5) + + +def test_radial_zip_rejects_unexpected_additional_member( + install_synthetic_radial_registry, +) -> None: + install_synthetic_radial_registry( + _make_zip(((_RADIAL_MEMBER, _RADIAL_CSV), ('extra.txt', b'extra'))) + ) + with pytest.raises(DatasetError, match='must contain exactly one member'): + get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + + +def test_radial_zip_rejects_duplicate_member_names( + install_synthetic_radial_registry, +) -> None: + install_synthetic_radial_registry( + _make_zip(((_RADIAL_MEMBER, _RADIAL_CSV), (_RADIAL_MEMBER, _RADIAL_CSV))) + ) + with pytest.raises(DatasetError, match='must contain exactly one member'): + get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + + +def test_radial_zip_rejects_directory_entry( + install_synthetic_radial_registry, +) -> None: + directory = 'proatomic_density_neutral.csv/' + install_synthetic_radial_registry( + _make_zip(((directory, b''),)), + member=directory, + ) + with pytest.raises(DatasetError, match='contains a directory entry'): + get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + + +def test_radial_zip_rejects_malformed_archive( + install_synthetic_radial_registry, +) -> None: + install_synthetic_radial_registry(b'not a ZIP archive') + with pytest.raises(DatasetError, match='invalid radial ZIP archive'): + get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + + +def test_radial_zip_wraps_corrupted_deflated_member( + install_synthetic_radial_registry, +) -> None: + archive_bytes = _make_zip(((_RADIAL_MEMBER, _RADIAL_CSV),)) + install_synthetic_radial_registry( + _corrupt_deflated_member_data(archive_bytes) + ) + with pytest.raises(DatasetError, match='invalid radial ZIP archive'): + get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + + +def test_radial_zip_rejects_encrypted_member( + install_synthetic_radial_registry, +) -> None: + archive_bytes = _make_zip(((_RADIAL_MEMBER, _RADIAL_CSV),)) + install_synthetic_radial_registry(_mark_zip_member_encrypted(archive_bytes)) + with pytest.raises(DatasetError, match='contains an encrypted member'): + get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + + +def test_radial_zip_rejects_invalid_csv_columns( + install_synthetic_radial_registry, +) -> None: + invalid_csv = b'radius,z001,z008\n0.0,1.0,8.0\n' + install_synthetic_radial_registry( + _make_zip(((_RADIAL_MEMBER, invalid_csv),)) + ) + with pytest.raises(DatasetError, match='invalid radial CSV columns'): + get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + + +def test_radial_set_values_are_immutable( + install_synthetic_radial_registry, +) -> None: + install_synthetic_radial_registry( + _make_zip(((_RADIAL_MEMBER, _RADIAL_CSV),)) + ) + dataset = get_builtin_set(ar.DatasetRef('synthetic_profile', 'synthetic')) + assert isinstance(dataset, ar.ElementRadialSet) + assert isinstance(dataset.radii, tuple) + assert isinstance(dataset.get('H'), tuple) + with pytest.raises(FrozenInstanceError): + dataset.radii = (1.0,) + + +def test_unknown_storage_kind_raises_dataset_error( + monkeypatch: pytest.MonkeyPatch, +) -> None: + ref = ar.DatasetRef('synthetic', 'unknown_storage') + info = ar.DatasetInfo( + ref=ref, + domain='element', + units=None, + name='Unknown storage', + storage=MappingProxyType({'kind': 'mystery'}), + ) + monkeypatch.setattr(registry, 'get_dataset_info', lambda requested: info) + registry._load_builtin_set.cache_clear() + with pytest.raises(DatasetError, match='unknown storage kind'): + get_builtin_set(ref) + + +def test_scalar_policy_rejects_radial_set_clearly() -> None: + ref = ar.DatasetRef('synthetic_profile', 'synthetic') + info = ar.DatasetInfo( + ref=ref, + domain='element', + units='arbitrary', + name='Synthetic radial profiles', + ) + radial = ar.ElementRadialSet( + ref=ref, + info=info, + radii=(0.0,), + profiles_by_z=(None, (1.0,)), + ) + with pytest.raises(DatasetError, match='radial payload; scalar dataset required'): + ar.ValuePolicy(base=radial) + + def test_list_quantities_and_quantity_info() -> None: quantities = ar.list_quantities() assert quantities == ( diff --git a/tools/check_registry.py b/tools/check_registry.py index 3af6025..a166a8b 100644 --- a/tools/check_registry.py +++ b/tools/check_registry.py @@ -16,6 +16,7 @@ sys.path.insert(0, str(SRC)) _ALLOWED_USAGE_ROLES = {"target", "support"} +_ALLOWED_STORAGE_KINDS = {"element_scalar_csv", "element_radial_csv_zip"} def _load_atomref_module(): @@ -96,37 +97,67 @@ def _validate_dataset_metadata(errors: list[str]) -> None: if info.storage is None: errors.append(f"missing storage metadata for {ref!r}") else: + kind = info.storage.get("kind") filename = info.storage.get("filename") - column = info.storage.get("column") - fmt = info.storage.get("format") + if kind not in _ALLOWED_STORAGE_KINDS: + errors.append(f"unsupported storage kind for {ref!r}: {kind!r}") if not isinstance(filename, str) or not filename: errors.append(f"invalid storage filename for {ref!r}: {filename!r}") - if not isinstance(column, str) or not column: - errors.append(f"invalid storage column for {ref!r}: {column!r}") - if fmt != "dense_by_z_csv": - errors.append(f"unsupported storage format for {ref!r}: {fmt!r}") + if kind == "element_scalar_csv": + column = info.storage.get("column") + fmt = info.storage.get("format") + if not isinstance(column, str) or not column: + errors.append(f"invalid storage column for {ref!r}: {column!r}") + if fmt != "dense_by_z_csv": + errors.append(f"unsupported storage format for {ref!r}: {fmt!r}") + elif kind == "element_radial_csv_zip": + member = info.storage.get("member") + radius_column = info.storage.get("radius_column") + density_pattern = info.storage.get("density_column_pattern") + if not isinstance(member, str) or not member: + errors.append( + f"invalid radial archive member for {ref!r}: {member!r}" + ) + if not isinstance(radius_column, str) or not radius_column: + errors.append( + f"invalid radial radius column for {ref!r}: {radius_column!r}" + ) + if not isinstance(density_pattern, str) or "{z" not in density_pattern: + errors.append( + f"invalid radial density-column pattern for {ref!r}: " + f"{density_pattern!r}" + ) + values_by_z = ( + dataset.values_by_z + if isinstance(dataset, ar.ElementScalarSet) + else dataset.profiles_by_z + ) coverage = info.coverage if coverage is None: errors.append(f"missing coverage metadata for {ref!r}") - max_z = len(dataset.values_by_z) - 1 + max_z = len(values_by_z) - 1 else: max_z = ( coverage.z_max if coverage.z_max is not None - else len(dataset.values_by_z) - 1 + else len(values_by_z) - 1 ) covered_z = tuple( z - for z, value in enumerate(dataset.values_by_z) + for z, value in enumerate(values_by_z) if z > 0 and value is not None and z <= max_z ) covered_set = set(covered_z) missing_z = tuple(z for z in range(1, max_z + 1) if z not in covered_set) - has_placeholders = info.placeholder_value is not None and any( - value is not None and abs(value - info.placeholder_value) < 1e-12 - for value in dataset.values_by_z[1 : max_z + 1] + has_placeholders = ( + isinstance(dataset, ar.ElementScalarSet) + and info.placeholder_value is not None + and any( + value is not None and abs(value - info.placeholder_value) < 1e-12 + for value in dataset.values_by_z[1 : max_z + 1] + ) ) if coverage is not None: From 1c78d1933ccdbed68fdbd2e402275965cff9d35b Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Sun, 12 Jul 2026 22:05:43 +0300 Subject: [PATCH 03/23] feat(data): add neutral proatomic density snapshot --- DEV_PLAN.md | 33 +- NOTICE.md | 22 + README.md | 2 +- docs/datasets/index.md | 21 + docs/guide/quickstart.md | 2 +- docs/index.md | 2 +- docs/notebooks/01-quickstart.md | 2 +- pyproject.toml | 1 + .../data/proatomic_density_neutral.zip | Bin 0 -> 1079951 bytes src/atomref/data/registry.json | 82 +++ tests/meta/test_package_data.py | 24 + tests/meta/test_release_tools.py | 64 ++ tests/proatoms/test_dataset.py | 181 ++++++ tests/proatoms/test_snapshot_builder.py | 174 ++++++ tests/registry/test_registry.py | 18 +- tools/README.md | 23 + tools/build_proatomic_density_snapshot.py | 563 ++++++++++++++++++ tools/check_dist.py | 152 ++++- tools/check_registry.py | 185 +++++- 19 files changed, 1517 insertions(+), 34 deletions(-) create mode 100644 src/atomref/data/proatomic_density_neutral.zip create mode 100644 tests/proatoms/test_dataset.py create mode 100644 tests/proatoms/test_snapshot_builder.py create mode 100644 tools/build_proatomic_density_snapshot.py diff --git a/DEV_PLAN.md b/DEV_PLAN.md index 6166462..2d40ae4 100644 --- a/DEV_PLAN.md +++ b/DEV_PLAN.md @@ -2301,28 +2301,28 @@ chore: prepare atomref 0.2.1 - [x] Runtime dependencies remain empty. - [x] Runtime performs no network access. - [x] No ionic or profile-correlation behavior is exposed. -- [ ] `get_builtin_set()` loads every packaged scalar and radial dataset. +- [x] `get_builtin_set()` loads every packaged scalar and radial dataset. - [x] Scalar policy code rejects radial sets clearly. - [x] Registry discovery and metadata work identically across payload kinds. ### 14.2 `0.2.0` data -- [ ] Source profiles hash matches the pinned `profiles.csv`. -- [ ] Source metadata hash matches the pinned `metadata.json`. -- [ ] Upstream dataset ID and version match. -- [ ] Basis ID and basis hash match. -- [ ] Exactly 103 neutral profiles are selected. -- [ ] Coverage is exactly Z=1..103. -- [ ] Retained row count is 1127. -- [ ] Public limit is exactly 20 bohr. -- [ ] First retained point above 20 bohr brackets the endpoint. -- [ ] All retained densities are finite and positive. -- [ ] All retained profiles are monotone non-increasing within tolerance. -- [ ] ZIP output satisfies the deterministic single-member contract in Section +- [x] Source profiles hash matches the pinned `profiles.csv`. +- [x] Source metadata hash matches the pinned `metadata.json`. +- [x] Upstream dataset ID and version match. +- [x] Basis ID and basis hash match. +- [x] Exactly 103 neutral profiles are selected. +- [x] Coverage is exactly Z=1..103. +- [x] Retained row count is 1127. +- [x] Public limit is exactly 20 bohr. +- [x] First retained point above 20 bohr brackets the endpoint. +- [x] All retained densities are finite and positive. +- [x] All retained profiles are monotone non-increasing within tolerance. +- [x] ZIP output satisfies the deterministic single-member contract in Section 6.6. -- [ ] Snapshot is built by the maintainer tool, not hand-edited. -- [ ] Wheel and sdist contain `proatomic_density_neutral.zip`. -- [ ] Registry and notice contain CC BY attribution and exact DOIs. +- [x] Snapshot is built by the maintainer tool, not hand-edited. +- [x] Wheel and sdist contain `proatomic_density_neutral.zip`. +- [x] Registry and notice contain CC BY attribution and exact DOIs. ### 14.3 `0.2.0` density API @@ -2746,4 +2746,3 @@ Disposition: Review notes: - ... ``` - diff --git a/NOTICE.md b/NOTICE.md index 01f1cf1..f1a9b1e 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -10,3 +10,25 @@ License: LGPL-3.0-or-later (see LICENSE and COPYING) The initial scaffold reuses and adapts data tables and design ideas from the Delone Commons `molcryst` repository, also authored by Ivan Chernyshov. + +### atomref-proatoms data + +`proatomic_density_neutral.zip` is a neutral H–Lr (Z=1–103), 20-bohr-truncated +consumer snapshot derived from: + +Ivan Yu. Chernyshov, *atomref-proatoms: spherical atomic and ionic reference +densities*, release 2.0.0, dataset +`pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`. + +The released source data and metadata are licensed under Creative Commons +Attribution 4.0 International (CC BY 4.0): +https://creativecommons.org/licenses/by/4.0/legalcode + +- Concept DOI: 10.5281/zenodo.21291021 +- Version DOI for the immutable 2.0.0 archive: 10.5281/zenodo.21291022 +- Source repository: https://github.com/DeloneCommons/atomref-proatoms + +atomref selected the 103 neutral profiles, retained source rows through the +first grid point above 20 bohr for endpoint bracketing, and repackaged them as a +deterministic single-member ZIP. This data license is separate from atomref's +LGPL-3.0-or-later code license. diff --git a/README.md b/README.md index c77838b..aa357f4 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ You can inspect the packaged quantity and dataset catalog directly: ```pycon >>> import atomref as ar >>> ar.list_quantities() -('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length') +('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') >>> ar.get_quantity_info("xh_bond_length") QuantityInfo(quantity='xh_bond_length', domain='element', units='angstrom', description='Element-indexed reference X-H bond lengths keyed by parent element X and intended for hydrogen-position normalisation or related geometry workflows.') >>> [info.ref.set_id for info in ar.list_dataset_infos("van_der_waals_radius", usage_role="target")] diff --git a/docs/datasets/index.md b/docs/datasets/index.md index d3b2951..739ee4d 100644 --- a/docs/datasets/index.md +++ b/docs/datasets/index.md @@ -30,9 +30,30 @@ or `list_xh_sets(...)`. If you want the packaged values themselves, use `get_builtin_set(...)`, `get_radii_set(...)`, or `get_xh_set(...)`. +## Neutral proatomic-density snapshot + +The `proatomic_density` quantity currently contains the dataset +`pbe0_sfx2c_dyallv4z_h-lr_neutral_v2`: a neutral H–Lr (Z = 1–103), truncated +consumer snapshot built from `atomref-proatoms` 2.0.0 dataset +`pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`. Its public radial domain is 0–20 bohr; +one original source point above 20 bohr is retained only to bracket that limit. +The native density unit is electron/bohr³. + +The profiles use PBE0, self-consistent spherical fractional-occupation UKS, +spin-free one-electron X2C, and the dyall-v4z basis. These are method-, basis-, +state-, and sphericalization-defined reference densities, not unique +basis-independent atomic observables. Detailed state and generation metadata +remain in the exact upstream 2.0.0 archive. + +The imported data are licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), +separately from the `atomref` code license. Cite both the +[atomref-proatoms concept DOI](https://doi.org/10.5281/zenodo.21291021) and the +[version-specific 2.0.0 DOI](https://doi.org/10.5281/zenodo.21291022). + ## Built-in quantity families - [Covalent radius](covalent_radius.md) - [van der Waals radius](van_der_waals_radius.md) - [Atomic radius](atomic_radius.md) - [X–H bond length](xh_bond_length.md) +- Proatomic density diff --git a/docs/guide/quickstart.md b/docs/guide/quickstart.md index 72e6858..fc9d629 100644 --- a/docs/guide/quickstart.md +++ b/docs/guide/quickstart.md @@ -31,7 +31,7 @@ You can inspect the packaged quantity layer directly: ```pycon >>> import atomref as ar >>> ar.list_quantities() -('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length') +('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') >>> ar.get_quantity_info("xh_bond_length") QuantityInfo(quantity='xh_bond_length', domain='element', units='angstrom', description='Element-indexed reference X-H bond lengths keyed by parent element X and intended for hydrogen-position normalisation or related geometry workflows.') >>> [info.ref.set_id for info in ar.list_radii_set_infos("van_der_waals", usage_role="target")] diff --git a/docs/index.md b/docs/index.md index 198fa6a..0b9f2e4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -103,7 +103,7 @@ You can inspect the packaged quantity and dataset catalog directly: ```pycon >>> import atomref as ar >>> ar.list_quantities() -('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length') +('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') >>> ar.get_quantity_info("xh_bond_length") QuantityInfo(quantity='xh_bond_length', domain='element', units='angstrom', description='Element-indexed reference X-H bond lengths keyed by parent element X and intended for hydrogen-position normalisation or related geometry workflows.') >>> [info.ref.set_id for info in ar.list_dataset_infos("van_der_waals_radius", usage_role="target")] diff --git a/docs/notebooks/01-quickstart.md b/docs/notebooks/01-quickstart.md index 12e8813..12529e0 100644 --- a/docs/notebooks/01-quickstart.md +++ b/docs/notebooks/01-quickstart.md @@ -15,7 +15,7 @@ print(ar.list_quantities()) **Output** ```text Element(z=17, symbol='Cl', name='Chlorine') -('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length') +('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') ``` ```python r_c = ar.get_covalent_radius('C') diff --git a/pyproject.toml b/pyproject.toml index b712101..fea1cba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ packages = ["src/atomref"] include = [ "src/atomref/data/*.csv", "src/atomref/data/*.json", + "src/atomref/data/*.zip", ] [tool.hatch.build.targets.sdist] diff --git a/src/atomref/data/proatomic_density_neutral.zip b/src/atomref/data/proatomic_density_neutral.zip new file mode 100644 index 0000000000000000000000000000000000000000..5df06e5124c95bb75d860e6ff5ef612087f5e795 GIT binary patch literal 1079951 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

fA%HE@>`;C} z+$Vvp^F@1FAoUwEu?QLn?w+`0NU&3H9j5H0{Ai`c&$yV*N+#m+X-IFRQ&Yi zgOy4H^ID{TI4-EfGOjme7?b0l)L`(s5@6F8*bfL^Kiq^i`yw1+UOUvS`|b|7nb+bh z3zOue442^bL!ks|Ox&N~wf3w|kxAiF4_@n&Wd-R)lKBr_v*KK*f!Ue7eulFVMDJPx zo4OMG9i4VPfQ_}G57g5oRujOMrH7YBwkGn}gfMjX^25aGV`3ZP_*;cz-Nb%KoG(2F z7Lk!H!rPB|f|N#f!|c++=KL>#>_zsz_?+q{HrXE1-`wX(2U{lr!A_Z4%|{-@CSovI zu)0x*O(coZhfUaW{0Cw`1Rj2oP1aIYMEgZ{g&&SUTxM%C=y+n$U1sZb>X_d|yk2PQ zvC4hbwtb%|!O*tlKPFJ(^%PZp}jqj>um! zn}Z9_tJJJM<5;)(BEM+8(AIzH=+$1i)n}U@t#ypcaiOjMIDD_CeW6|KP3v?avJ}wn zWFt5-Tf5Mf6DE}C-JKf~H%_%l35#E7i`iO!+Epc)O_FxwPf=hI%%(1^P_{y;Niv&o z$gnZiFS8#f$#sP3tb*B%j3p%meB()GW3TD!NmBI7>_VVh^l0^Uu(f+Qd9;@IWwr>^ z?32!ZnN3NdUaxGca+$q#Q#vmLIRS0GZk>eW-1~*Lj(P%6lgNpotqZmDgasVj3++k9 zQ#;sBcR)LgoTio2FSO-aXip7X%b@o25khhP2+#zz<--y6(Yd(B>tf?+ECNkWztk2a zx^PDxDX6_aRBtqVl?;j#+mvU4y|FSV-aqVE?Q+?#BcQF{Q~gBPX#s871TsR{^13>p zEgCnN(r_IE+76Qla1)RQw22sFvgu{YT@PqCtwOyOY}%F3CIkYI35zGRUGAiPCH!7! z+nvEQGE59@IeNN`JpM>0TRT~&pZ+Duu7tLHkV1iArxq-rEz3ctIeF%iY3zZOqi0Kr z=tyX@3@K36|8EIx{SgFh&ZQdK+I%`JH?eYDYM@OTLCcT0a?3n6S!HpW<9?5T_I7y~ z&xX{tsZD8ylh%X!7ekvuw(fXzEdlKamGHczX*LG30x zboKBUS5kWt10yx#vNcnCV&urgE5wXUZO$(p3e*(nCW6}H$p8piU>O!Co~{E+1fArg zOzp-|uBR|c7N6-RWmQ4ykxsTarH48yL5UU2*80M{6r6P1!R*n-hvFk5r8}AJ6cs^N zqE;Y#6Xp@HUHxQc>&0|}6k3EjC4$+w@T3T;y>m0WE(#3^r%{$`X6uVSUX(ca0d4KX zkIM}>;{)2A7|76v5q1b@Q#|;gyhiJ~0`1eBDAf1i+5H<9#|)HN_7}9Z9N9!Kw256j zhdTH#v}FNM&G3`QCQa^?{1@7kqH(I#A82FR=tZ6{wcFwkslFC2JmGa*YV(R+bx46=P`gw9PKm*KtIxECO#6Mxr8XxOY4SSWOg14f?L94@ z3+-oUt?_opWp<@VXA)9-W8#FvyTgmK7%9p}*0`m!Nq6j3^Nd_jg z^$UtCIrq5G)|#Yhywn%k+JPATBrZxqTMJPDC_wHevYXxr{*r#vjfy{zjP2LQW%gq; z?wPjjgV`)2eYLFna+%HM)c?)4yUbo(FC;pG8_b^GW-dOv%zo%s@TP8;+0$i1S`kzq z2eZjNuu%6UHfHHt2vg0Bh|53ZgGpM9s5+yf zVAC5QW3r}?&3mTaOGnShB=*{*O_vYLnYbqEk3+&+O*0%ZWoNOtRy<)kicv|` zf$PDp)-Tn$hDB;zH!A5HH8ZOuuBpEoR8MzKaN` zC2UD+d`1~U4-}|DYpSiQ7B}{6y4WsQ(9Yr9&02B&k@kitCtD(O*wh6rLV@&61g*6R zgk_?*XQz+dspKL~N-R5YjUfgpAE7y8;;bwk0$Aa?3FCSYJrAC%9a6N6- zQ}MlPI@yghGgZOMh0VsbzLLm!C&-?-Cci?_Gy#spH3UJ8MLAz2m|xT0b#C{%ntYmp<0|&9iSwUQ<6^1|%o42O5k3q3BFwQq>#jIWs z{g>GK;7V7_OYBKZb+gi6Vpklf;dt2vHXeZ<;Q0c(x}~RKLS10X1(_7qy)9lkV)f|_ z!{r6GzF%hwum1~dYA~n^C@-)F$sN7w=e)qyF2Z&##NGnfph_#B63rLbTFQiR_w$Cv zt8)@^UC+6|*2%6T$FcnjY*{y*f;YfxV(XZ1*kCTM4`2(`-6eq)Z#jTXfb5WjcX53H zn?3`QMEp?zTaO5TP#?EUY>pGbhZ>LZjg9LLD9h$iTx9D2q=t;&aFIJuzM|ASqnGBx*7uS?YlJOZgEZ)d`YYmEe z5f)#>nkW;M%aF8&RB{)z0USFCT0g{si8_rph22S-xL)b?u<`nFx@gl%TI(RwA})MI z(E1@5xOn75hmzKvp07rBe_mS4?d;G|4rTDVeoR5?gp=1p{*~@mhRq^z8oZXtzG)ZZ z*f6lEC(!1yGgL3JISwhek$HHDO+B+XB`2X=WXnHw7PGy?=G>>ChOD27-6)Wt%J$$h zB(W)9mv1yMxJ_(X`T8VmBw89wY`W+u+^2QJz;^lXleMrTfi17n-GkH%2C&VkMVj6` zFt91ykV}BenN?@FSCu+&sx%VV+I43d^)qwWn{)~&en`^?uyw109qs_Dd98DbD)H!5 zFtB$t*17I1d0nt|H(MZSt-aJ-2WvE&)*W}d(C$TD2ecmKES5|ATV}BJM|6bI|4DQe zT5|%@L?GLK&ZIR_22K)}dYIN6G&=rQBPkuUelWK=nTd-9tqBc1RGhIKg4W%gb`+e~ z6B%qwhJ{(ZNxTQGWt-7$$GJ%sW4b&_&NZE6lMd#fwOQaI%AGahg-R~|00<* zES~sIT2o^1KnLP&LF-1dB1eCegt&Q6_6Ox4dW<(D{#4ri@G(xNu=Nkugo=VCL*m>Q z#dlt_L2FheY4S#GWzt&bflepl*vIKSosfDTi*-ABgr@b<)1e`TPL8BCK@u5HPNrmQ zS0k#512dp2QcTVbq z$!J(>)yTLRR9yBbIs;g;^B$+$dcc|jclH{D`09FM&$>v#`i1iFaF=G|H68> zhwU#|>uyQ&{lL0W7+QYv*s|*>lffdqu%=3fW%*MkPC1{|L# zRv4OZP@JEjO$uVwyXiY9#_dnAD!g-^0w=5?=~M;rum zban)=w_b^uOXt%|Y;Z#vDv|>xwvJkQWY{ZjNL=RZU~y4Un!wifrUEl+ngiIPu9lbf z8nq4q>`4W0i+VJwrP z5iB|hYzo*oEHDES*hEsr4VGZmiDS!X6)jQ-JtE?hq;I8nNeADJh!2vAl~f((uZhi` zogCD5#7%6s$7{QE*AAQ5RI5=A$Ky|8ljFe++s|$6(q|pOj)9YD>`s|Got2aj1hQ>u zY9r9bH51vLNCu^(+XfYlv-qFKd!Z(@U3OM*J)R3~S)VvJ&As=AHsyZBm&yj4(LQ6$ZQ+h-4Xzm zR;vqbv8p-ujH*aLo8Tb+$mwrHoS@YpJLtwfpe=*pL&mh~d^-Vcqfl!Pqy7Z6F=-}Y zLFaP<+W6C(&NWn60@_6z4eP3tg9&Kk-4Y_>ObTW{^n_Cf_}scPtb~bngzje~vvnPn ziO!rRv#TrSy)%q#tp@rt8%5TMY+LZ1w$V8f*?c%`E>s@{vUPHQXm`_da6p_#ie;%? zMVn9%o3z8ApQh8VXJR)*0zeeiIuM&?TB=1rw~}_CpeIIhy+d8<)qG^)Ils+etapZ(o-?HE2V6 zflcx10jv83wvH`w*0$Ia*p2gmaK_iWMt6Z%io+ei?qv96vQV#>yw--oU@C7@SFyK- z&wVbgAG(wu>ZC5MDf!SIqo)4Sy7EhOjzis`HC2DwY>T3qfHeWdV%*>~2dv4Ej4^{q zL2LL<3eIiULF-}%i!v?hW= zbD*2`g4P6}X=N=TC16@p;zAg3&U0z)G=ZfH>cw^C=vzQhrlI zP{+9L7+sXr_bfZpR_rjAw&;0rO%?)n5*7+v7ksIWuh1{9xdk`tj&X7A8Xz5r{p(*p zZc>)Tz}gC4V<6GDM4wXfn)r$Kifqt!d99~PUYfLN@VZl~%+26Q1+VcL^t9;MWFx}s zMk@|FFQ~nk*Dj;eevL?4@R}x@iBn8*V(^*>#l!-YIn|TbBz*NzI*Uq{oh^0{{w^gf z!Rulj99b9Iuo)6}y8$8v`|azpn#tzvoOG}kjW;MNxg08Rtv_-B-&pBjlgY&|((`Ev z>xWizZTL2>cnV!tgybkh2v`@lpFMP5VwE>nVC(BJcc%NY4eL(EnfpYrD`4Fm1g2P_ zWXah>C6J3%D9AFbIk+sZ)LMqaeX^i{L8>oUP3Y#aE5JiAt0}(RM8zqxHmbEkXw;}B zLQ|mH-W!Te+YLgXx;e2AsL7%nGkak7x%%&vL^XN@)Vs4Y64fM=WT4V#d;-;re71mq zQIW~(saH6R&Bjgv)pcXr;BeQH{x$gu*Im1LKTzH2KBU#zdK*S{qcWx9vFwb757$t* zPk&~~8Eu3J0RYq>^+1Pd_yDbuypDX?o|fndQe#tCdS+v~o75fo9>Phi=OA^ZP(iO? zTA(UeEw3LpuIdHVV70tUVwHz(mk>_jV3bR|Xx(4wUN=zl=ti}MYQTDVP4csemKxTi z+4-L4jwr*LC1@!fTlj`G23_N6Eo(d(){mI4E&VMxU;f@@l6Pl&l`|s&Dq%9=!bZYRMyj zJvr9UB&t_Cg}fB@`oNS;U{kL{9WVK*1a_yD1QmqmX0KgZ!Md&cb+0iN-N1F;Lh%k@ zF*a~Ng4hqe{``ya2Ezx5Vx5S?cB_d!QKz6WfXGr1TMx$C7Eay;v9757;;_ zvb7db_R{foF0zr1YR+xei)=T2EUJ}zFuU-Ihz*7CAIzry+7(LFxfkiD zMs}suYtSG}x1N!`x%tZH(bj8ZkBGg|<5n^#PJ)Dl2&Z}yo4~@v!E&>Yi7iWoHNWt~=&NHzo?x(?oDwHe~mqDQOe^MWRrBeWAAqY!_L^7LoztFaF zWfI8X)Op-^OL9G+O}#gzqRxl}wJVZj4V52spG<91){TS<^(>}#!^?Bo5f+=NP2JOj zDK=f*-|Y^&`PZsJStvdb#>4pETxe|50@U66UbE1 zQEDWX7Q`O>Jc@%(&7Lmg;x?rbd_@AAvl3~*m9=JJAkeTu`$^zh*3BqxvMH%_rmgj< zWF*UZaZP%xAy={17qEqIL@jw}nBMxO|W z^H6AFe=tNcckO@@vLPrN1+NFzhK~JZ%`x*@{)>aTx{-rwO5qi=JH;0qzkhkH&!wvF zm)BHf&fjo2%|>*E{qTB0CU(@zYn~U`!k^?duV4H62iM!0DZPGk*9)J-h3+q|N$>6A zF8_;bJqLQoV_^oad2Nlz(zmVap$C|h>Dz-%mEsX52u@#IlWgI|KW~lrgF@cp?%9iL zq`L_5?QgN!V<9lqTy9?%s|06x;;SdGb&>2W^Z_rgXTog-b85n+4vsyE=o{ z$R16aH1}I?#_{D|uZrc;DMP}@+jy5P^VhmBY8lEj8I#w;xg7dnUxiXy(&$g#*s8&6 zDq3NE2|XP1dbq#=Ie|AIKJa${b_T>ZJ!r1HWxYXbTH^|#Po$nMt*vP(`*VG1?Natm z<6XP7rcK7N;Qq$L2~Q6MMB2Kvh7EWiIFU(fR9f8Ex6Z|NEL3{d>%}!;ZR#Q^i%eYO zfb6KyH?AL4KM#>^x}gKto$v!Khp5V2TtBw{Nlm4PO%*>j7d?jAhFy?lvGGJng>g;S z|3GG7aA+IXKIN!WK%&{S=1&%P&(WW_X2o%C?&ydI*EAPd zFWyp+iEFwG5rk@l%>vhs&2$7@>kWt_)q~nGk)^=(LI;xCfCENh@G#C$9CgE5{;%(!lkmZIHGu435Bc zr#gyaPqG`vwea|H_Zi92jEGN~-iXRKija+KppG^z{y{acwfO1sKbD62efHb;!MC@>JBUXuH0XV-`m%^4L0k zsohw15@&$BIZ{D#x6JIu*0!dCbvgtGxQST7M%tisbN_&Qx;61vJ;hn^^V{3S5kSrM zucJ-EQ+DFF9~)&WuZSOR7uPaSpnbtjVANWfUvN_&h&lGGH{wPgt(hv9L?pf(qd?mBoLeNbDD>#{0qtY1^RTRBhL zWY)N-uCXKtf^$;S^X`k335vm;cCT5_BE zZtBzZXtGvZZVh@d^SBwt+@?!NkCKMAleOYRfL8>@X1du6dFYAog`gVTX0aa3+-X5- zuub@q{T?d3#Z7+P6@QM^ z)F?~E(t_HShm(KNdF>e-@2JK+$WUbECe?KH~LSW4G%Mw{aj7>L``6c=6b+WYd~H3}Lm=RbY?_$) z;vZyFBu~Qq2ifk@belmkyHJ-nx-F~AY%1aOabLYNH%AU0!^>=xlBVZiywI))n$Wnn zUuf&_T0D>T`q~5i{y`o0TPIHVp0fP<6)1(^E&6~jw8I40I?8W9wst3_4UaWF6WYr< zZ(0uHYLpgwy_dVtUuYB8v({)!y)(vXOdc0qbT71t))b52UT9PMx+x=j_0DLg9!Nfv zewmGE$U`-in_tXq1ZIv4yf3tcVxoYQjNLkMvh~y#*-|mHsdXA;DQF2xWmaC$e{oxdF&71+km$Bq|o$y2M5x zf*B00txN1iHQZo8$+*NGbQ~@ESnDM=Jx(Ny$Jf&)UwkMFJo6HJAUt24#|>hme!b}G z>xg|Mu@SSNt>h`0%jSTa=w=owXOq|i>CA}`YzD@4@=|cIL)SWh?OJHT5x>O1Uf~-Z z3+4o{DO#E6?bAPNUR!sXIvD;uc#ZlC9hMa}qu@0ONa|)?Rc>By=4N!XPLUc7UL)f} zd|glV2>}(|7W|RbC>tsrZM@#;`gp$#^O_P#ZPmve)$rQ&4n&$QZxp;nw(95(356uD zkqszV$DT1@URT=Sht&bI@NBv$lis9yaorhqL&XC@mcTWI!DIu^%u4ZzZKiVyVW=z> zueS4=wK1o0O&ySK7Vn9Uqri11<~}Sxmvv{Am^*1+gb)JPg?ETC-QB}$Tx(Z%OV}Z6 zYg|`4g6I($S3X5Yyhu#!f-F(+T09`=iRwv@V)4yz71_4bQO-zaQ$8#!`B2%Io!C^- zFSij+W;B`Cln`{wqu;tSbmqVsQKC0At}_p51Y(4j*TiEFwSrc1Hm}jcCJ#ihLh@QC zqbCngogTQhA3%ePE^T~*wntAAazY*%Np#>iikV2*(mPeXbcoqgV@v`JesjYU6iFLq(*i%a=g^bi|mKkYLeT{8Sv5SvDS)QVBi2uX3w1#}GQ zb6F*b?S63d?#b7g*m|Sd#@kaYvQ~Uq`yPEPJ4M(;B|4AbNRCX>g6!*q7IoigkvTyu2RA1tt5N%SKKEiIn+uvbkhYXYnmP z5Yvy>)Y^IHG zyCtuYDQd2=xVdcIn48kbLMN{&c-U@TYq$Cg^%uHOn|mqDYgPt9PXhUu*AG)6urJ2z zWRIwu4+a_7M1{=Pl`{%@*AN zDD838d@$RcewG|6m)QuiS8LAH%WP_z@l?+_m)W#WH)4aOUS?}wT9R?qdYLVQiWz0y zii*K(GLIulq%N~DQvmk90xHapIL?;#buP16@ff#m7z$=n4&6D{PpXPTG-{Ex;(EQ% zX8wkw3pF~j9rh%)*qv=QwaJ2GsJ7^<3%O&?5Lz);){74s&W8`!kuBMl5{qQaZOPU# z)ZEs`c)3teaRg;J`-bKb)J8W3rKIhilccs|&YODbmf39b)MQ#*9vaxTL?m{qoiV6A zQ1v8OvOL#@+7NQDl&6w`Oladavde}esHQeEzO>aS%jrRFCW8>Nr>JfvwhQw^$PJL6 z32bxtR@$7`tch*BY-%SOgCC7;-6*P z;g#j$h0#-maG4{7ZW-5kjF>V|K7nn5_7v>ndPQ)26G}!Oh_-?j*hYZ_DcFWULtq;f zb^?;qU7W@?dRa7iZz@g$+qzX^sOawW6i`8pr_n_11YKa8DPP0AmDGl_CFCe^!qCc7 zGoy{Y7g^4)o85+G>-Ba=y4iO23kD&rkLI>8k3s_#NpmE(DQCeDqT4dLjUuruEA;PH za$Bdgj_z_$Drog9Mq>=7Ebc))gj`^JVcws}Dw`K2`ow(+$P&qOs&y!#j1{DVzope*LuH#Sa+vH3K6xoyo>$8j&V z$xkqZbH3Q-)$LI|M&M$*aiF=b`{g$2#U04J+-6D=-A+^z1-DT=72v7qyxi6uZD(7{ z%kAzFZ=&aQv#k+Dr+j2^g#g{u0HJ!5*4zL#Ay%f8gc#X(<`NTJs` zz0R9Tk%1grQo9Pi%{A}oYZq6?($o9Qs2WH50?AVJnP>XiM7^ns;x|Q3o40*j^E7~{ zsf{;;7qX`FYEXM)0l1HsDmzo#GIUyo=oMaHn~r2cdogm$)FvKV`8lg)aQyMP_mD=t zq1~Yq%ujB+#~UePOUJ_PLF9vZx8$IV?MnCs*$Ge132ajod?M;V_m9Q<*z&?I>B)TS#tBx?`HHMk$=En;UoQM-Y-m6mSIcfM!Tx|YLvaoYu(T})v?k7}{c+BRO~f;h{gm)>(Irh}KMv#&7pRHswmqPZ zwhK-!BfDA>5?Qi~Y|b<66pW9|XIGSvw*_?Fbv*c$-O7%STlbA@vdff3B3U#*HbYn# z4mv4mz|OMC?};IdTtf{EWLNiZkq{AuT51jYU)-p`VU=Rwg#WS^4TQ9Fjv0N zcGEa9;V-lcQf}jCP&Up4Bh7x=yb4lYjnd1P*{BIq`~F2XmVCk7dXZhDo5>?%gW1SS zW3;y)X4^Ea)_=duE|wmSX6IlwqM?jee9O@0c7+R~`j^;fGg75anOG9LGKLq5t~Z@c zVKz1Zv*8lih+tv8a?A#@Enu-}T}aD97kU_CoR!k&b>Rfe^%{LV!5OMKjylhZXlrUTg7XN=+L<@)`x3<+{Y4I;Fhpfi=d75Wq$#4DosTxzo?q$BS|%a0;I> zW|}fgYB(>i$?7i3j!?va*9c-zUrP|9q)aIa)bxkb-WXlk4Da^bM^Ci~V51*EJrCWq z5gmU#*OaU*8c;!OBzrwAkjj@Jc41Oav+*RmL*MeXLmJz zj`NT6&-pTUo%Min%6qjC^+^Qv`A(XOt;uDajC$yyJ^rULmkYtUhctts0~=rlT8^K9u`h< zNS;yVq{D`$W3L(0+LFHfOFGz+)>=8A*$|pWqcn1(Xqut#GXC|jDec90{(&`bru@~C zO-Dj=Z_9->I+?@cVP04>y3gGfKd|m@TxvhG=7H!z?r+)nV2mg74Bu2X7XGA{@vT2A zsCpte_PkwQ948coqO+d3=2aKBXMJ&vi$_jqzM=65>Qhm5RaB^)D>mm|6&0O%oiPHO z4;R>!mz3e!)#NqZa7Y|7ZZUaXsCi><*2`<8EsjSKj+fV#eWmaEtjp`g9P?p`y31=U zBC^tt2Tz&TF;I>iz;^cVAGmID-~5%>0%F;@HL7Adz7D17_(6+uRO*eHu=mw zQPj;QFKXw=S#+myg4h-gwfM=%aUg>qO|vQEyIK&tn+NEnVbqz}jH9AT*~d;0TQ3M} zf~InMgdp~CI}jU?Hc1m3lZWwp9txPmCVE0GRQHS(6C2lmGCywCoVTg-lIRIlgtu&* znncz!I^x-vjY4eo5LOGTnb`8-EblX{i)dm$?%vjJLyaPcJv=vYv2Al42-OVJLZj#E zsCZN~)gov&2eQdiIMTypw?sBs92zPOanA%GmgULZEnRG;lsKhh-QXa0qX29`lfNyA z3FDahNo<5W*nAWc2C=(yM1;P9kcs^;Y{KJt$cvcRWH^gQa9Lg!V)LsVm?elE#3rLM zn#J;4D2r1CMHzl2o|aBFrC8V#E~!gmBd$gR$HO%AAT}fA3z}i=wtAt;hG;8K_%hpr z(mZ{S7i4BP3c_f3aN7W6I5LDN`WX(`2eb7$CJ{+SHzu=-Zp?Wd7jPQdD2On7uH2&W z3VNx3L2@>kO|*uB-FbVnUDUnY|M#Ty8H3gPvH_Ph_t@1Z6njoUKDsUhzqfE6ybx}$j@))RWlbMpxvmRcN%m={TZntw3$grws5i0X=T7T zuv$Byy%^yGc=4YD+J)J3bP!WYn9wFxf^HiUG5=-hy3W9E*SF}8{-rkMG3PI8KUxdV z^E1JV9eJ3X`h_-sfrj`mv^^ZI{wklr5u*R)t5fQ@D7`P=us8$Eo$LFiv(djm1oE5C zmIqEoo7(KlYN$5t1+$UZKRE1HR~!L}%7okXR-Z9Fn0Rr!#b+e> zd6p#hlGr5p>`?I-64?yxq^e`TE_SDyj)BErDHRIhHgy*Ld86W!j3^gl zA@D#phYu>=`o~cY|18ZWWC z#~+Yj=|OBfS7d8BOG*%XGgpA~3H9YmY+eRd;Z;(xN+`1v9#4!4U^}l&k!8tDHb{L+ z=m+#mY~r>!xKtH-PT@gwjHt9*rA%x)hyw2|dAu<`0$x=oTjfK6bYjsoBwZP|J&lq3}JJhLsk zJ3@`kZC4CnS6AH;C*d?OuxaUWQ_;G*XVgG3Qh=7r)IGD@*MfchX0knM2BB4L@x(R5 z96RI7cb1=dvI*0+bQ}b%xBbi=B27VS3j$J4z|f1NwPz^fbfXR*w0^8!H<^f$v}WiF z_CY_Ms|>9zWnL*y=i{L@sbS28>Xb{+x;lH0?NmzAn!-s!Pw0|{lnOJ!A1{%HCO?AB z7;sE;v7mCmnyBooPEV997Vl{NEr!+7Z40yz;YPy>_f(x}A|3<@!cX1oPs77k@{Do@rI<8N9SuH$b zPyetQeZJx5y^Cs;PZ+EH<6mQap!W6EJR@z|&@}wEWk1eqcv#d$b$1KjvTS{_+LK+O z#XAGUDa^_g;J5UwP{Zm+RbN))-pR0{lIFtt@$?)0#CmDHdCD-`zHffEB@Kd5<=F+-Ra7BSvyQOC|Pjotw#=JS~jqATXBIVMWph=;Tq`kDZW(g%^ zV_aN=zaH|^iRPg*m4+${y!mHnX0V99c_`=+%A>kii?-+1`416*kbs_oLchBN?W3_)wO0hsXC z?Iy)kVf{k%z)<{&YVpC4k8YK8#4=6m>b~}A1-|sJYotg>hpZ4$A)HHzZzJ>?!?8Il zM@VB{S{Ex1;5pTm!s4QIEabgzm<;exFm&b)159ZEzILa{$=w{`f;B}==pHllx|E+nwc>P-E&HW|jwbqDb zes!MC9=xV|pb_4NrNL`z-!?IsHf-7w0H$LKA^k;Dtu;(tbpjNb5fQ*%gxkH6nS5(t zm&fJ~{(RpFY^|m8IGNHyTAYRcvEDIyvk7bqiV;*AcMwRUP3!S2C?~^;a&lbxa-a$ZvZWV?e4KweA=Y-d(Z46oT5p>>i5=?Xk;j;>9GW zsnQE(TO^DzVH9r%vy0n8dCt%{m@Svl!$Qm3lfAl08{#d=Y{s6tnRQ%bBN60*5X{34 zWTT?^AhGf0u&2F->LPz5d-7P3ZnKu+MRs%-FlOUwm1?_K{FSP?WHu>_?ZI62y4Xb0 zvFnJ<@u#IYs6DI^KSzviG`8796fAl?SYR7#qI)v?Of40<(cp+P|^_Ql#CMZWtY7~Lj2ExNSBZ)(Jqk*J7;=9rWpDPjW%jYfq|ZW2y-rE;agt zE-Yhv5>2J8`HXDxTF0WZjxt6X8yz+#%b@?qQEqJ0Z^=x?0|x@z9?Dpmz}r(wMbsp% zK#ivLmcTakT=W~!!j*nD1?msa3IijkT_VLwt>bmGEgpEB6i8@WS{40;QA4kkf=~uS zQVun;$BR71!}6k$EucM_C@PTh32j7$=zZ&+hV0=g zE+1uY(4b~$laKby!DS79piMQ`g53`p3PT&U)yHBe$WR*EXtAR!uNPZ)K)bntoW6O^ zK|@>r+lOeUPXDC#!_Pj9{6fq&uua!IRkgHg#=Ol!VN0ho8Lco=yU^E4!zDe+ck=QS`Df+C8eR{EtTpJ7f zi*3e{dBW-!+tfZg$njDey>{!feW^WBs9xDQ=RkAs4NZO5beP z`-$Zq#Kt3B6rFABUrtX3BHtA=qcSi)ELq6!a1#Z@e(2Ic4Bs6FCbm_&s0%==!Ng|v z2N@(+5e2Xvyg<2uAp`+zPe3DFPv|y)O*h#ys1$z%vJnfw;3R7mBfMmXFOFFh2^Z4l~W^z5v0Zx zBo-t4q0tQ;3U`kg+34EQy+&Fzs?Wgj#{*=YI0e}rHb)UwMV{Qu-X3Ps=xU_0!OTWn zkH{6lsSJ)=Sa*jTPJ>rN%Zee1kB1)po5=3dhQ}|nSI@DlUuHkp+T%yGlv$4W#Oup! zk8LgE{A98x-7w{6gcs(rYumrkagN)^)q^jy8G=T+N&R}-xP#RX=4Ezupt@MKGMG)4 zr8&f%#C~vu*nu7!6U468;+sEycCl*UqzNQ}ZDGag)b9m0hW}&*-0|2C<^CKZ6H(3t zHWWqqY~0c_Pfj14FL&4`-%U5i@z9mPHBz>ez z5U@&Iqxy&IhS)*idU`s#ho(nloM0LEYQ68CxMrGjjXA&x>xY7Gf{8uHV^>m!DD*gZ zX;+Ufdf6A(&$3=V1?895xF6K(j2yVh zd)iH`EYTOm~+ zxTbCMVGzLz5v^{)=i#8y!O;WPjJt9ZF45R%$o3>+Is#DyG_C3X+l?VsTSmrfZ-saB7o5kK}s%rzV|8n3aWzL=2-e%=f1G{_f z3UkEnz)goYv6CKVGz738OBi@qd3JU+mh1P+gWS#8e4NRxV4^4WOGu|CeDP|AP`lDGiZ%O&%yH__PH(UzL;IYU@nMdS~Itu zd^A1!A)>;R@(;q!AS61wa5!kU8V(2strs#83<@kgCvH-PKt&RVIBJOpMHoVj!E2JymZM!aDo$C3 zmoGZ*$!k(4j(C+D6{nn>WM+vHDR_-dUo3@rZXz4eUD7>`0nf>6F^9VPK|4|K+Fk0C z{<(AA>q^B1^OMi3FHQ^_KbBI#p>FDFHu**~v8aL|$l92#8dnab5i6sRJYr zYg|*fh4dT>bJ^H6qrsgP=g?1qQt1C9mfOz7HD$>xfD|b$bmGEl-2(N+(3VFmQhIcn&v3#ZUy6+ zxTX}Eh}rUxJmb0{UBj5hnOW?Ip>;GH7Pp$3)_&K=5s>6H1y{6$pm-b;L71gQ;;B&` z6u6!qiFXjQInmd}C{daVXxOTW>p_D()5y5Lz;#8f742Ru=RcvfZ1HpyFX^8!uF=VT zY=fP|HPyS=f~@8mja?L;sGarxDx3Mo={R^15TN7b;+0UNb;rIe2z?y{u6}&HKyi#d_(e5Wl=; zY6z1S#v2i5RJdMd^|;t1Hfrz;|KfxWVmCTpFbCEpcJ)|gr2TF$Hr_UR&E*{kqVukz zYQuP;%WLf(?=J>r1+QHr(X9TE^RXKZdvvp+v~+>((4>w9r;rS6=CP3?ZPsfJVAHnW zkuB|4S)2-ZK8zlQ1h!6+K0Nxb-^w%eVu&fz{BnUUM-yFK`u{JmW%<#oYH*m>!~o^) zFcIWjUZdEC<&DuBybfqkK3ZGy8t2HPA^UZ&JMpQFQtDXAYhoHylB1z%UJnM|F~$eW zXkTD2itjB3{yNy~sKv!MiA~KCR+b)YdmU^fohU%P!13m)Vb7Ly%MH!E6Hj`nuY}A>f#O?D`o2S$bw6%;h{AgJ8{Us@xgv!RXr^ z%qDC;tWr&hs+rx9w?cKlaUL7m9*Mb_1~AjbCIZ3>rZ#IQvk@0%iU;AlWOj2yusj$S zWtiCyl_01Kb?kST?IF{s!4u^VW<$M+Og$|p$!uC^5f&hMlgy?)8+@W$oXl=a*&vqH zR>s6>Of1a#W0Gw!n^BD9=ox5ulG#MSnnfF*XvU_HZCbd9R)k=7H55o|JR`E$IvGFi zc%;Op1+%>-;2C5MXb8!-oH8duX0Z|aqPCEpkRH%}mc_E2)(U8EN&`qI z5g*tA?VO5Ms9Fgpn^Y>@j7%i!Id`*CzD3FOiBSen+kO1H;Mpym>_!TCZ2V`-PgU^Y zjw!#m9@J(e3~fxD?&)OHnOGQpS$4VDu9!l^^V}~9w)?QiDN+^lB(}-%qgupFqr`Sp zco(|6TXLJ}W@PPX-MdrE4Ra#E6+u@r1_TYHX>el}Kj%eoh` zBI~gPRdAcmYC87(o`c)Ii48gkQ<>W`=s(ta<$Mfo6U}&hLQ&#jZev6v8%XI_4{oCg zKt~Q0>Cuu+w*SMlY-)IGa+|R_A#Z&UP$6GYJYWk!yz(!%nV?NQ+Yh%%##uAxP zE`jc1Ri{o`k&y@|ZfPa>PK>^(%_FSmIv13Hr@r8R7|lsl!)?i~!=7BTU|w>YwPy*kee>AN!EukE!%5~g&~sUp_d%?WR)?=$R|QO-(;z&?s??xtrVJ<-)g8C~0uh$ATg~!nzq6_h?k6{|=A; zAlP>}3$gkOQ|`KTc@eppcC4PP8RsS_7-8IGOa#G8bL|veA}hd6&p$Xphci>z-33V& zTMBM-eBl8M;_6W{uE$j^9!#xK33gBK%DvB=Y-Ft@wIzugCRa=SSpfCn}rm(4>ZVLg+?=wh7M#NVKx)W zscLxW9t~!9Xi09J(y?InB8@IDzH<=AoQC@RYKbTGU+)p0e7?@4L9Mw|D7p8(rJNU28@b{1@?W`lT9hy_fUIA`L z;N=iH3}g>F+9^#OHjN>6p|X|YEyS};Y(D)V7|h6tAhwqChyF4;c0)o1kkK5EuU?bb z_|S9!V`xVj8(}7oz-`XLjW{(CjdRaWXsA5L!p?*`Sm8lz2BRKykqG4ku?ydbUYqB> zS|{aj=25S{8E|T16UOluKW?vh9JxClgHDmCiH)WoYxEG!fBscb!9iQU<3N}jNeS;S zuu0ik7wrYM1BG-soChtN)(zjro=1wfk_67D>7Fz8;j&{Q} zLIkS5z^0&+>cjpGjXxw~7%ng%L7vyqrs58TfwOP9 zI6^Y0DtK^U@Y)_H7F%Dp@Qhrdr_=Ks642h5FD4-3^H5PBVV1;>KWLrIbv~%%{&!qBzt*@jq(}=Caf+KyT*V!4>9gRY@Rf` zk5T305_?!>kg2Nc5*uBehGt*gm)MoGr9;u@5}Sq<3c>fCy~)l30|lEui0wgzG#gDC zzf5eqJ**t6n}gVHQeyb`pr6LXrq+njY>Wj2OO%|fo$FCXxq`2kUeWwl4}klmK0IxG`HZzP>7Mp#)#-v#2Qah| zcC$onqfgGz=1x7{+CqXS=b|xi&k{h_(Pqkt&i~C&*nl=dAv9NcdTBzN&0 _sj$B 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

44qnO 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ð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

Th1cd?lG&Jxx(`^VL5~65WN94aIW(N)5 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ðV(f1JVpad+Fq^>iFmHn~&#OIDsO1_vXU;!XQFh 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 z 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

    IZre~9 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∨+i+Nmdiw5^S!H*e;BLNu!G#{#<9 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 literal 0 HcmV?d00001 diff --git a/src/atomref/data/registry.json b/src/atomref/data/registry.json index 55bec2e..c248bdc 100644 --- a/src/atomref/data/registry.json +++ b/src/atomref/data/registry.json @@ -28,6 +28,11 @@ "domain": "element", "units": "angstrom", "description": "Element-indexed reference X-H bond lengths keyed by parent element X and intended for hydrogen-position normalisation or related geometry workflows." + }, + "proatomic_density": { + "domain": "element", + "units": "electron/bohr^3", + "description": "Element-indexed neutral spherical proatomic density profiles sampled on a shared radial grid." } }, "datasets": { @@ -510,6 +515,83 @@ ], "usage_role": "target" } + }, + "proatomic_density": { + "pbe0_sfx2c_dyallv4z_h-lr_neutral_v2": { + "name": "Neutral H-Lr proatomic densities (PBE0/sf-X2C/dyall-v4z, upstream v2)", + "description": "Neutral spherical proatomic reference densities for H through Lr derived from the atomref-proatoms 2.0.0 PBE0/sf-X2C/dyall-v4z dataset and truncated to a 20-bohr public domain with one retained source bracketing point.", + "semantic_class": "proatomic_density_radial", + "origin_class": "computed_theoretical", + "phase_context": "isolated_atom", + "method_summary": "PBE0 self-consistent spherical fractional-occupation UKS with spin-free one-electron X2C and the dyall-v4z basis.", + "storage": { + "kind": "element_radial_csv_zip", + "format": "wide_csv_zip", + "filename": "proatomic_density_neutral.zip", + "member": "proatomic_density_neutral.csv", + "radius_column": "r_bohr", + "density_column_pattern": "z{z:03d}", + "native_coordinate_unit": "bohr", + "native_density_unit": "electron/bohr^3", + "public_max_radius_bohr": 20.0, + "retained_bracketing_radius_bohr": 20.1644204667093, + "retained_rows": 1127, + "interpolation_contract": "loglog_positive_bracketed_v1", + "monotonicity_relative_tolerance": 1e-12, + "charge_scope": "neutral atoms only", + "source_project": "atomref-proatoms", + "source_release": "2.0.0", + "source_archive_commit": "5b330d8215eea95b273e38a6f4962ecf917e71e6", + "source_dataset_id": "pbe0_sfx2c_dyallv4z_h-lr_spherical_v2", + "source_profiles_sha256": "b5520ab009542d52098dd6dbb920966d8d13377a4a5004f584a7bd15cd41c299", + "source_metadata_sha256": "32c833ca69fa0f7eb9ed32841aafc638123ff872861e636156610e417fc4c514", + "basis_id": "dyall-v4z", + "basis_sha256": "0ee543855f8b1e7fbe9868d4abb844d8e8cc8b8c2694067b2b40de014bb4be94", + "profile_data_version": "2.0.0", + "electronic_method": "PBE0", + "scf_model": "self-consistent spherical fractional-occupation UKS", + "relativity": "spin-free one-electron X2C", + "data_license": "CC BY 4.0", + "data_license_url": "https://creativecommons.org/licenses/by/4.0/", + "concept_doi": "10.5281/zenodo.21291021", + "version_doi": "10.5281/zenodo.21291022" + }, + "coverage": { + "n_values": 103, + "z_min": 1, + "z_max": 103, + "has_placeholders": false + }, + "placeholder_value": null, + "extraction_source": "Deterministic neutral, truncated consumer snapshot built from atomref-proatoms 2.0.0 dataset pbe0_sfx2c_dyallv4z_h-lr_spherical_v2.", + "aliases": [ + "neutral H-Lr PBE0 proatomic densities", + "atomref-proatoms neutral v2" + ], + "references": [ + { + "authors": "Ivan Yu. Chernyshov", + "year": 2026, + "title": "atomref-proatoms: spherical atomic and ionic reference densities", + "doi": "10.5281/zenodo.21291021", + "note": "Concept DOI; cite with atomref-proatoms release 2.0.0 and the exact dataset ID." + }, + { + "authors": "Ivan Yu. Chernyshov", + "year": 2026, + "title": "atomref-proatoms 2.0.0", + "doi": "10.5281/zenodo.21291022", + "note": "Version-specific DOI for the immutable source archive." + } + ], + "notes": [ + "These method-, basis-, state-, and sphericalization-defined reference densities are not unique basis-independent atomic observables.", + "The public validity domain is 0 <= r <= 20 bohr; the retained point at 20.1644204667093 bohr is an internal interpolation bracket only.", + "Detailed state and generation metadata remain available in the exact atomref-proatoms 2.0.0 archive.", + "The imported data are licensed CC BY 4.0 separately from atomref code." + ], + "usage_role": "target" + } } } } diff --git a/tests/meta/test_package_data.py b/tests/meta/test_package_data.py index a9a7e61..7581f61 100644 --- a/tests/meta/test_package_data.py +++ b/tests/meta/test_package_data.py @@ -9,6 +9,7 @@ def test_packaged_data_files_are_available() -> None: for name in ( 'periodic_table.csv', 'covalent.csv', + 'proatomic_density_neutral.zip', 'van_der_waals.csv', 'registry.json', 'xh_bond_length.csv', @@ -26,3 +27,26 @@ def test_packaged_registry_keeps_atomic_support_classification() -> None: assert rahm['usage_role'] == 'support' assert rahm['semantic_class'] == 'atomic_isodensity' assert rahm['phase_context'] == 'isolated_atom' + + +def test_packaged_registry_records_proatomic_attribution() -> None: + data_root = resources.files('atomref.data') + raw = json.loads(data_root.joinpath('registry.json').read_text(encoding='utf-8')) + + datasets = raw['datasets']['proatomic_density'] + matches = [ + entry + for entry in datasets.values() + if entry.get('storage', {}).get('filename') + == 'proatomic_density_neutral.zip' + ] + assert len(matches) == 1 + + entry_text = json.dumps(matches[0], sort_keys=True) + for marker in ( + 'CC BY 4.0', + '10.5281/zenodo.21291021', + '10.5281/zenodo.21291022', + 'pbe0_sfx2c_dyallv4z_h-lr_spherical_v2', + ): + assert marker in entry_text diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index 7cbff90..1179ca8 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -1,11 +1,75 @@ from __future__ import annotations +import hashlib +import importlib.util +import io import subprocess import sys from pathlib import Path +import zipfile + +import pytest REPO_ROOT = Path(__file__).resolve().parents[2] +CHECK_DIST_PATH = REPO_ROOT / "tools" / "check_dist.py" +SNAPSHOT_PATH = REPO_ROOT / "src" / "atomref" / "data" / ( + "proatomic_density_neutral.zip" +) + +spec = importlib.util.spec_from_file_location("check_dist_tool", CHECK_DIST_PATH) +assert spec is not None and spec.loader is not None +check_dist = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = check_dist +spec.loader.exec_module(check_dist) + + +def _snapshot_with_modified_csv(payload: bytes) -> bytes: + with zipfile.ZipFile(io.BytesIO(payload), mode="r") as archive: + csv_payload = bytearray(archive.read(check_dist.PROATOMIC_SNAPSHOT_MEMBER)) + csv_payload[len(csv_payload) // 2] ^= 1 + + output = io.BytesIO() + with zipfile.ZipFile(output, mode="w", compression=zipfile.ZIP_DEFLATED) as archive: + archive.writestr(check_dist.PROATOMIC_SNAPSHOT_MEMBER, csv_payload) + return output.getvalue() + + +def test_dist_check_accepts_pinned_proatomic_snapshot() -> None: + check_dist._assert_proatomic_snapshot( + SNAPSHOT_PATH.read_bytes(), + member=SNAPSHOT_PATH.name, + label="source tree", + ) + + +def test_dist_check_rejects_changed_snapshot_archive() -> None: + payload = SNAPSHOT_PATH.read_bytes() + b"altered" + + with pytest.raises(check_dist.DistCheckError, match="snapshot SHA-256"): + check_dist._assert_proatomic_snapshot( + payload, + member=SNAPSHOT_PATH.name, + label="test wheel", + ) + + +def test_dist_check_independently_rejects_changed_inner_csv( + monkeypatch: pytest.MonkeyPatch, +) -> None: + payload = _snapshot_with_modified_csv(SNAPSHOT_PATH.read_bytes()) + monkeypatch.setattr( + check_dist, + "EXPECTED_PROATOMIC_SNAPSHOT_SHA256", + hashlib.sha256(payload).hexdigest(), + ) + + with pytest.raises(check_dist.DistCheckError, match="inner CSV SHA-256"): + check_dist._assert_proatomic_snapshot( + payload, + member=SNAPSHOT_PATH.name, + label="test sdist", + ) # Keeping this as a subprocess test ensures the helper stays importable and diff --git a/tests/proatoms/test_dataset.py b/tests/proatoms/test_dataset.py new file mode 100644 index 0000000..0ecb65c --- /dev/null +++ b/tests/proatoms/test_dataset.py @@ -0,0 +1,181 @@ +from __future__ import annotations + +import csv +import hashlib +from importlib import resources +import io +import math +import zipfile + +import atomref as ar + + +_DATASET_ID = "pbe0_sfx2c_dyallv4z_h-lr_neutral_v2" +_ARCHIVE_NAME = "proatomic_density_neutral.zip" +_MEMBER_NAME = "proatomic_density_neutral.csv" +_EXPECTED_HEADER = ("r_bohr", *(f"z{z:03d}" for z in range(1, 104))) +_EXPECTED_ROWS = 1127 +_MONOTONIC_REL_TOL = 1.0e-12 +_EXPECTED_ARCHIVE_SHA256 = ( + "1ec0318c8bc8f6e71eb3125cf1d4387e4593d7bee8ff5ee5270fbcc32c70ec6b" +) +_EXPECTED_CSV_SHA256 = ( + "8478da862233c8874e36d65bb5eb762cdb9cbcb0e0278733c0f425ae00c2dcfe" +) + + +def _snapshot_csv_bytes() -> tuple[bytes, zipfile.ZipInfo, bytes]: + archive_bytes = ( + resources.files("atomref.data").joinpath(_ARCHIVE_NAME).read_bytes() + ) + with zipfile.ZipFile(io.BytesIO(archive_bytes), mode="r") as archive: + assert archive.comment == b"" + members = archive.infolist() + assert len(members) == 1 + member = members[0] + csv_bytes = archive.read(member) + return archive_bytes, member, csv_bytes + + +def test_neutral_snapshot_matches_pinned_scientific_fingerprints() -> None: + archive_bytes, _, csv_bytes = _snapshot_csv_bytes() + + assert hashlib.sha256(archive_bytes).hexdigest() == _EXPECTED_ARCHIVE_SHA256 + assert hashlib.sha256(csv_bytes).hexdigest() == _EXPECTED_CSV_SHA256 + + +def test_neutral_snapshot_has_exact_zip_and_csv_contract() -> None: + _, member, csv_bytes = _snapshot_csv_bytes() + + assert member.filename == _MEMBER_NAME + assert not member.is_dir() + assert member.date_time == (1980, 1, 1, 0, 0, 0) + assert member.compress_type == zipfile.ZIP_DEFLATED + assert member.create_system == 3 + assert member.external_attr == 0o100644 << 16 + assert member.internal_attr == 0 + assert member.extra == b"" + assert member.comment == b"" + assert member.flag_bits & 0x1 == 0 + + assert csv_bytes.endswith(b"\n") + assert b"\r" not in csv_bytes + rows = list(csv.reader(io.StringIO(csv_bytes.decode("utf-8"), newline=""))) + assert tuple(rows[0]) == _EXPECTED_HEADER + assert len(rows) - 1 == _EXPECTED_ROWS + assert all(len(row) == len(_EXPECTED_HEADER) for row in rows[1:]) + + +def test_neutral_snapshot_values_satisfy_the_retained_domain_contract() -> None: + _, _, csv_bytes = _snapshot_csv_bytes() + rows = list(csv.reader(io.StringIO(csv_bytes.decode("utf-8"), newline=""))) + data_rows = rows[1:] + + radii = tuple(float(row[0]) for row in data_rows) + assert len(radii) == _EXPECTED_ROWS + assert all(math.isfinite(radius) and radius > 0.0 for radius in radii) + assert all(right > left for left, right in zip(radii, radii[1:])) + assert radii[-2] == 19.865456344881434 + assert radii[-2] < 20.0 < radii[-1] + assert radii[-1] == 20.1644204667093 + + for column_index in range(1, 104): + profile = tuple(float(row[column_index]) for row in data_rows) + assert all(math.isfinite(value) and value > 0.0 for value in profile) + assert all( + current <= previous + or math.isclose( + current, + previous, + rel_tol=_MONOTONIC_REL_TOL, + abs_tol=0.0, + ) + for previous, current in zip(profile, profile[1:]) + ) + + +def test_neutral_snapshot_uses_generic_registry_discovery_and_caching() -> None: + ref = ar.DatasetRef("proatomic_density", _DATASET_ID) + + assert "proatomic_density" in ar.list_quantities() + assert ar.list_dataset_ids("proatomic_density") == (_DATASET_ID,) + assert tuple( + info.ref.set_id for info in ar.list_dataset_infos("proatomic_density") + ) == (_DATASET_ID,) + + info = ar.get_dataset_info(ref) + alias_info = ar.get_dataset_info( + ar.DatasetRef("proatomic_density", "atomref-proatoms neutral v2") + ) + assert alias_info.ref == ref + assert info.coverage is not None + assert info.coverage.n_values == 103 + assert info.coverage.z_min == 1 + assert info.coverage.z_max == 103 + assert info.coverage.has_placeholders is False + + dataset = ar.get_builtin_set(ref) + via_alias = ar.get_builtin_set( + ar.DatasetRef("proatomic_density", "atomref-proatoms neutral v2") + ) + assert isinstance(dataset, ar.ElementRadialSet) + assert via_alias is dataset + assert dataset.info is info or dataset.info == info + assert len(dataset.radii) == _EXPECTED_ROWS + assert all(dataset.profiles_by_z[z] is not None for z in range(1, 104)) + assert all(dataset.profiles_by_z[z] is None for z in range(104, 119)) + assert all( + len(dataset.profiles_by_z[z] or ()) == _EXPECTED_ROWS + for z in range(1, 104) + ) + + +def test_neutral_snapshot_registry_records_pinned_source_and_license() -> None: + info = ar.get_dataset_info( + ar.DatasetRef("proatomic_density", _DATASET_ID) + ) + assert info.storage is not None + storage = info.storage + + expected = { + "kind": "element_radial_csv_zip", + "filename": _ARCHIVE_NAME, + "member": _MEMBER_NAME, + "radius_column": "r_bohr", + "density_column_pattern": "z{z:03d}", + "native_coordinate_unit": "bohr", + "native_density_unit": "electron/bohr^3", + "public_max_radius_bohr": 20.0, + "retained_bracketing_radius_bohr": 20.1644204667093, + "retained_rows": _EXPECTED_ROWS, + "interpolation_contract": "loglog_positive_bracketed_v1", + "monotonicity_relative_tolerance": _MONOTONIC_REL_TOL, + "charge_scope": "neutral atoms only", + "source_project": "atomref-proatoms", + "source_release": "2.0.0", + "source_dataset_id": "pbe0_sfx2c_dyallv4z_h-lr_spherical_v2", + "source_profiles_sha256": ( + "b5520ab009542d52098dd6dbb920966d8d13377a4a5004f584a7bd15cd41c299" + ), + "source_metadata_sha256": ( + "32c833ca69fa0f7eb9ed32841aafc638123ff872861e636156610e417fc4c514" + ), + "basis_id": "dyall-v4z", + "basis_sha256": ( + "0ee543855f8b1e7fbe9868d4abb844d8e8cc8b8c2694067b2b40de014bb4be94" + ), + "profile_data_version": "2.0.0", + "electronic_method": "PBE0", + "scf_model": "self-consistent spherical fractional-occupation UKS", + "relativity": "spin-free one-electron X2C", + "data_license": "CC BY 4.0", + "data_license_url": "https://creativecommons.org/licenses/by/4.0/", + "concept_doi": "10.5281/zenodo.21291021", + "version_doi": "10.5281/zenodo.21291022", + } + assert {key: storage[key] for key in expected} == expected + assert {reference.doi for reference in info.references} == { + "10.5281/zenodo.21291021", + "10.5281/zenodo.21291022", + } + assert "CC BY 4.0" in " ".join(info.notes) diff --git a/tests/proatoms/test_snapshot_builder.py b/tests/proatoms/test_snapshot_builder.py new file mode 100644 index 0000000..9bd6137 --- /dev/null +++ b/tests/proatoms/test_snapshot_builder.py @@ -0,0 +1,174 @@ +from __future__ import annotations + +from collections.abc import Callable +import importlib.util +import io +from pathlib import Path +import sys +import zipfile + +import pytest + +from atomref.elements import iter_elements + + +REPO_ROOT = Path(__file__).resolve().parents[2] +MODULE_PATH = REPO_ROOT / "tools" / "build_proatomic_density_snapshot.py" + +spec = importlib.util.spec_from_file_location("snapshot_builder_tool", MODULE_PATH) +assert spec is not None and spec.loader is not None +snapshot_builder = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = snapshot_builder +spec.loader.exec_module(snapshot_builder) + + +_Metadata = dict[str, dict[str, dict[str, object]]] +_EXPECTED_Z = tuple(range(1, 104)) +_SYMBOL_BY_Z = { + element.z: element.symbol + for element in iter_elements() + if element.z in _EXPECTED_Z +} + + +def _state_id(z: int) -> str: + return f"neutral_z{z:03d}" + + +def _column_name(z: int) -> str: + return f"rho_e_bohr3__{_state_id(z)}" + + +def _synthetic_metadata( + *, + state_order: tuple[int, ...] = _EXPECTED_Z, + column_order: tuple[int, ...] = _EXPECTED_Z, +) -> _Metadata: + states: dict[str, dict[str, object]] = {} + columns: dict[str, dict[str, object]] = {} + for z in state_order: + states[_state_id(z)] = { + "symbol": _SYMBOL_BY_Z[z], + "z": z, + "charge": 0, + "electron_count": z, + "multiplicity": 1, + } + for z in column_order: + columns[_column_name(z)] = { + "state_id": _state_id(z), + "symbol": _SYMBOL_BY_Z[z], + "z": z, + "charge": 0, + "electron_count": z, + "multiplicity": 1, + } + return {"states": states, "columns": columns} + + +def _remove_neutral_state(metadata: _Metadata) -> None: + metadata["states"].pop(_state_id(8)) + + +def _duplicate_neutral_state(metadata: _Metadata) -> None: + metadata["states"]["duplicate_neutral_z008"] = dict( + metadata["states"][_state_id(8)] + ) + + +def _remove_selected_column(metadata: _Metadata) -> None: + metadata["columns"].pop(_column_name(8)) + + +def _disagree_with_state(metadata: _Metadata) -> None: + metadata["columns"][_column_name(8)]["multiplicity"] = 3 + + +def test_zip_builder_is_deterministic_and_normalizes_member_metadata() -> None: + csv_bytes = b"r_bohr,z001\n1e-6,1.0\n" + + first = snapshot_builder.build_deterministic_zip(csv_bytes) + second = snapshot_builder.build_deterministic_zip(csv_bytes) + assert first == second + + with zipfile.ZipFile(io.BytesIO(first), mode="r") as archive: + assert archive.comment == b"" + assert len(archive.infolist()) == 1 + member = archive.infolist()[0] + assert member.filename == snapshot_builder.ARCHIVE_MEMBER + assert member.date_time == (1980, 1, 1, 0, 0, 0) + assert member.compress_type == zipfile.ZIP_DEFLATED + assert member.create_system == 3 + assert member.external_attr == 0o100644 << 16 + assert member.internal_attr == 0 + assert member.extra == b"" + assert member.comment == b"" + assert archive.read(member) == csv_bytes + + +def test_neutral_selection_is_metadata_driven_and_returns_in_z_order() -> None: + odd_then_even = tuple(range(1, 104, 2)) + tuple(range(2, 104, 2)) + metadata = _synthetic_metadata( + state_order=tuple(reversed(_EXPECTED_Z)), + column_order=odd_then_even, + ) + + selected = snapshot_builder._select_neutral_columns(metadata) + + assert selected == tuple((z, _column_name(z)) for z in _EXPECTED_Z) + + +@pytest.mark.parametrize( + ("mutate", "message"), + ( + pytest.param( + _remove_neutral_state, + "neutral-state coverage must be exact Z=1..103", + id="missing-neutral-z", + ), + pytest.param( + _duplicate_neutral_state, + "multiple neutral states for Z=8", + id="duplicate-neutral-z", + ), + pytest.param( + _remove_selected_column, + "selected state 'neutral_z008' maps to 0 CSV columns", + id="missing-selected-column", + ), + pytest.param( + _disagree_with_state, + "state/column metadata disagree for 'neutral_z008': multiplicity", + id="state-column-disagreement", + ), + ), +) +def test_neutral_selection_rejects_invalid_metadata( + mutate: Callable[[_Metadata], None], + message: str, +) -> None: + metadata = _synthetic_metadata() + mutate(metadata) + + with pytest.raises(snapshot_builder.SnapshotError, match=message): + snapshot_builder._select_neutral_columns(metadata) + + +def test_pinned_source_reader_rejects_hash_mismatch(tmp_path: Path) -> None: + source = tmp_path / "profiles.csv" + source.write_bytes(b"not the pinned source") + + with pytest.raises(snapshot_builder.SnapshotError, match="SHA-256 mismatch"): + snapshot_builder._read_pinned( + source, + expected_sha256="0" * 64, + label="profiles.csv", + ) + + +def test_check_mode_rejects_output_byte_mismatch(tmp_path: Path) -> None: + output = tmp_path / "snapshot.zip" + output.write_bytes(b"stale snapshot") + + with pytest.raises(snapshot_builder.SnapshotError, match="snapshot differs"): + snapshot_builder._check_output(output, b"expected snapshot") diff --git a/tests/registry/test_registry.py b/tests/registry/test_registry.py index e9677f9..911940a 100644 --- a/tests/registry/test_registry.py +++ b/tests/registry/test_registry.py @@ -138,10 +138,15 @@ def read_package_data_bytes(filename: str) -> bytes: def test_packaged_data_files_exist() -> None: pkg = 'atomref.data' - assert resources.files(pkg).joinpath('periodic_table.csv').is_file() - assert resources.files(pkg).joinpath('covalent.csv').is_file() - assert resources.files(pkg).joinpath('van_der_waals.csv').is_file() - assert resources.files(pkg).joinpath('registry.json').is_file() + for filename in ( + 'periodic_table.csv', + 'covalent.csv', + 'van_der_waals.csv', + 'xh_bond_length.csv', + 'proatomic_density_neutral.zip', + 'registry.json', + ): + assert resources.files(pkg).joinpath(filename).is_file(), filename def test_registry_lists_vdw_sets_but_not_atomic_support_sets() -> None: @@ -361,6 +366,7 @@ def test_list_quantities_and_quantity_info() -> None: 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', + 'proatomic_density', ) info = ar.get_quantity_info('atomic_radius') @@ -369,6 +375,10 @@ def test_list_quantities_and_quantity_info() -> None: assert info.units == 'angstrom' assert 'support' in (info.description or '') + proatomic = ar.get_quantity_info('proatomic_density') + assert proatomic.domain == 'element' + assert proatomic.units == 'electron/bohr^3' + def test_rahm_note_no_longer_claims_it_is_classified_as_vdw() -> None: info = ar.get_dataset_info(ar.DatasetRef('atomic_radius', 'rahm2016')) diff --git a/tools/README.md b/tools/README.md index 943900d..912295e 100644 --- a/tools/README.md +++ b/tools/README.md @@ -5,6 +5,10 @@ release preparation. ## Scripts +- `build_proatomic_density_snapshot.py` — verify the pinned local + `atomref-proatoms` 2.0.0 source and write or check the deterministic neutral + H–Lr consumer ZIP. This is a maintainer-only tool and performs no network + access. - `check_dist.py` — verify that wheel and source-distribution artifacts contain the key files expected by the project. - `check_notebooks.py` — validate notebook JSON and execute notebook code cells. @@ -26,5 +30,24 @@ python tools/gen_readme.py python tools/release_check.py ``` +## Neutral proatomic-density snapshot + +Run the snapshot builder against the immutable local reference dataset. Write +mode regenerates the packaged archive; check mode rebuilds it in memory and +requires an exact byte-for-byte match: + +```bash +python tools/build_proatomic_density_snapshot.py \ + --source-dir ../atomref-proatoms-reference-v2.0.0/upstream/data/profiles/pbe0_sfx2c_dyallv4z_h-lr_spherical_v2 \ + --write + +python tools/build_proatomic_density_snapshot.py \ + --source-dir ../atomref-proatoms-reference-v2.0.0/upstream/data/profiles/pbe0_sfx2c_dyallv4z_h-lr_spherical_v2 \ + --check +``` + +The upstream project is not an `atomref` dependency. Keep its complete source +data outside this repository and do not edit the generated ZIP by hand. + The main project README is generated from the documentation home page. To change `README.md`, edit `docs/index.md` and then run `python tools/gen_readme.py`. diff --git a/tools/build_proatomic_density_snapshot.py b/tools/build_proatomic_density_snapshot.py new file mode 100644 index 0000000..fc3f738 --- /dev/null +++ b/tools/build_proatomic_density_snapshot.py @@ -0,0 +1,563 @@ +#!/usr/bin/env python3 +"""Build the deterministic neutral H-Lr proatomic-density snapshot. + +This maintainer-only tool consumes the pinned local atomref-proatoms 2.0.0 +dataset. It performs no network access and keeps the complete upstream source +outside the atomref package. +""" + +from __future__ import annotations + +import argparse +import csv +from dataclasses import dataclass +import hashlib +import io +import json +import math +from pathlib import Path +import sys +from typing import Mapping +import zipfile + + +REPO_ROOT = Path(__file__).resolve().parents[1] +SRC = REPO_ROOT / "src" +if str(SRC) not in sys.path: + sys.path.insert(0, str(SRC)) + +from atomref.elements import iter_elements # noqa: E402 + + +SOURCE_DATASET_ID = "pbe0_sfx2c_dyallv4z_h-lr_spherical_v2" +SOURCE_DATA_VERSION = "2.0.0" +SOURCE_SCHEMA_VERSION = "atomref.proatoms.profile_dataset.v1" +PROFILES_SHA256 = "b5520ab009542d52098dd6dbb920966d8d13377a4a5004f584a7bd15cd41c299" +METADATA_SHA256 = "32c833ca69fa0f7eb9ed32841aafc638123ff872861e636156610e417fc4c514" +BASIS_ID = "dyall-v4z" +BASIS_SHA256 = "0ee543855f8b1e7fbe9868d4abb844d8e8cc8b8c2694067b2b40de014bb4be94" + +OUTPUT_PATH = REPO_ROOT / "src" / "atomref" / "data" / ( + "proatomic_density_neutral.zip" +) +ARCHIVE_MEMBER = "proatomic_density_neutral.csv" +RADIUS_COLUMN = "r_bohr" +PUBLIC_MAX_RADIUS_BOHR = 20.0 +EXPECTED_LAST_BELOW_BOHR = 19.865456344881434 +EXPECTED_BRACKET_BOHR = 20.1644204667093 +EXPECTED_RETAINED_ROWS = 1127 +EXPECTED_Z = tuple(range(1, 104)) + +# Scale-free tolerance used only when rejecting numerical upward noise. The +# pinned table is strictly decreasing, and its smallest relative decrease is +# about 4.20e-12, more than four times this tolerance. +MONOTONIC_REL_TOL = 1.0e-12 + + +class SnapshotError(RuntimeError): + """Raised when pinned input or generated output violates the contract.""" + + +@dataclass(frozen=True, slots=True) +class Snapshot: + """Generated snapshot bytes and concise validation facts.""" + + csv_bytes: bytes + archive_bytes: bytes + selected_profiles: int + retained_rows: int + last_below_bohr: float + bracket_bohr: float + + +def _sha256(data: bytes) -> str: + """Return the lowercase SHA-256 hex digest for ``data``.""" + + return hashlib.sha256(data).hexdigest() + + +def _read_pinned(path: Path, *, expected_sha256: str, label: str) -> bytes: + """Read one source file and reject any byte-level identity mismatch.""" + + try: + data = path.read_bytes() + except OSError as exc: + raise SnapshotError(f"cannot read {label}: {path}") from exc + actual = _sha256(data) + if actual != expected_sha256: + raise SnapshotError( + f"{label} SHA-256 mismatch: expected {expected_sha256}, got {actual}" + ) + return data + + +def _mapping(value: object, *, what: str) -> Mapping[str, object]: + """Require a string-keyed JSON object.""" + + if not isinstance(value, dict) or not all( + isinstance(key, str) for key in value + ): + raise SnapshotError(f"invalid metadata: {what} must be an object") + return value + + +def _integer(value: object, *, what: str) -> int: + """Require a JSON integer while rejecting booleans.""" + + if not isinstance(value, int) or isinstance(value, bool): + raise SnapshotError(f"invalid metadata: {what} must be an integer") + return value + + +def _expect(mapping: Mapping[str, object], key: str, expected: object) -> None: + """Require one exact metadata identity value.""" + + actual = mapping.get(key) + if actual != expected: + raise SnapshotError( + f"invalid metadata {key!r}: expected {expected!r}, got {actual!r}" + ) + + +def _load_metadata(data: bytes) -> Mapping[str, object]: + """Parse and validate pinned dataset and scientific identity metadata.""" + + try: + raw = json.loads(data) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise SnapshotError("metadata.json is not valid UTF-8 JSON") from exc + metadata = _mapping(raw, what="top level") + + expected_top_level = { + "schema_version": SOURCE_SCHEMA_VERSION, + "profile_data_version": SOURCE_DATA_VERSION, + "dataset_id": SOURCE_DATASET_ID, + "basis_id": BASIS_ID, + "basis_sha256": BASIS_SHA256, + "density_model": ( + "self_consistent_fractional_occupation_spherical_uks" + ), + } + for key, expected in expected_top_level.items(): + _expect(metadata, key, expected) + + method = _mapping(metadata.get("method"), what="method") + for key, expected in { + "scf_type": "UKS", + "xc": "PBE0", + "relativity": "sf-X2C-1e", + "spherical_basis": True, + "basis_id": BASIS_ID, + "basis_sha256": BASIS_SHA256, + }.items(): + _expect(method, key, expected) + + units = _mapping(metadata.get("units"), what="units") + _expect(units, "r", "bohr") + _expect(units, "rho", "electron/bohr^3") + + profile_grid = _mapping(metadata.get("profile_grid"), what="profile_grid") + for key, expected in { + "type": "log", + "r_min_bohr": 1.0e-6, + "r_max_bohr": 60.0, + "n": 1200, + }.items(): + _expect(profile_grid, key, expected) + return metadata + + +def _element_symbols_by_z() -> dict[int, str]: + """Return the H-Lr symbol mapping from atomref's element registry.""" + + symbols = { + element.z: element.symbol + for element in iter_elements() + if element.z in EXPECTED_Z + } + if tuple(sorted(symbols)) != EXPECTED_Z: + raise SnapshotError("atomref element registry does not cover exact Z=1..103") + return symbols + + +def _select_neutral_columns( + metadata: Mapping[str, object], +) -> tuple[tuple[int, str], ...]: + """Select exactly one metadata-declared neutral CSV column per H-Lr Z.""" + + states = _mapping(metadata.get("states"), what="states") + columns = _mapping(metadata.get("columns"), what="columns") + symbols_by_z = _element_symbols_by_z() + + selected_states: dict[int, tuple[str, Mapping[str, object]]] = {} + for state_id, value in states.items(): + state = _mapping(value, what=f"state {state_id!r}") + charge = _integer(state.get("charge"), what=f"state {state_id!r} charge") + if charge != 0: + continue + z = _integer(state.get("z"), what=f"state {state_id!r} z") + if z in selected_states: + other = selected_states[z][0] + raise SnapshotError( + f"multiple neutral states for Z={z}: {other!r} and {state_id!r}" + ) + selected_states[z] = (state_id, state) + + if tuple(sorted(selected_states)) != EXPECTED_Z: + actual = tuple(sorted(selected_states)) + raise SnapshotError( + f"neutral-state coverage must be exact Z=1..103, got {actual!r}" + ) + + columns_by_state: dict[str, list[tuple[str, Mapping[str, object]]]] = {} + for column_name, value in columns.items(): + column = _mapping(value, what=f"column {column_name!r}") + state_id = column.get("state_id") + if not isinstance(state_id, str) or not state_id: + raise SnapshotError( + f"invalid metadata: column {column_name!r} has no state_id" + ) + columns_by_state.setdefault(state_id, []).append((column_name, column)) + + selected_columns: list[tuple[int, str]] = [] + for z in EXPECTED_Z: + state_id, state = selected_states[z] + matches = columns_by_state.get(state_id, []) + if len(matches) != 1: + raise SnapshotError( + f"selected state {state_id!r} maps to {len(matches)} CSV columns" + ) + column_name, column = matches[0] + expected_column = f"rho_e_bohr3__{state_id}" + if column_name != expected_column: + raise SnapshotError( + f"selected state {state_id!r} uses unexpected column {column_name!r}" + ) + + for key in ("symbol", "z", "charge", "electron_count", "multiplicity"): + if state.get(key) != column.get(key): + raise SnapshotError( + f"state/column metadata disagree for {state_id!r}: {key}" + ) + if state.get("symbol") != symbols_by_z[z]: + raise SnapshotError( + f"selected symbol for Z={z} disagrees with atomref element registry" + ) + electron_count = _integer( + state.get("electron_count"), + what=f"state {state_id!r} electron_count", + ) + if electron_count != z: + raise SnapshotError(f"selected state {state_id!r} is not neutral") + selected_columns.append((z, column_name)) + return tuple(selected_columns) + + +def _parse_retained_csv( + profiles_data: bytes, + metadata: Mapping[str, object], + selected_columns: tuple[tuple[int, str], ...], +) -> tuple[bytes, float, float, int]: + """Validate source rows and return the decimal-preserving consumer CSV.""" + + try: + source_text = profiles_data.decode("utf-8") + except UnicodeDecodeError as exc: + raise SnapshotError("profiles.csv is not valid UTF-8") from exc + + source_handle = io.StringIO(source_text, newline="") + reader = csv.reader(source_handle) + try: + header = next(reader) + except StopIteration as exc: + raise SnapshotError("profiles.csv is empty") from exc + except csv.Error as exc: + raise SnapshotError("profiles.csv header is invalid") from exc + + if not header or header[0] != RADIUS_COLUMN: + raise SnapshotError(f"profiles.csv must start with {RADIUS_COLUMN!r}") + if len(header) != len(set(header)): + raise SnapshotError("profiles.csv contains duplicate columns") + + columns = _mapping(metadata.get("columns"), what="columns") + if tuple(header[1:]) != tuple(columns): + raise SnapshotError("profiles.csv header disagrees with column metadata") + header_index = {name: index for index, name in enumerate(header)} + missing = [name for _, name in selected_columns if name not in header_index] + if missing: + raise SnapshotError(f"selected CSV columns are missing: {missing!r}") + + output_rows: list[list[str]] = [] + previous_radius: float | None = None + previous_density: dict[int, float] = {} + last_below: float | None = None + bracket: float | None = None + + try: + for row_number, row in enumerate(reader, start=2): + if len(row) != len(header): + raise SnapshotError( + f"profiles.csv row {row_number} has {len(row)} cells; " + f"expected {len(header)}" + ) + radius_text = row[0] + try: + radius = float(radius_text) + except ValueError as exc: + raise SnapshotError( + f"invalid radius in profiles.csv row {row_number}" + ) from exc + if not math.isfinite(radius) or radius <= 0.0: + raise SnapshotError( + f"radius must be finite and positive in row {row_number}" + ) + if previous_radius is not None and radius <= previous_radius: + raise SnapshotError( + f"radii must strictly increase at profiles.csv row {row_number}" + ) + + consumer_row = [radius_text] + for z, column_name in selected_columns: + density_text = row[header_index[column_name]] + try: + density = float(density_text) + except ValueError as exc: + raise SnapshotError( + f"invalid density for Z={z} in row {row_number}" + ) from exc + if not math.isfinite(density) or density <= 0.0: + raise SnapshotError( + f"density must be finite and positive for Z={z} " + f"in row {row_number}" + ) + previous = previous_density.get(z) + if ( + previous is not None + and density > previous + and not math.isclose( + density, + previous, + rel_tol=MONOTONIC_REL_TOL, + abs_tol=0.0, + ) + ): + raise SnapshotError( + f"density increases beyond tolerance for Z={z} " + f"in row {row_number}" + ) + previous_density[z] = density + consumer_row.append(density_text) + + output_rows.append(consumer_row) + previous_radius = radius + if radius <= PUBLIC_MAX_RADIUS_BOHR: + last_below = radius + else: + bracket = radius + break + except csv.Error as exc: + raise SnapshotError("profiles.csv is invalid") from exc + + if last_below != EXPECTED_LAST_BELOW_BOHR: + raise SnapshotError( + f"unexpected last radius below 20 bohr: {last_below!r}" + ) + if bracket != EXPECTED_BRACKET_BOHR: + raise SnapshotError(f"unexpected radius bracket above 20 bohr: {bracket!r}") + if len(output_rows) != EXPECTED_RETAINED_ROWS: + raise SnapshotError( + f"unexpected retained row count: {len(output_rows)}; " + f"expected {EXPECTED_RETAINED_ROWS}" + ) + + output_handle = io.StringIO(newline="") + writer = csv.writer(output_handle, lineterminator="\n") + writer.writerow([RADIUS_COLUMN, *(f"z{z:03d}" for z in EXPECTED_Z)]) + writer.writerows(output_rows) + return ( + output_handle.getvalue().encode("utf-8"), + last_below, + bracket, + len(output_rows), + ) + + +def build_deterministic_zip(csv_bytes: bytes) -> bytes: + """Return the canonical single-member ZIP bytes for a consumer CSV.""" + + member = zipfile.ZipInfo( + filename=ARCHIVE_MEMBER, + date_time=(1980, 1, 1, 0, 0, 0), + ) + member.compress_type = zipfile.ZIP_DEFLATED + member.create_system = 3 + member.external_attr = 0o100644 << 16 + member.internal_attr = 0 + member.extra = b"" + member.comment = b"" + + buffer = io.BytesIO() + with zipfile.ZipFile( + buffer, + mode="w", + compression=zipfile.ZIP_DEFLATED, + compresslevel=9, + ) as archive: + archive.comment = b"" + archive.writestr( + member, + csv_bytes, + compress_type=zipfile.ZIP_DEFLATED, + compresslevel=9, + ) + return buffer.getvalue() + + +def build_snapshot(profiles_csv: Path, metadata_json: Path) -> Snapshot: + """Build and fully validate the pinned neutral consumer snapshot.""" + + profiles_data = _read_pinned( + profiles_csv, + expected_sha256=PROFILES_SHA256, + label="profiles.csv", + ) + metadata_data = _read_pinned( + metadata_json, + expected_sha256=METADATA_SHA256, + label="metadata.json", + ) + metadata = _load_metadata(metadata_data) + selected_columns = _select_neutral_columns(metadata) + csv_bytes, last_below, bracket, retained_rows = _parse_retained_csv( + profiles_data, + metadata, + selected_columns, + ) + archive_bytes = build_deterministic_zip(csv_bytes) + return Snapshot( + csv_bytes=csv_bytes, + archive_bytes=archive_bytes, + selected_profiles=len(selected_columns), + retained_rows=retained_rows, + last_below_bohr=last_below, + bracket_bohr=bracket, + ) + + +def _resolve_source_paths(args: argparse.Namespace) -> tuple[Path, Path]: + """Resolve either a source directory or two explicit source paths.""" + + if args.source_dir is not None: + if args.profiles_csv is not None or args.metadata_json is not None: + raise SnapshotError( + "use --source-dir or explicit --profiles-csv/--metadata-json, not both" + ) + return args.source_dir / "profiles.csv", args.source_dir / "metadata.json" + if args.profiles_csv is None or args.metadata_json is None: + raise SnapshotError( + "provide --source-dir or both --profiles-csv and --metadata-json" + ) + return args.profiles_csv, args.metadata_json + + +def _write_output(path: Path, data: bytes) -> None: + """Write generated bytes, creating only the selected output directory.""" + + try: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + except OSError as exc: + raise SnapshotError(f"cannot write output: {path}") from exc + + +def _check_output(path: Path, expected: bytes) -> None: + """Require the committed output to match regenerated bytes exactly.""" + + try: + actual = path.read_bytes() + except OSError as exc: + raise SnapshotError(f"cannot read output for check: {path}") from exc + if actual != expected: + raise SnapshotError( + f"snapshot differs: expected SHA-256 {_sha256(expected)}, " + f"got {_sha256(actual)}" + ) + + +def _parser() -> argparse.ArgumentParser: + """Create the snapshot-builder command-line parser.""" + + parser = argparse.ArgumentParser( + description=( + "Build or check the pinned neutral H-Lr proatomic-density snapshot." + ) + ) + parser.add_argument( + "--source-dir", + type=Path, + help=( + "local upstream dataset directory containing profiles.csv and " + "metadata.json" + ), + ) + parser.add_argument("--profiles-csv", type=Path, help="explicit profiles.csv path") + parser.add_argument( + "--metadata-json", + type=Path, + help="explicit metadata.json path", + ) + parser.add_argument( + "--output", + type=Path, + default=OUTPUT_PATH, + help=f"output ZIP path (default: {OUTPUT_PATH})", + ) + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument("--write", action="store_true", help="write the snapshot") + mode.add_argument("--check", action="store_true", help="check exact output bytes") + return parser + + +def main(argv: list[str] | None = None) -> int: + """Run the local snapshot builder in write or byte-check mode.""" + + parser = _parser() + args = parser.parse_args(argv) + try: + profiles_csv, metadata_json = _resolve_source_paths(args) + snapshot = build_snapshot(profiles_csv, metadata_json) + if args.write: + _write_output(args.output, snapshot.archive_bytes) + action = "wrote" + else: + _check_output(args.output, snapshot.archive_bytes) + action = "verified" + except SnapshotError as exc: + print(f"ERROR: {exc}", file=sys.stderr) + return 1 + + print( + f"Source: atomref-proatoms {SOURCE_DATA_VERSION} / {SOURCE_DATASET_ID}" + ) + print( + f"Inputs: profiles sha256={PROFILES_SHA256}; " + f"metadata sha256={METADATA_SHA256}" + ) + print( + f"Selection: {snapshot.selected_profiles} neutral profiles, " + f"Z={EXPECTED_Z[0]}..{EXPECTED_Z[-1]}" + ) + print( + f"Rows: {snapshot.retained_rows}; 20-bohr bracket " + f"{snapshot.last_below_bohr!r} < 20 < {snapshot.bracket_bohr!r}" + ) + print( + f"Output: {action} {args.output} " + f"({len(snapshot.archive_bytes)} bytes, " + f"sha256={_sha256(snapshot.archive_bytes)})" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/check_dist.py b/tools/check_dist.py index df70910..2bd7d31 100644 --- a/tools/check_dist.py +++ b/tools/check_dist.py @@ -3,6 +3,8 @@ from __future__ import annotations import argparse +import hashlib +import io from pathlib import Path import tarfile import zipfile @@ -11,14 +13,17 @@ REQUIRED_WHEEL_MEMBERS = { "atomref/data/periodic_table.csv", "atomref/data/covalent.csv", + "atomref/data/proatomic_density_neutral.zip", "atomref/data/van_der_waals.csv", "atomref/data/registry.json", "atomref/py.typed", + "dist-info/licenses/NOTICE.md", } REQUIRED_SDIST_SUFFIXES = { "src/atomref/data/periodic_table.csv", "src/atomref/data/covalent.csv", + "src/atomref/data/proatomic_density_neutral.zip", "src/atomref/data/van_der_waals.csv", "src/atomref/data/registry.json", "src/atomref/py.typed", @@ -26,6 +31,7 @@ "CHANGELOG.md", "DEV_PLAN.md", "LICENSE", + "NOTICE.md", "pyproject.toml", "notebooks/01-quickstart.ipynb", "notebooks/02-policies-and-assessment.ipynb", @@ -33,6 +39,7 @@ "docs/notebooks/01-quickstart.md", "docs/notebooks/02-policies-and-assessment.md", "docs/notebooks/03-custom-sets-and-discovery.md", + "tools/build_proatomic_density_snapshot.py", "tools/check_notebooks.py", "tools/export_notebooks.py", "tools/gen_readme.py", @@ -40,6 +47,21 @@ "tools/README.md", } +ATTRIBUTION_MARKERS = { + "CC BY 4.0", + "10.5281/zenodo.21291021", + "10.5281/zenodo.21291022", + "pbe0_sfx2c_dyallv4z_h-lr_spherical_v2", +} + +PROATOMIC_SNAPSHOT_MEMBER = "proatomic_density_neutral.csv" +EXPECTED_PROATOMIC_SNAPSHOT_SHA256 = ( + "1ec0318c8bc8f6e71eb3125cf1d4387e4593d7bee8ff5ee5270fbcc32c70ec6b" +) +EXPECTED_PROATOMIC_CSV_SHA256 = ( + "8478da862233c8874e36d65bb5eb762cdb9cbcb0e0278733c0f425ae00c2dcfe" +) + class DistCheckError(RuntimeError): """Raised when a built distribution is missing required members.""" @@ -69,17 +91,101 @@ def _members_matching_suffixes(actual: set[str], suffixes: set[str]) -> set[str] return matched +def _member_matching_suffix( + actual: set[str], + suffix: str, + *, + label: str, +) -> str: + """Return the unique archive member ending in ``suffix``.""" + + matches = sorted(name for name in actual if name.endswith(suffix)) + if len(matches) != 1: + raise DistCheckError( + f"{label} must contain exactly one member ending in {suffix!r}" + ) + return matches[0] + + +def _assert_attribution(text: str, *, member: str, label: str) -> None: + """Raise when a packaged metadata file omits required data attribution.""" + + missing = sorted(marker for marker in ATTRIBUTION_MARKERS if marker not in text) + if missing: + joined = ", ".join(missing) + raise DistCheckError( + f"{label} member {member!r} is missing attribution markers: {joined}" + ) + + +def _decode_utf8(payload: bytes, *, member: str, label: str) -> str: + """Decode one distribution member as UTF-8 with a focused error.""" + + try: + return payload.decode("utf-8") + except UnicodeDecodeError as exc: + raise DistCheckError( + f"{label} member {member!r} is not valid UTF-8" + ) from exc + + +def _assert_proatomic_snapshot(payload: bytes, *, member: str, label: str) -> None: + """Require the exact pinned consumer ZIP and scientific CSV fingerprints.""" + + archive_digest = hashlib.sha256(payload).hexdigest() + if archive_digest != EXPECTED_PROATOMIC_SNAPSHOT_SHA256: + raise DistCheckError( + f"{label} member {member!r} has unexpected proatomic snapshot " + f"SHA-256: {archive_digest}" + ) + + try: + with zipfile.ZipFile(io.BytesIO(payload), mode="r") as snapshot: + members = snapshot.infolist() + if len(members) != 1 or members[0].filename != PROATOMIC_SNAPSHOT_MEMBER: + raise DistCheckError( + f"{label} member {member!r} has an invalid nested snapshot" + ) + csv_payload = snapshot.read(members[0]) + except (RuntimeError, zipfile.BadZipFile, zipfile.LargeZipFile) as exc: + raise DistCheckError( + f"{label} member {member!r} is not a valid snapshot ZIP" + ) from exc + + csv_digest = hashlib.sha256(csv_payload).hexdigest() + if csv_digest != EXPECTED_PROATOMIC_CSV_SHA256: + raise DistCheckError( + f"{label} member {member!r} has unexpected inner CSV SHA-256: " + f"{csv_digest}" + ) + + def check_wheel(path: Path) -> None: """Validate the contents of one built wheel.""" with zipfile.ZipFile(path) as zf: names = set(zf.namelist()) - matched = { - member - for member in REQUIRED_WHEEL_MEMBERS - if any(name.endswith(member) for name in names) - } - _assert_members_present(matched, REQUIRED_WHEEL_MEMBERS, label=path.name) + matched = _members_matching_suffixes(names, REQUIRED_WHEEL_MEMBERS) + _assert_members_present(matched, REQUIRED_WHEEL_MEMBERS, label=path.name) + + snapshot_member = _member_matching_suffix( + names, + "atomref/data/proatomic_density_neutral.zip", + label=path.name, + ) + _assert_proatomic_snapshot( + zf.read(snapshot_member), + member=snapshot_member, + label=path.name, + ) + + for suffix in ( + "atomref/data/registry.json", + "dist-info/licenses/NOTICE.md", + ): + member = _member_matching_suffix(names, suffix, label=path.name) + text = _decode_utf8(zf.read(member), member=member, label=path.name) + _assert_attribution(text, member=member, label=path.name) def check_sdist(path: Path) -> None: @@ -87,8 +193,38 @@ def check_sdist(path: Path) -> None: with tarfile.open(path, "r:gz") as tf: names = {member.name for member in tf.getmembers()} - matched = _members_matching_suffixes(names, REQUIRED_SDIST_SUFFIXES) - _assert_members_present(matched, REQUIRED_SDIST_SUFFIXES, label=path.name) + matched = _members_matching_suffixes(names, REQUIRED_SDIST_SUFFIXES) + _assert_members_present(matched, REQUIRED_SDIST_SUFFIXES, label=path.name) + + snapshot_member = _member_matching_suffix( + names, + "src/atomref/data/proatomic_density_neutral.zip", + label=path.name, + ) + snapshot_file = tf.extractfile(snapshot_member) + if snapshot_file is None: + raise DistCheckError( + f"{path.name} member {snapshot_member!r} is not a regular file" + ) + _assert_proatomic_snapshot( + snapshot_file.read(), + member=snapshot_member, + label=path.name, + ) + + for suffix in ("src/atomref/data/registry.json", "NOTICE.md"): + member = _member_matching_suffix(names, suffix, label=path.name) + extracted = tf.extractfile(member) + if extracted is None: + raise DistCheckError( + f"{path.name} member {member!r} is not a regular file" + ) + text = _decode_utf8( + extracted.read(), + member=member, + label=path.name, + ) + _assert_attribution(text, member=member, label=path.name) def main() -> None: diff --git a/tools/check_registry.py b/tools/check_registry.py index a166a8b..4072e42 100644 --- a/tools/check_registry.py +++ b/tools/check_registry.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 -"""Validate packaged registry metadata against bundled CSV tables.""" +"""Validate packaged registry metadata against bundled data tables.""" from __future__ import annotations from collections import defaultdict from dataclasses import asdict from importlib import import_module +import math from pathlib import Path import sys from typing import Iterable @@ -17,6 +18,32 @@ _ALLOWED_USAGE_ROLES = {"target", "support"} _ALLOWED_STORAGE_KINDS = {"element_scalar_csv", "element_radial_csv_zip"} +_RADIAL_REQUIRED_TEXT_FIELDS = ( + "member", + "radius_column", + "density_column_pattern", + "native_coordinate_unit", + "native_density_unit", + "interpolation_contract", + "charge_scope", + "source_project", + "source_release", + "source_dataset_id", + "basis_id", + "profile_data_version", + "electronic_method", + "scf_model", + "relativity", + "data_license", + "data_license_url", + "concept_doi", + "version_doi", +) +_RADIAL_REQUIRED_SHA256_FIELDS = ( + "source_profiles_sha256", + "source_metadata_sha256", + "basis_sha256", +) def _load_atomref_module(): @@ -59,6 +86,158 @@ def _validate_alias_collisions(errors: list[str]) -> None: seen[key] = set_id +def _is_finite_number(value: object) -> bool: + return ( + isinstance(value, (int, float)) + and not isinstance(value, bool) + and math.isfinite(float(value)) + ) + + +def _validate_radial_storage(ref, info, errors: list[str]) -> None: + storage = info.storage + if storage is None: + return + + if storage.get("format") != "wide_csv_zip": + errors.append( + f"unsupported radial storage format for {ref!r}: " + f"{storage.get('format')!r}" + ) + + for field in _RADIAL_REQUIRED_TEXT_FIELDS: + value = storage.get(field) + if not isinstance(value, str) or not value.strip(): + errors.append( + f"missing or invalid radial metadata {field!r} for {ref!r}" + ) + + hexadecimal = frozenset("0123456789abcdef") + for field in _RADIAL_REQUIRED_SHA256_FIELDS: + value = storage.get(field) + if ( + not isinstance(value, str) + or len(value) != 64 + or any(character not in hexadecimal for character in value) + ): + errors.append(f"invalid radial SHA-256 {field!r} for {ref!r}") + + retained_rows = storage.get("retained_rows") + if ( + not isinstance(retained_rows, int) + or isinstance(retained_rows, bool) + or retained_rows <= 0 + ): + errors.append(f"invalid retained_rows for {ref!r}: {retained_rows!r}") + + public_limit = storage.get("public_max_radius_bohr") + if not _is_finite_number(public_limit) or float(public_limit) <= 0.0: + errors.append( + f"invalid public_max_radius_bohr for {ref!r}: {public_limit!r}" + ) + + bracket = storage.get("retained_bracketing_radius_bohr") + if not _is_finite_number(bracket): + errors.append( + f"invalid retained_bracketing_radius_bohr for {ref!r}: {bracket!r}" + ) + elif _is_finite_number(public_limit) and float(bracket) <= float(public_limit): + errors.append( + f"radial bracket does not exceed the public limit for {ref!r}" + ) + + tolerance = storage.get("monotonicity_relative_tolerance") + if not _is_finite_number(tolerance) or float(tolerance) < 0.0: + errors.append( + f"invalid monotonicity_relative_tolerance for {ref!r}: " + f"{tolerance!r}" + ) + + if storage.get("native_density_unit") != info.units: + errors.append( + f"radial native density unit mismatch for {ref!r}: " + f"{storage.get('native_density_unit')!r} != {info.units!r}" + ) + + +def _validate_radial_values(ref, info, dataset, errors: list[str]) -> None: + storage = info.storage + if storage is None: + return + + radii = dataset.radii + if not radii: + errors.append(f"radial dataset has no radii for {ref!r}") + else: + if any(not math.isfinite(radius) or radius <= 0.0 for radius in radii): + errors.append( + f"radial dataset has nonpositive or non-finite radii: {ref!r}" + ) + if any(right <= left for left, right in zip(radii, radii[1:])): + errors.append(f"radial dataset radii do not strictly increase: {ref!r}") + + retained_rows = storage.get("retained_rows") + if ( + isinstance(retained_rows, int) + and not isinstance(retained_rows, bool) + and len(radii) != retained_rows + ): + errors.append( + f"radial row-count mismatch for {ref!r}: loaded {len(radii)}, " + f"declared {retained_rows}" + ) + + public_limit = storage.get("public_max_radius_bohr") + bracket = storage.get("retained_bracketing_radius_bohr") + if _is_finite_number(public_limit) and radii: + if len(radii) < 2 or not ( + radii[-2] <= float(public_limit) < radii[-1] + ): + errors.append( + f"radial grid does not retain exactly one public-limit bracket " + f"for {ref!r}" + ) + if _is_finite_number(bracket) and radii and radii[-1] != float(bracket): + errors.append( + f"radial bracket mismatch for {ref!r}: loaded {radii[-1]!r}, " + f"declared {bracket!r}" + ) + + tolerance = storage.get("monotonicity_relative_tolerance") + usable_tolerance = ( + float(tolerance) + if _is_finite_number(tolerance) and float(tolerance) >= 0.0 + else None + ) + for z, profile in enumerate(dataset.profiles_by_z): + if z == 0 or profile is None: + continue + if len(profile) != len(radii): + errors.append( + f"radial profile length mismatch for {ref!r}, Z={z}: " + f"{len(profile)} != {len(radii)}" + ) + continue + if any(not math.isfinite(value) or value <= 0.0 for value in profile): + errors.append( + f"radial profile has nonpositive or non-finite values for " + f"{ref!r}, Z={z}" + ) + if usable_tolerance is not None and any( + current > previous + and not math.isclose( + current, + previous, + rel_tol=usable_tolerance, + abs_tol=0.0, + ) + for previous, current in zip(profile, profile[1:]) + ): + errors.append( + f"radial profile increases beyond tolerance for {ref!r}, Z={z}" + ) + + def _validate_dataset_metadata(errors: list[str]) -> None: ar = _load_atomref_module() quantities = set(ar.list_quantities()) @@ -127,6 +306,7 @@ def _validate_dataset_metadata(errors: list[str]) -> None: f"invalid radial density-column pattern for {ref!r}: " f"{density_pattern!r}" ) + _validate_radial_storage(ref, info, errors) values_by_z = ( dataset.values_by_z @@ -160,6 +340,9 @@ def _validate_dataset_metadata(errors: list[str]) -> None: ) ) + if isinstance(dataset, ar.ElementRadialSet): + _validate_radial_values(ref, info, dataset, errors) + if coverage is not None: expected = { "n_values": len(covered_z), From 721c1f0ea5d8b5a5d43ac4e8f8626f09ded70551 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Sun, 12 Jul 2026 23:19:53 +0300 Subject: [PATCH 04/23] feat: add proatomic density evaluation --- .gitignore | 3 - docs/api/proatoms.md | 7 + docs/guide/proatomic_density.md | 48 ++++ mkdocs.yml | 2 + src/atomref/__init__.py | 22 ++ src/atomref/proatoms.py | 442 ++++++++++++++++++++++++++++++++ tests/meta/test_public_api.py | 10 + tests/proatoms/test_density.py | 364 ++++++++++++++++++++++++++ 8 files changed, 895 insertions(+), 3 deletions(-) create mode 100644 docs/api/proatoms.md create mode 100644 docs/guide/proatomic_density.md create mode 100644 src/atomref/proatoms.py create mode 100644 tests/proatoms/test_density.py diff --git a/.gitignore b/.gitignore index 76cee96..b7faf40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# user-specific -atomref-development.code-workspace - # Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] diff --git a/docs/api/proatoms.md b/docs/api/proatoms.md new file mode 100644 index 0000000..581dece --- /dev/null +++ b/docs/api/proatoms.md @@ -0,0 +1,7 @@ +# atomref.proatoms + +This module exposes immutable neutral proatomic-density profiles and scalar +positive-region log-log evaluation. See the [proatomic-density guide](../guide/proatomic_density.md) +for provenance, units, range, interpolation, and limitations. + +::: atomref.proatoms diff --git a/docs/guide/proatomic_density.md b/docs/guide/proatomic_density.md new file mode 100644 index 0000000..2136135 --- /dev/null +++ b/docs/guide/proatomic_density.md @@ -0,0 +1,48 @@ +# Proatomic density + +`atomref` supplies frozen neutral spherical proatomic-density profiles for H +through Lr. They come from the `atomref-proatoms` 2.0.0 dataset +`pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`: PBE0, self-consistent spherical +fractional-occupation UKS, spin-free one-electron X2C, and the dyall-v4z basis. +The exact dataset metadata, source hashes, CC BY 4.0 attribution, and DOIs are +available through `get_proatomic_density_set_info()`. + +Evaluate one scalar coordinate at a time: + +```python +import atomref as ar + +rho = ar.get_proatomic_density( + "O", + 0.75, + radius_unit="angstrom", + density_unit="electron/bohr^3", +) + +profile = ar.get_proatomic_density_profile("O") +rho_at_1_5_bohr = profile(1.5, radius_unit="bohr") +``` + +Radius and density units are independent. Radius coordinates accept `angstrom` +(the default) or `bohr`; density output accepts `electron/bohr^3` (the default) +or `electron/angstrom^3`. + +The supported public interval is exactly 0 through 20 bohr, inclusive. The +stored snapshot retains one source point above 20 bohr only to bracket the +endpoint. Between positive stored knots, evaluation uses the dependency-free +`loglog_positive_bracketed_v1` contract: linear interpolation in `log(r)` and +`log(rho)`. Exact knots return their stored values. At zero and below the first +finite grid point, the first stored density is returned; this is a finite-grid +convention, not an exact evaluation at the nucleus. Negative, non-finite, and +above-domain radii raise `ValueError`; there is no extrapolation or zero fill. + +Elements may be supplied as symbols or integer atomic numbers. Symbols follow +the package's normal element handling, and `D` and `T` use H's electronic +profile. Invalid values and elements beyond Lr return `None`; no neighboring- +element substitution, correlation, ionic selection, or scalar `ValuePolicy` is +applied. + +The ZIP snapshot loads lazily through `get_builtin_set()`. Loaded sets, shared +grids, stored values, and cached profile views are immutable. This API describes +method-, basis-, state-, and sphericalization-defined isolated-atom references, +not unique basis-independent atomic observables or molecular densities. diff --git a/mkdocs.yml b/mkdocs.yml index e0952f2..ef01733 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -23,6 +23,7 @@ nav: - Quickstart: guide/quickstart.md - Policies: guide/policies.md - Custom sets: guide/custom_sets.md + - Proatomic density: guide/proatomic_density.md - Non-goals: guide/non_goals.md - Datasets: - Overview: datasets/index.md @@ -46,5 +47,6 @@ nav: - atomref.registry: api/registry.md - atomref.transfer: api/transfer.md - atomref.policy: api/policy.md + - atomref.proatoms: api/proatoms.md - atomref.radii: api/radii.md - atomref.xh: api/xh.md diff --git a/src/atomref/__init__.py b/src/atomref/__init__.py index 9d37135..af9f4db 100644 --- a/src/atomref/__init__.py +++ b/src/atomref/__init__.py @@ -9,6 +9,18 @@ is_valid_element_symbol, ) from .policy import LookupResult, ValuePolicy, get_value, lookup_value +from .proatoms import ( + BOHR_TO_ANGSTROM, + DEFAULT_PROATOMIC_DENSITY_SET, + ProatomicDensityProfile, + ProatomicDensitySet, + get_proatomic_density, + get_proatomic_density_profile, + get_proatomic_density_set, + get_proatomic_density_set_info, + list_proatomic_density_set_infos, + list_proatomic_density_sets, +) from .radii import ( DEFAULT_COVALENT_POLICY, DEFAULT_VDW_POLICY, @@ -81,6 +93,16 @@ "ValuePolicy", "lookup_value", "get_value", + "BOHR_TO_ANGSTROM", + "DEFAULT_PROATOMIC_DENSITY_SET", + "ProatomicDensityProfile", + "ProatomicDensitySet", + "list_proatomic_density_sets", + "list_proatomic_density_set_infos", + "get_proatomic_density_set", + "get_proatomic_density_set_info", + "get_proatomic_density_profile", + "get_proatomic_density", "RadiiPolicy", "RadiiElementAssessment", "RadiiPolicyAssessment", diff --git a/src/atomref/proatoms.py b/src/atomref/proatoms.py new file mode 100644 index 0000000..2f406d5 --- /dev/null +++ b/src/atomref/proatoms.py @@ -0,0 +1,442 @@ +"""Neutral spherical proatomic-density profiles and scalar evaluation.""" + +from __future__ import annotations + +from bisect import bisect_left +from collections.abc import Mapping +from dataclasses import dataclass, field +from functools import lru_cache +import math +from typing import Literal + +from .elements import Element, get_element, iter_elements +from .errors import DatasetError +from .registry import ( + DatasetInfo, + DatasetRef, + ElementRadialSet, + _normalize_element_domain_symbol, + get_builtin_set, + get_dataset_info, + list_dataset_ids, + list_dataset_infos, +) + + +DEFAULT_PROATOMIC_DENSITY_SET = "pbe0_sfx2c_dyallv4z_h-lr_neutral_v2" +"""Identifier of the default packaged neutral proatomic-density set.""" + +BOHR_TO_ANGSTROM = 0.529177210903 +"""Bohr radius in angstrom, used for public coordinate conversion.""" + +_QUANTITY = "proatomic_density" +_NATIVE_RADIUS_UNIT = "bohr" +_NATIVE_DENSITY_UNIT = "electron/bohr^3" +_ANGSTROM_DENSITY_UNIT = "electron/angstrom^3" +_INTERPOLATION_CONTRACT = "loglog_positive_bracketed_v1" + +ProatomicDensitySet = ElementRadialSet +"""Loaded immutable element-indexed proatomic-density dataset.""" + + +def _require_storage(info: DatasetInfo) -> Mapping[str, object]: + """Return density storage metadata or raise a clear dataset error.""" + + storage = info.storage + if not isinstance(storage, Mapping): + raise DatasetError(f"missing storage metadata for dataset: {info.ref!r}") + if storage.get("native_coordinate_unit") != _NATIVE_RADIUS_UNIT: + raise DatasetError( + f"unsupported native coordinate unit for dataset: {info.ref!r}" + ) + if storage.get("native_density_unit") != _NATIVE_DENSITY_UNIT: + raise DatasetError(f"unsupported native density unit for dataset: {info.ref!r}") + if storage.get("interpolation_contract") != _INTERPOLATION_CONTRACT: + raise DatasetError( + f"unsupported interpolation contract for dataset: {info.ref!r}" + ) + return storage + + +def _coerce_public_max_radius(storage: Mapping[str, object], ref: DatasetRef) -> float: + """Return the finite positive public radius limit declared by metadata.""" + + try: + public_max = float(storage["public_max_radius_bohr"]) + except (KeyError, TypeError, ValueError) as exc: + raise DatasetError(f"invalid public radius limit for dataset: {ref!r}") from exc + if not math.isfinite(public_max) or public_max <= 0.0: + raise DatasetError(f"invalid public radius limit for dataset: {ref!r}") + return public_max + + +@dataclass(frozen=True, slots=True) +class ProatomicDensityProfile: + """Immutable view of one neutral spherical proatomic-density profile. + + Radii and stored densities use the dataset's native units, bohr and + electron/bohr^3. Calling the profile evaluates one scalar coordinate using + positive-region log-log interpolation. The public domain ends at the + dataset-declared maximum (20 bohr for the packaged neutral H-Lr set). At the + origin and below the first finite grid point, the first stored value is + returned as a finite-grid convention rather than an exact nuclear value. + """ + + dataset: ElementRadialSet = field(repr=False) + atomic_number: int + symbol: str = field(init=False) + _densities: tuple[float, ...] = field(init=False, repr=False) + _log_radii: tuple[float, ...] = field(init=False, repr=False) + _log_densities: tuple[float, ...] = field(init=False, repr=False) + _public_max_radius_bohr: float = field(init=False, repr=False) + + def __post_init__(self) -> None: + """Validate and precompute the positive log-log interpolation data.""" + + if self.dataset.ref != self.dataset.info.ref: + raise DatasetError( + "radial dataset reference does not match its metadata reference: " + f"{self.dataset.ref!r} != {self.dataset.info.ref!r}" + ) + element = _get_element_by_atomic_number(self.atomic_number) + if element is None: + raise DatasetError( + f"invalid atomic number for proatomic-density profile: " + f"{self.atomic_number!r}" + ) + object.__setattr__(self, "symbol", element.symbol) + + storage = _require_storage(self.dataset.info) + radii = self.dataset.radii + densities = self.dataset.get(self.atomic_number) + if densities is None: + raise DatasetError( + f"profile Z={self.atomic_number} is absent from {self.dataset.ref!r}" + ) + if len(radii) != len(densities) or not radii: + raise DatasetError( + f"radial grid/profile length mismatch for {self.dataset.ref!r}, " + f"Z={self.atomic_number}" + ) + if any(not math.isfinite(radius) or radius <= 0.0 for radius in radii): + raise DatasetError( + f"radial grid must be finite and positive for {self.dataset.ref!r}" + ) + if any(right <= left for left, right in zip(radii, radii[1:])): + raise DatasetError( + f"radial grid must strictly increase for {self.dataset.ref!r}" + ) + if any(not math.isfinite(value) or value <= 0.0 for value in densities): + raise DatasetError( + f"profile values must be finite and positive for " + f"{self.dataset.ref!r}, Z={self.atomic_number}" + ) + + public_max = _coerce_public_max_radius(storage, self.dataset.ref) + if public_max > radii[-1]: + raise DatasetError( + f"radial grid does not bracket the public limit for " + f"{self.dataset.ref!r}" + ) + + object.__setattr__(self, "_densities", densities) + object.__setattr__(self, "_log_radii", tuple(math.log(r) for r in radii)) + object.__setattr__( + self, + "_log_densities", + tuple(math.log(value) for value in densities), + ) + object.__setattr__(self, "_public_max_radius_bohr", public_max) + + @property + def ref(self) -> DatasetRef: + """Canonical registry reference for the source dataset.""" + + return self.dataset.ref + + @property + def info(self) -> DatasetInfo: + """Curated metadata and provenance for the source dataset.""" + + return self.dataset.info + + @property + def radii(self) -> tuple[float, ...]: + """Shared immutable source grid in bohr, including the endpoint bracket.""" + + return self.dataset.radii + + @property + def densities(self) -> tuple[float, ...]: + """Immutable stored density values in electron/bohr^3.""" + + return self._densities + + @property + def interpolation_contract(self) -> str: + """Stable identifier for this profile's interpolation behavior.""" + + return _INTERPOLATION_CONTRACT + + @property + def public_max_radius_bohr(self) -> float: + """Largest supported public radius coordinate, in bohr.""" + + return self._public_max_radius_bohr + + def evaluate( + self, + radius: float, + *, + radius_unit: Literal["angstrom", "bohr"] = "angstrom", + density_unit: Literal[ + "electron/bohr^3", "electron/angstrom^3" + ] = "electron/bohr^3", + ) -> float: + """Evaluate the density at one radius. + + ``radius_unit`` accepts ``"angstrom"`` or ``"bohr"`` independently + of ``density_unit``, which accepts ``"electron/bohr^3"`` or + ``"electron/angstrom^3"``. Invalid units, negative or non-finite + radii, and coordinates above the dataset-declared public maximum raise + :class:`ValueError`. The packaged neutral H-Lr set uses 20 bohr. + """ + + radius_bohr = _radius_to_bohr( + radius, + radius_unit=radius_unit, + public_max_bohr=self._public_max_radius_bohr, + ) + density = self._evaluate_bohr(radius_bohr) + return _density_from_native(density, density_unit=density_unit) + + def __call__( + self, + radius: float, + *, + radius_unit: Literal["angstrom", "bohr"] = "angstrom", + density_unit: Literal[ + "electron/bohr^3", "electron/angstrom^3" + ] = "electron/bohr^3", + ) -> float: + """Evaluate the density; equivalent to :meth:`evaluate`.""" + + return self.evaluate( + radius, + radius_unit=radius_unit, + density_unit=density_unit, + ) + + def _evaluate_bohr(self, radius_bohr: float) -> float: + """Evaluate one already validated native-coordinate radius.""" + + radii = self.dataset.radii + if radius_bohr <= radii[0]: + return self._densities[0] + + right = bisect_left(radii, radius_bohr) + if right < len(radii) and radii[right] == radius_bohr: + return self._densities[right] + if right == len(radii): + raise DatasetError( + f"radial grid does not bracket radius {radius_bohr!r} bohr for " + f"{self.dataset.ref!r}" + ) + + left = right - 1 + fraction = ( + (math.log(radius_bohr) - self._log_radii[left]) + / (self._log_radii[right] - self._log_radii[left]) + ) + log_density = self._log_densities[left] + fraction * ( + self._log_densities[right] - self._log_densities[left] + ) + density = math.exp(log_density) + if not math.isfinite(density) or density <= 0.0: + raise DatasetError( + f"density interpolation failed for {self.dataset.ref!r}, " + f"Z={self.atomic_number}" + ) + return density + + +def _radius_to_bohr( + radius: float, + *, + radius_unit: Literal["angstrom", "bohr"], + public_max_bohr: float, +) -> float: + """Validate one public radius and convert it to bohr.""" + + if radius_unit not in {"angstrom", "bohr"}: + raise ValueError(f"unknown radius unit: {radius_unit!r}") + try: + value = float(radius) + except (TypeError, ValueError) as exc: + raise ValueError("radius must be a finite non-negative scalar") from exc + if not math.isfinite(value): + raise ValueError("radius must be finite") + if value < 0.0: + raise ValueError("radius must be non-negative") + + public_max = ( + public_max_bohr + if radius_unit == "bohr" + else public_max_bohr * BOHR_TO_ANGSTROM + ) + if value > public_max: + raise ValueError( + f"radius exceeds the public limit of {public_max_bohr:g} bohr" + ) + if radius_unit == "bohr": + return value + radius_bohr = value / BOHR_TO_ANGSTROM + return min(radius_bohr, public_max_bohr) + + +def _density_from_native( + value: float, + *, + density_unit: Literal["electron/bohr^3", "electron/angstrom^3"], +) -> float: + """Convert electron/bohr^3 to the selected output density unit.""" + + if density_unit == _NATIVE_DENSITY_UNIT: + return value + if density_unit == _ANGSTROM_DENSITY_UNIT: + return value / BOHR_TO_ANGSTROM**3 + raise ValueError(f"unknown density unit: {density_unit!r}") + + +def list_proatomic_density_sets() -> tuple[str, ...]: + """List packaged proatomic-density dataset identifiers.""" + + return list_dataset_ids(_QUANTITY) + + +def list_proatomic_density_set_infos() -> tuple[DatasetInfo, ...]: + """Return metadata for all packaged proatomic-density datasets.""" + + return list_dataset_infos(_QUANTITY) + + +def get_proatomic_density_set_info( + set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, +) -> DatasetInfo: + """Return metadata for one packaged proatomic-density dataset or alias.""" + + return get_dataset_info(DatasetRef(_QUANTITY, set_id)) + + +def get_proatomic_density_set( + set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, +) -> ProatomicDensitySet: + """Return one cached immutable packaged proatomic-density dataset.""" + + loaded = get_builtin_set(DatasetRef(_QUANTITY, set_id)) + if not isinstance(loaded, ElementRadialSet): + raise DatasetError( + f"dataset {loaded.ref!r} has a scalar payload; radial dataset required" + ) + return loaded + + +@lru_cache(maxsize=None) +def _get_element_by_atomic_number_cached( + atomic_number: int, +) -> Element | None: + """Return the periodic-table element for a validated integer Z.""" + + return next( + (candidate for candidate in iter_elements() if candidate.z == atomic_number), + None, + ) + + +def _get_element_by_atomic_number(atomic_number: object) -> Element | None: + """Resolve an integer atomic number while rejecting booleans safely.""" + + if not isinstance(atomic_number, int) or isinstance(atomic_number, bool): + return None + return _get_element_by_atomic_number_cached(atomic_number) + + +def _resolve_density_element(element: str | int | None) -> Element | None: + """Resolve a symbol, isotope alias, or integer atomic number.""" + + if isinstance(element, int): + return _get_element_by_atomic_number(element) + if isinstance(element, str) or element is None: + symbol = _normalize_element_domain_symbol(element) + return get_element(symbol) + return None + + +@lru_cache(maxsize=None) +def _get_profile_cached(ref: DatasetRef, atomic_number: int) -> ProatomicDensityProfile: + """Create one shared immutable profile view from a canonical dataset ref.""" + + dataset = get_builtin_set(ref) + if not isinstance(dataset, ElementRadialSet): + raise DatasetError( + f"dataset {dataset.ref!r} has a scalar payload; radial dataset required" + ) + element = _get_element_by_atomic_number(atomic_number) + if element is None: + raise DatasetError(f"unknown atomic number in radial dataset: {atomic_number}") + return ProatomicDensityProfile( + dataset=dataset, + atomic_number=atomic_number, + ) + + +def get_proatomic_density_profile( + element: str | int | None, + *, + set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, +) -> ProatomicDensityProfile | None: + """Return a cached neutral profile, or ``None`` for unsupported elements. + + Element symbols are canonicalized using the package's normal element rules, + and integer atomic numbers are accepted. Deuterium (``D``) and tritium + (``T``) return hydrogen's electronic profile. Invalid values, including + booleans, return ``None``. No substitution, correlation, or ionic selection + is performed. + """ + + resolved = _resolve_density_element(element) + if resolved is None: + return None + info = get_proatomic_density_set_info(set_id) + dataset = get_proatomic_density_set(info.ref.set_id) + if dataset.get(resolved.z) is None: + return None + return _get_profile_cached(dataset.ref, resolved.z) + + +def get_proatomic_density( + element: str | int | None, + radius: float, + *, + set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, + radius_unit: Literal["angstrom", "bohr"] = "angstrom", + density_unit: Literal[ + "electron/bohr^3", "electron/angstrom^3" + ] = "electron/bohr^3", +) -> float | None: + """Evaluate one neutral proatomic density, or return ``None`` if absent. + + The default coordinate unit is angstrom and the default output unit is + electron/bohr^3. The supported interval ends at the dataset-declared public + maximum (20 bohr for the packaged neutral H-Lr set). Evaluation is scalar + and dependency-free. + """ + + profile = get_proatomic_density_profile(element, set_id=set_id) + if profile is None: + return None + return profile( + radius, + radius_unit=radius_unit, + density_unit=density_unit, + ) diff --git a/tests/meta/test_public_api.py b/tests/meta/test_public_api.py index 17a81c9..de199aa 100644 --- a/tests/meta/test_public_api.py +++ b/tests/meta/test_public_api.py @@ -15,6 +15,10 @@ 'RadiiPolicy', 'DEFAULT_COVALENT_POLICY', 'DEFAULT_VDW_POLICY', + 'BOHR_TO_ANGSTROM', + 'DEFAULT_PROATOMIC_DENSITY_SET', + 'ProatomicDensityProfile', + 'ProatomicDensitySet', 'LinearTransfer', 'SubstitutionTransfer', 'get_builtin_set', @@ -35,6 +39,12 @@ 'list_dataset_infos', 'list_radii_sets', 'list_radii_set_infos', + 'get_proatomic_density', + 'get_proatomic_density_profile', + 'get_proatomic_density_set', + 'get_proatomic_density_set_info', + 'list_proatomic_density_sets', + 'list_proatomic_density_set_infos', } diff --git a/tests/proatoms/test_density.py b/tests/proatoms/test_density.py new file mode 100644 index 0000000..f3075ad --- /dev/null +++ b/tests/proatoms/test_density.py @@ -0,0 +1,364 @@ +from __future__ import annotations + +from dataclasses import FrozenInstanceError +import math +from types import MappingProxyType + +import pytest + +import atomref as ar +from atomref.errors import DatasetError +import atomref.proatoms as proatoms +import atomref.registry as registry + + +_DATASET_ID = "pbe0_sfx2c_dyallv4z_h-lr_neutral_v2" + + +def _synthetic_power_law_profile() -> ar.ProatomicDensityProfile: + ref = ar.DatasetRef("proatomic_density", "synthetic_power_law") + info = ar.DatasetInfo( + ref=ref, + domain="element", + units="electron/bohr^3", + name="Synthetic power law", + storage=MappingProxyType( + { + "native_coordinate_unit": "bohr", + "native_density_unit": "electron/bohr^3", + "public_max_radius_bohr": 8.0, + "interpolation_contract": "loglog_positive_bracketed_v1", + } + ), + ) + radii = (0.25, 1.0, 4.0, 8.0) + coefficient = 7.25 + exponent = -2.75 + densities = tuple(coefficient * radius**exponent for radius in radii) + dataset = ar.ElementRadialSet( + ref=ref, + info=info, + radii=radii, + profiles_by_z=(None, densities), + ) + return ar.ProatomicDensityProfile( + dataset=dataset, + atomic_number=1, + ) + + +def test_density_set_discovery_aliases_and_generic_loader_identity() -> None: + assert ar.list_proatomic_density_sets() == (_DATASET_ID,) + assert tuple( + info.ref.set_id for info in ar.list_proatomic_density_set_infos() + ) == (_DATASET_ID,) + + info = ar.get_proatomic_density_set_info("atomref-proatoms neutral v2") + dataset = ar.get_proatomic_density_set("atomref-proatoms neutral v2") + generic = ar.get_builtin_set(ar.DatasetRef("proatomic_density", _DATASET_ID)) + assert info.ref.set_id == _DATASET_ID + assert dataset is generic + assert isinstance(dataset, ar.ProatomicDensitySet) + + +@pytest.mark.parametrize("symbol", ["H", "C", "O", "Fe", "La", "U", "Lr"]) +def test_representative_h_through_lr_profiles_load(symbol: str) -> None: + profile = ar.get_proatomic_density_profile(symbol) + assert profile is not None + assert profile.symbol == symbol + assert profile.atomic_number == ar.get_element(symbol).z + assert profile.dataset is ar.get_proatomic_density_set() + assert profile.ref == profile.info.ref + + +def test_all_h_through_lr_profiles_are_available() -> None: + for element in ar.iter_elements(): + if element.z > 103: + break + profile = ar.get_proatomic_density_profile(element.symbol) + assert profile is not None, element.symbol + assert profile.atomic_number == element.z + + +@pytest.mark.parametrize( + ("atomic_number", "symbol"), + [(1, "H"), (8, "O"), (103, "Lr")], +) +def test_integer_atomic_numbers_use_canonical_cached_profiles( + atomic_number: int, + symbol: str, +) -> None: + profile = ar.get_proatomic_density_profile(atomic_number) + assert profile is ar.get_proatomic_density_profile(symbol) + assert profile is not None + assert profile.atomic_number == atomic_number + assert profile.symbol == symbol + assert ar.get_proatomic_density( + atomic_number, + 1.0, + radius_unit="bohr", + ) == ar.get_proatomic_density(symbol, 1.0, radius_unit="bohr") + + +@pytest.mark.parametrize("atomic_number", [104, 0, -1, True, False]) +def test_invalid_or_unsupported_atomic_numbers_return_none( + atomic_number: int, +) -> None: + assert ar.get_proatomic_density_profile(atomic_number) is None + assert ar.get_proatomic_density(atomic_number, 1.0) is None + + +def test_bool_does_not_poison_integer_atomic_number_resolution_cache() -> None: + proatoms._get_element_by_atomic_number_cached.cache_clear() + assert ar.get_proatomic_density_profile(True) is None + assert ar.get_proatomic_density_profile(1) is ar.get_proatomic_density_profile( + "H" + ) + + +def test_isotopes_invalid_symbols_and_unsupported_elements() -> None: + hydrogen = ar.get_proatomic_density_profile("H") + assert ar.get_proatomic_density_profile("D") is hydrogen + assert ar.get_proatomic_density_profile("T") is hydrogen + assert ar.get_proatomic_density("D", 0.5) == ar.get_proatomic_density("H", 0.5) + assert ar.get_proatomic_density("T", 0.5) == ar.get_proatomic_density("H", 0.5) + for symbol in ("Rf", "Og", "not-an-element", "", None): + assert ar.get_proatomic_density_profile(symbol) is None + assert ar.get_proatomic_density(symbol, 0.5) is None + + +def test_set_and_profile_cache_reuse_and_public_immutability() -> None: + profile = ar.get_proatomic_density_profile("O") + assert profile is not None + assert ar.get_proatomic_density_profile("o") is profile + assert ar.get_proatomic_density_profile("O") is profile + assert isinstance(profile.radii, tuple) + assert isinstance(profile.densities, tuple) + with pytest.raises(FrozenInstanceError): + profile.symbol = "N" + with pytest.raises(TypeError): + profile.densities[0] = 0.0 + with pytest.raises(FrozenInstanceError): + profile.dataset.radii = () + + +def test_profile_identity_is_derived_and_repr_is_concise() -> None: + dataset = ar.get_proatomic_density_set() + profile = ar.ProatomicDensityProfile(dataset=dataset, atomic_number=1) + assert profile.symbol == "H" + assert profile.atomic_number == 1 + assert repr(profile) == ( + "ProatomicDensityProfile(atomic_number=1, symbol='H')" + ) + assert len(repr(profile)) < 100 + with pytest.raises(TypeError, match="unexpected keyword argument 'symbol'"): + ar.ProatomicDensityProfile( + dataset=dataset, + atomic_number=1, + symbol="O", + ) + + +def test_profile_rejects_dataset_metadata_reference_mismatch() -> None: + valid = _synthetic_power_law_profile() + mismatched_info = ar.DatasetInfo( + ref=ar.DatasetRef("proatomic_density", "different"), + domain=valid.info.domain, + units=valid.info.units, + name=valid.info.name, + storage=valid.info.storage, + ) + mismatched = ar.ElementRadialSet( + ref=valid.ref, + info=mismatched_info, + radii=valid.radii, + profiles_by_z=valid.dataset.profiles_by_z, + ) + with pytest.raises(DatasetError, match="reference does not match"): + ar.ProatomicDensityProfile(dataset=mismatched, atomic_number=1) + + +def test_packaged_density_loading_is_lazy_and_shared( + monkeypatch: pytest.MonkeyPatch, +) -> None: + real_reader = registry._read_package_data_bytes + calls = 0 + + def counting_reader(filename: str) -> bytes: + nonlocal calls + calls += 1 + return real_reader(filename) + + registry._load_builtin_set.cache_clear() + registry._load_radial_csv_zip.cache_clear() + proatoms._get_profile_cached.cache_clear() + monkeypatch.setattr(registry, "_read_package_data_bytes", counting_reader) + try: + ar.get_proatomic_density_set_info() + assert calls == 0 + first = ar.get_proatomic_density_profile("O") + assert first is not None + assert calls == 1 + assert ar.get_proatomic_density_profile("O") is first + assert ar.get_proatomic_density("O", 0.75) is not None + assert calls == 1 + finally: + registry._load_builtin_set.cache_clear() + registry._load_radial_csv_zip.cache_clear() + proatoms._get_profile_cached.cache_clear() + + +def test_origin_first_grid_point_and_exact_stored_knots() -> None: + profile = ar.get_proatomic_density_profile("O") + assert profile is not None + assert profile(0.0, radius_unit="bohr") == 358.401594629436 + assert profile(profile.radii[0] / 2.0, radius_unit="bohr") == ( + 358.401594629436 + ) + assert profile(profile.radii[0], radius_unit="bohr") == 358.401594629436 + expected_knots = { + 100: (4.453688832606716e-06, 358.4005490198518), + 500: (0.0017522632545894682, 326.9578641098411), + 1000: (3.0704265133844744, 0.0015857375047883448), + 1125: (19.865456344881434, 1.1086022497194577e-36), + } + for index, (radius, density) in expected_knots.items(): + assert profile.radii[index] == radius + assert profile.densities[index] == density + assert profile(radius, radius_unit="bohr") == density + + +def test_exact_public_endpoint_and_out_of_range_radii() -> None: + profile = ar.get_proatomic_density_profile("O") + assert profile is not None + endpoint = profile(20.0, radius_unit="bohr") + endpoint_angstrom = profile(20.0 * ar.BOHR_TO_ANGSTROM) + assert math.isfinite(endpoint) and endpoint > 0.0 + assert endpoint_angstrom == pytest.approx(endpoint, rel=2.0e-15) + with pytest.raises(ValueError, match="exceeds the public limit"): + profile(math.nextafter(20.0, math.inf), radius_unit="bohr") + with pytest.raises(ValueError, match="exceeds the public limit"): + profile(20.01, radius_unit="bohr") + with pytest.raises(ValueError, match="exceeds the public limit"): + profile(math.nextafter(20.0 * ar.BOHR_TO_ANGSTROM, math.inf)) + + +@pytest.mark.parametrize("radius", [-1.0, math.nan, math.inf, -math.inf]) +def test_negative_and_non_finite_radii_raise(radius: float) -> None: + with pytest.raises(ValueError, match="radius"): + ar.get_proatomic_density("O", radius, radius_unit="bohr") + + +def test_unknown_units_raise() -> None: + with pytest.raises(ValueError, match="unknown radius unit"): + ar.get_proatomic_density("O", 1.0, radius_unit="meter") + with pytest.raises(ValueError, match="unknown density unit"): + ar.get_proatomic_density("O", 1.0, density_unit="kg/m^3") + + +def test_synthetic_power_law_is_exact_under_loglog_interpolation() -> None: + profile = _synthetic_power_law_profile() + coefficient = 7.25 + exponent = -2.75 + for radius in (0.5, 2.0, 6.0): + expected = coefficient * radius**exponent + assert profile(radius, radius_unit="bohr") == pytest.approx( + expected, + rel=2.0e-15, + ) + + +def test_interpolation_is_loglog_positive_and_continuous_at_knots() -> None: + profile = _synthetic_power_law_profile() + left_density = profile.densities[1] + right_density = profile.densities[2] + midpoint = math.sqrt(profile.radii[1] * profile.radii[2]) + expected_loglog = math.sqrt(left_density * right_density) + linear_midpoint = (left_density + right_density) / 2.0 + interpolated = profile(midpoint, radius_unit="bohr") + assert interpolated == pytest.approx(expected_loglog, rel=2.0e-15) + assert not math.isclose(interpolated, linear_midpoint, rel_tol=1.0e-3) + assert interpolated > 0.0 + + for knot in profile.radii[1:-1]: + exact = profile(knot, radius_unit="bohr") + left = profile(math.nextafter(knot, 0.0), radius_unit="bohr") + right = profile(math.nextafter(knot, math.inf), radius_unit="bohr") + assert left == pytest.approx(exact, rel=2.0e-14) + assert right == pytest.approx(exact, rel=2.0e-14) + + +def test_nonpositive_density_data_are_rejected_without_zero_fill() -> None: + valid = _synthetic_power_law_profile() + invalid_dataset = ar.ElementRadialSet( + ref=valid.ref, + info=valid.info, + radii=valid.radii, + profiles_by_z=(None, (valid.densities[0], 0.0, *valid.densities[2:])), + ) + with pytest.raises(DatasetError, match="finite and positive"): + ar.ProatomicDensityProfile( + dataset=invalid_dataset, + atomic_number=1, + ) + + +@pytest.mark.parametrize("symbol", ["H", "O", "Fe", "U", "Lr"]) +def test_packaged_profiles_are_positive_and_non_increasing(symbol: str) -> None: + profile = ar.get_proatomic_density_profile(symbol) + assert profile is not None + assert all(value > 0.0 for value in profile.densities) + assert all( + current <= previous + or math.isclose(current, previous, rel_tol=1.0e-12, abs_tol=0.0) + for previous, current in zip(profile.densities, profile.densities[1:]) + ) + + +def test_radius_and_density_units_are_independent() -> None: + profile = ar.get_proatomic_density_profile("O") + assert profile is not None + radius_bohr = 1.75 + assert ar.BOHR_TO_ANGSTROM == 0.529177210903 + radius_angstrom = radius_bohr * 0.529177210903 + native = profile(radius_bohr, radius_unit="bohr") + equivalent = profile(radius_angstrom, radius_unit="angstrom") + converted = profile( + radius_bohr, + radius_unit="bohr", + density_unit="electron/angstrom^3", + ) + assert equivalent == pytest.approx(native, rel=2.0e-15) + assert converted == pytest.approx( + native / 0.529177210903**3, + rel=2.0e-15, + ) + assert profile.ref == ar.get_proatomic_density_profile("O").ref + assert profile.interpolation_contract == "loglog_positive_bracketed_v1" + + +def test_documented_units_are_function_defaults() -> None: + profile = ar.get_proatomic_density_profile("O") + assert profile is not None + radius_angstrom = 0.75 + assert profile(radius_angstrom) == profile( + radius_angstrom, + radius_unit="angstrom", + density_unit="electron/bohr^3", + ) + assert ar.get_proatomic_density("O", radius_angstrom) == profile(radius_angstrom) + + +def test_missing_density_dataset_raises_dataset_error() -> None: + with pytest.raises(DatasetError, match="unknown dataset id"): + ar.get_proatomic_density_profile("O", set_id="missing") + + +def test_scalar_accessors_remain_narrow_after_density_use() -> None: + assert isinstance( + ar.get_radii_set("covalent", "cordero2008"), + ar.ElementScalarSet, + ) + assert isinstance(ar.get_xh_set("csd_legacy_xh_cno"), ar.ElementScalarSet) + with pytest.raises(DatasetError, match="radial payload; scalar dataset required"): + ar.ValuePolicy(base=ar.get_proatomic_density_set()) From e2683736e14207ed540d1e20dab42cc7dea2761d Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Mon, 13 Jul 2026 22:22:02 +0300 Subject: [PATCH 05/23] docs: adopt two-mode Stage 4 IAS plan --- DEV_PLAN.md | 1100 +++++++++++------ .../c-o-method-comparison.png | Bin 0 -> 80965 bytes docs/assets/ias-method-study/cutoff-radii.png | Bin 0 -> 70603 bytes .../ias-method-study/li-li-symmetry.png | Bin 0 -> 73845 bytes docs/dev/dev_plan.md | 40 +- docs/dev/ias_method_selection.md | 116 ++ docs/guide/notebooks.md | 17 +- mkdocs.yml | 1 + notebooks/04-ias-method-selection-study.ipynb | 1079 ++++++++++++++++ tests/meta/test_notebooks.py | 1 + tools/check_notebooks.py | 1 + 11 files changed, 1915 insertions(+), 440 deletions(-) create mode 100644 docs/assets/ias-method-study/c-o-method-comparison.png create mode 100644 docs/assets/ias-method-study/cutoff-radii.png create mode 100644 docs/assets/ias-method-study/li-li-symmetry.png create mode 100644 docs/dev/ias_method_selection.md create mode 100644 notebooks/04-ias-method-selection-study.ipynb diff --git a/DEV_PLAN.md b/DEV_PLAN.md index 2d40ae4..e36183c 100644 --- a/DEV_PLAN.md +++ b/DEV_PLAN.md @@ -1,8 +1,8 @@ # atomref 0.2.x development plan -> **Status:** third review draft +> **Status:** fourth review draft — Stage 4 replanned after numerical review > **Plan lifecycle state:** `ACTIVE` -> **Primary target:** `atomref 0.2.0` — dataset architecture, neutral proatomic density, IAS analysis, and an executed feature notebook +> **Primary target:** `atomref 0.2.0` — dataset architecture, neutral proatomic density, two-mode pairwise boundary/IAS-proxy analysis, and executed notebooks > **Planned follow-up:** `atomref 0.2.1` — notebook/documentation infrastructure, complete API reference, and README positioning > **Source data:** `atomref-proatoms 2.0.0`, dataset `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2` @@ -15,8 +15,8 @@ repository, but it should remain understandable to a human reviewer. The plan has three scopes: 1. **`0.2.0` binding implementation work** — generic packaged-dataset loading, - neutral radial density data, interpolation, IAS analysis, tests, and one - executed explanatory notebook. + neutral radial density data, interpolation, two-mode pairwise + boundary/IAS-proxy analysis, tests, and executed explanatory notebooks. 2. **`0.2.1` planned documentation work** — direct rendering of saved executed notebooks, richer notebook explanations, complete docstring-based API reference, and a marketing-oriented README rewrite. @@ -135,7 +135,8 @@ Examples: - no runtime dependency on `atomref-proatoms`; - reproducible source verification; - explicit units and failures outside the supported domain; -- exposing all relevant IAS minima rather than hiding them. +- keeping the stable proatomic-boundary mode distinct from the optional + promolecular-minimum mode, with the cutoff and practical resolution explicit. ### 2.2 Preferred first design @@ -156,7 +157,7 @@ Examples: - exact class and function names; - whether two closely related result classes are combined; - precise internal CSV column names; -- exact local-minimum refinement algorithm; +- exact practical valley-refinement implementation; - whether density and IAS implementation share one module or use two modules. ### 2.3 Open decision @@ -165,9 +166,11 @@ An open decision is intentionally left for focused design work during the relevant stage. It MUST be resolved explicitly before implementation of the dependent behavior. -The main open decision in this plan is the exact IAS local-minimum search and -candidate-merging algorithm. The required outputs and invariants are binding; -the numerical search mechanics are not yet final. +The earlier open decision about exhaustive IAS local-minimum enumeration is +resolved by Sections 10 and 11. `0.2.0` uses two explicit modes: a stable +proatomic-boundary mode and a resolution-limited promolecular-minimum mode. +Final public spelling may still be adjusted during Stage 4B, but the scientific +meaning, cutoff policy, default mode, and invariants are binding. ## 3. Agent operating rules @@ -274,30 +277,44 @@ accepted the `0.2.0` functionality and release state. `atomref 0.2.0` should establish the package as a small source of **reference atoms for structure algorithms**, not only a radii lookup package. -The release should support two new workflows: +The release should support these workflows: ```python import atomref as ar rho = ar.get_proatomic_density("O", 0.75) -analysis = ar.estimate_ias_position("C", "O", 1.43) + +boundary = ar.estimate_proatomic_boundary("C", "O", 1.43) +minimum = ar.estimate_promolecular_density_minimum("C", "O", 1.43) + +# One convenience entry point with an explicit policy choice. +selected = ar.estimate_ias_position("C", "O", 1.43, mode="boundary") ``` -The first evaluates a frozen neutral spherical proatomic density. +The density call evaluates a frozen neutral spherical proatomic density. -The second evaluates the promolecular density along the vector between two -atoms and returns a transparent analysis of its minima, including a practical -averaged IAS estimate. +The pairwise helpers expose two related but scientifically different +approximations: + +- **`boundary` mode** returns a stable neutral-proatom divider. It uses exact + homonuclear symmetry, equal proatomic contributions in the meaningful-overlap + region, and the midpoint between fixed low-density contours when the contours + no longer overlap. +- **`minimum` mode** searches for one practically resolved minimum of the summed + promolecular line density inside the meaningful-overlap region. This is the + more Bader-oriented proxy, but it is optional, resolution-limited, and may + report ambiguity or no resolved minimum. The release is successful when a quantum-chemistry, crystallographic, or molecular-software developer can immediately understand: -- what the supplied profiles represent; -- where they came from; +- what the supplied profiles represent and where they came from; - how to evaluate them; -- how the IAS approximation is constructed; +- the difference between the two pairwise modes; +- why `boundary` is the default; +- where the fixed tail cutoff enters; - what numerical domain is supported; -- what the result does and does not claim physically. +- what each result does and does not claim physically. ## 5. Release scope @@ -316,14 +333,20 @@ molecular-software developer can immediately understand: - independent radius-coordinate and density-output unit selection; - a strict public radius domain of `0 <= r <= 20 bohr`; - positive-region log–log interpolation; -- approximate IAS analysis for two atoms separated by a supplied distance; -- discovery and inspection of all relevant local minima; -- global-minimum identification; -- a geometrically interpretable minimum-width measure; -- a dimensionless depth-weighted minima center; -- a weighted positional spread or equivalent ambiguity measure; -- explicit provenance, method, basis, units, limitations, and data licensing; -- one executed and saved notebook for proatomic density and IAS workflows, +- a cached fixed neutral-proatom tail radius for every packaged element at + `1e-4 electron/bohr^3`; +- a stable pairwise proatomic-boundary estimator; +- an optional, cutoff-bounded and resolution-limited promolecular-density + minimum estimator; +- a convenience dispatcher with an explicit `mode` and default + `mode="boundary"`; +- exact homonuclear midpoint behavior in both modes; +- explicit low-density-gap, one-atom-dominance, boundary-dominated, + no-resolved-minimum, and competing-minimum diagnostics; +- pair-reversal and unit-invariance guarantees; +- explicit provenance, method, cutoff, units, limitations, and data licensing; +- an executed supporting notebook explaining the Stage 4 method decision; +- one executed feature notebook for proatomic density and pairwise workflows, including explanatory Markdown cells and plots; - minimum accurate user/API documentation needed to release the functionality; - tests, packaging checks, and release metadata. @@ -348,8 +371,9 @@ molecular-software developer can immediately understand: - removing the duplicate documentation copy of the development plan; - simplifying notebook/documentation CI after the new workflow is stable. -`0.2.1` MUST NOT change density or IAS numerical results except to fix a -confirmed defect with an explicit changelog entry and regression test. +`0.2.1` MUST NOT change density, cutoff radii, pairwise mode semantics, or +selected coordinates except to fix a confirmed defect with an explicit +changelog entry and regression test. ### 5.3 Excluded from `0.2.0` and `0.2.1` @@ -755,7 +779,9 @@ tools/build_proatomic_density_snapshot.py tests/proatoms/test_dataset.py tests/proatoms/test_density.py tests/proatoms/test_ias.py -notebooks/04-proatomic-density-and-ias.ipynb +notebooks/04-ias-method-selection-study.ipynb +notebooks/05-proatomic-density-and-ias.ipynb +docs/dev/ias_method_selection.md ``` The exact test split is flexible. @@ -938,11 +964,11 @@ Changing `density_unit` must change only reported density values. It must not change: - selected interpolation interval; -- radius geometry; -- local-minimum positions; -- global-minimum identity; -- dimensionless weights; -- weighted IAS position or spread. +- cutoff radii or contour-overlap geometry; +- selected pairwise mode or method; +- returned coordinate; +- resolved-minimum identity; +- ambiguity, domination, or low-density-gap status. ### 8.6 Error and missing-data behavior @@ -1045,9 +1071,9 @@ far-tail zeros. The two projects should still document compatible positive-positive log–log behavior. -## 10. IAS scientific contract +## 10. Pairwise proatomic boundary and IAS-proxy scientific contract -### 10.1 Objective +### 10.1 Two related quantities, not one hidden policy For atoms A and B separated by distance `R`, define `x` from A toward B: @@ -1056,372 +1082,565 @@ A ---- x ----> B 0 R ``` -The promolecular line density is: +The component and summed neutral-promolecular line densities are: \[ -f(x; R) = \rho_A(x) + \rho_B(R - x) +\rho_A(x),\qquad \rho_B(R-x),\qquad +f(x;R)=\rho_A(x)+\rho_B(R-x) \] -for: +for `0 <= x <= R`. -\[ -0 \le x \le R -\] +`0.2.0` deliberately exposes two different pairwise estimates: -The IAS helper analyzes minima of this one-dimensional function. +1. **Proatomic boundary** — a stable geometric divider based on equal neutral + proatomic contributions, with a fixed low-density contour fallback. +2. **Promolecular density minimum** — one practically resolved minimum of + `f(x;R)` inside a region where both proatomic contributions remain above the + fixed tail cutoff. -### 10.2 Interpretation +They MUST NOT be presented as interchangeable definitions. The first is the +production default because it is unique, fast, and stable. The second is +available because a line-density minimum is closer in spirit to a +bond-critical-point coordinate and is useful for Bader-oriented calibration, +but it is a neutral-promolecular proxy rather than a molecular QTAIM result. -The result is an **approximate neutral-proatom IAS coordinate**. +### 10.2 Interpretation and naming -It is not: +Neither mode returns: - a QTAIM zero-flux surface; - a molecular-density topological critical point; - an environment-relaxed atomic boundary; - an ionic, charged, or self-consistent stockholder boundary; -- evidence that the underlying isolated-atom partition is unique. +- proof of a bond or interaction. + +User-facing documentation SHOULD use: + +- “neutral-proatom boundary estimate” for `boundary` mode; +- “promolecular line-density minimum” or “promolecular IAS proxy” for + `minimum` mode; +- “pairwise IAS-position proxy” only for the mode-selecting convenience API. -The wording “approximate IAS position” or “promolecular density minimum along -the atom–atom vector” should be used consistently. +The phrase “most probable position” MUST NOT be used because no probability +model is defined. -### 10.3 Input domain and units +Scientific reference context: -Preferred public function: +- molecular QTAIM basin boundaries are zero-flux surfaces of the molecular + density: R. F. W. Bader, *Chemical Reviews* **91** (1991), 893–928, + `doi:10.1021/cr00005a013`; +- the equal-proatom rule is a pairwise stockholder balance based on free-atom + reference densities: F. L. Hirshfeld, *Theoretica Chimica Acta* **44** + (1977), 129–138, `doi:10.1007/BF00549096`. + +These references motivate the interpretation; they do not turn either neutral- +proatom mode into a molecular QTAIM calculation. + +### 10.3 Public functions and default policy + +Preferred public API: ```python +estimate_proatomic_boundary( + atom_a, + atom_b, + distance, + *, + distance_unit="angstrom", + density_unit="electron/bohr^3", + set_id=DEFAULT_PROATOMIC_DENSITY_SET, +) + +estimate_promolecular_density_minimum( + atom_a, + atom_b, + distance, + *, + distance_unit="angstrom", + density_unit="electron/bohr^3", + set_id=DEFAULT_PROATOMIC_DENSITY_SET, +) + estimate_ias_position( atom_a, atom_b, distance, *, + mode="boundary", distance_unit="angstrom", density_unit="electron/bohr^3", set_id=DEFAULT_PROATOMIC_DENSITY_SET, - ... ) ``` +Binding behavior: + +- `mode` accepts only `"boundary"` and `"minimum"` in `0.2.0`; +- the default is `"boundary"`; +- the dispatcher calls the same internal implementation as the corresponding + direct function; +- `minimum` mode MUST NOT silently fall back to `boundary` mode; +- callers can compare the two direct results without hidden policy changes. + +The exact public names remain a preferred first design. Any rename MUST preserve +these three roles and the explicit default. + +### 10.4 Input domain and units + Binding input rules: - both elements must have profiles; - distance must be finite and strictly positive; - distance must not exceed 20 bohr after conversion; - the result coordinate is measured from atom A toward atom B; -- all reported positions and widths use `distance_unit`; +- all reported positions use `distance_unit`; - component and summed densities use `density_unit`; - D/T follow hydrogen behavior; - missing element profiles return `None`; -- invalid distance or unit raises `ValueError`. +- invalid distance, mode, or unit raises `ValueError`. + +All scientific decisions MUST be performed in bohr and electron/bohr³. Output +conversion occurs only after method selection, status classification, and +coordinate calculation. Changing `density_unit` MUST NOT change geometry, +method, status, or ambiguity. -Distance and density units are independent. Converting the density unit must not -change any reported position, tie, width, weight, spread, or status. +No universal lower distance such as `0.5 angstrom` is imposed. Extremely short +or dominated pairs are handled by explicit statuses instead of an arbitrary +ban. -### 10.4 Required result information +### 10.5 Fixed neutral-proatom tail cutoff -The public result MUST expose: +The `0.2.0` numerical contract uses the fixed per-atom cutoff: -1. all distinct interior local minima found under the documented numerical - rules; -2. the density value and position of each minimum; -3. the global minimum or all globally tied minima; -4. a width measure for the global minimum basin, or for each tied global basin; -5. a depth-weighted center of the local-minimum positions; -6. a weighted positional spread or equivalent ambiguity measure; -7. source dataset and interpolation provenance; -8. enough input information to interpret the coordinate orientation; -9. whether a boundary candidate invalidates or weakens the ordinary interior - IAS interpretation. +\[ +\rho_c = 10^{-4}\ \mathrm{electron/bohr^3}. +\] -Do not return only one unexplained float. +For every profile, define the unique cutoff radius: -### 10.5 Preferred result types +\[ +\rho_A(r_A^c)=\rho_c, +\qquad +\rho_B(r_B^c)=\rho_c. +\] -Preferred first design: +This is a model policy for ignoring weak neutral-proatom tails. It MUST NOT be +described as a universal lower bound for a chemically meaningful QTAIM bond +critical point. -```python -@dataclass(frozen=True, slots=True) -class IASMinimum: - position_from_a: float - position_from_b: float - fraction_from_a: float - rho_a: float - rho_b: float - rho_sum: float - width: float | None - width_left: float | None - width_right: float | None - width_is_clipped: bool +The cutoff is applied to each component separately. Therefore a gap where both +components are below the cutoff can have a summed density below +`2 * rho_c`, not necessarily below `rho_c`. +The cutoff is fixed rather than user-configurable in `0.2.0`. It MUST be exposed +in result metadata and included in the pairwise numerical-contract identifier. +### 10.6 Shared result information + +Use one small immutable result type for both direct functions and the dispatcher +unless implementation evidence strongly favors two types. + +Preferred first design: + +```python @dataclass(frozen=True, slots=True) -class IASResult: +class IASPositionResult: atom_a: str atom_b: str distance: float distance_unit: str density_unit: str - local_minima: tuple[IASMinimum, ...] - global_minima: tuple[IASMinimum, ...] - weighted_position_from_a: float | None - weighted_fraction_from_a: float | None - weighted_spread: float | None - weighting_power: float - width_fraction: float + requested_mode: str + method: str status: str + position_from_a: float | None + position_from_b: float | None + fraction_from_a: float | None + rho_a: float | None + rho_b: float | None + rho_sum: float | None + cutoff_density: float + cutoff_radius_a: float + cutoff_radius_b: float + contour_separation: float + alternative_position_from_a: float | None + alternative_rho_sum: float | None + relative_depth_gap: float | None + search_resolution: float | None + search_converged: bool | None dataset_id: str interpolation_contract: str + pairwise_contract: str ``` -The exact field list is preferred, not binding. It may be simplified if all -required information remains explicit and typed. - -Useful convenience behavior MAY include: - -```python -result.weighted_position_from_b -result.has_unique_global_minimum -result.unique_global_minimum -``` - -Do not create a synthetic singular “global minimum” when multiple minima are -equal within the declared tolerance without preserving the tie. - -### 10.6 Local minima - -The implementation MUST deliberately search for multiple minima. - -It MUST NOT assume that the complete objective is globally unimodal. - -Possible sources of multiple candidates include: +The exact field list is preferred, not binding. The result MUST still expose: -- shell structure; -- slope changes at profile interpolation knots; -- symmetric pairs; -- very short separations; -- shallow numerical splitting of one broad basin. +1. the returned coordinate or an explicit absence; +2. requested mode and actual method; +3. status; +4. component and summed density at a returned coordinate; +5. cutoff, cutoff radii, and signed contour separation; +6. enough minimum-search diagnostics to interpret ambiguity when `mode` is + `"minimum"`; +7. dataset, interpolation, and pairwise-contract provenance; +8. coordinate orientation. -All reported minima must be sorted by position from A. +Do not return one unexplained float. -Adjacent numerical duplicates must be merged under documented position and -density tolerances. +### 10.7 Proatomic-boundary mode -The exact candidate search and merge procedure is an open Stage 4 decision. - -### 10.7 Global minima and ties - -Let: +Let the signed contour separation be: \[ -f_{\min} = \min_i f_i +d_c=R-r_A^c-r_B^c. \] -A candidate is globally tied when its value agrees with `f_min` under documented -relative and absolute tolerances. - -The result MUST retain every tied global candidate. - -For a homonuclear pair: - -- the complete objective is symmetric around `R/2`; -- the candidate set should be symmetric within tolerance; -- the weighted center should be `R/2` within numerical tolerance; -- the implementation must not discard symmetric off-center minima merely to - force a unique midpoint global minimum. - -### 10.8 Width measure - -The preferred minimum-width definition is the connected interval around a -minimum for which: +For a homonuclear pair, return exactly: \[ -f(x) \le (1 + \epsilon) f_i +x=R/2. \] -where: +Use status `low_density_gap` when its two cutoff contours are separated; +otherwise use the ordinary successful status. The coordinate remains `R/2` in +either regime. -- `f_i` is the density at that minimum; -- `epsilon` is a documented dimensionless rise fraction; -- preferred default `epsilon = 0.05`. +For an unlike pair: -The width is: +- if `d_c > 0`, the cutoff contours do not overlap. Return the midpoint of the + two contour points: -```text -x_right - x_left -``` + \[ + x=\frac{r_A^c+(R-r_B^c)}{2} + =\frac{R+r_A^c-r_B^c}{2}; + \] -This gives a directly interpretable distance: + use method `cutoff_gap_midpoint` and status `low_density_gap`; +- if the contours overlap or touch, solve the unique monotone equation: -- small width: narrow minimum; -- large width: broad minimum. + \[ + \rho_A(x)=\rho_B(R-x); + \] -The result should record: + use method `equal_proatom_density`; +- if an unlike atom dominates the entire interval and the equal-contribution + equation has no interior solution, return no coordinate with status + `one_atom_dominates`. -- the width; -- left and right crossing positions; -- the rise fraction; -- whether a crossing was clipped by a neighboring basin or domain boundary. +At `R=r_A^c+r_B^c`, the contour midpoint and equal-contribution definitions meet +at the same coordinate. The implementation SHOULD classify this as +`cutoff_contact` or otherwise make contact visible without changing the +coordinate. -This definition is preferred because it: +### 10.8 Promolecular-minimum mode -- is unit invariant in density; -- does not require numerical second derivatives; -- remains meaningful at interpolation knots; -- is easy to explain to users. +This mode searches only the meaningful-overlap interval where both neutral +proatomic contributions are at least the cutoff: -The agent MAY propose a better robust geometric measure during Stage 4, but must -justify it before implementation. +\[ +I_c= +\left[ +\max(0,R-r_B^c), +\min(R,r_A^c) +\right]. +\] -### 10.9 Depth-weighted minima center +Binding behavior: -The averaged coordinate must use dimensionless weights. +- for an unlike pair, if `I_c` is empty, return no minimum coordinate with + status `low_density_gap`; +- for a homonuclear pair, return exactly `R/2` by symmetry, even if `I_c` is + empty or the raw interpolant contains slightly lower symmetric off-center + shell minima; retain `low_density_gap` as the status when applicable; +- for unlike pairs, find one **practically resolved** interior minimum of + `f(x;R)` under Section 11’s fixed spatial-resolution contract; +- do not enumerate or expose every mathematical microminimum; +- retain at most the selected resolved valley and one materially competitive + alternative; +- if no interior valley is resolved, return no coordinate with status + `no_resolved_interior_minimum`; +- if a boundary value is lower than the selected interior valley, retain the + candidate as a diagnostic but use status `boundary_dominated`; +- if two separated resolved valleys are practically competitive, return the + deeper one and expose the runner-up and relative depth gap with status + `ambiguous_competing_minima`; +- if the adaptive passes do not stabilize, return the finest-pass candidate + with status `search_unstable` and `search_converged=False`. + +The result MUST identify a homonuclear return as `homonuclear_midpoint`, not +mislabel it as a numerically discovered minimum when the midpoint fails the +resolved-valley check. + +The following concepts from the superseded all-minima design are intentionally +absent from `0.2.0`: + +- exhaustive local-minimum lists; +- exact global-tie enumeration; +- basin widths and watershed clipping; +- weighted averages of separate minima; +- high-precision reconstruction of sub-ULP event topology. + +### 10.9 Why both modes are retained + +`boundary` mode is the recommended production choice for geometry and weighted +Voronoi/Laguerre workflows because it is unique, continuous at cutoff contact, +symmetry-preserving, and inexpensive. + +`minimum` mode is retained for Bader-oriented research and calibration because a +line-density minimum is a more direct promolecular analogue of an internuclear +critical-point coordinate. Its optional status and diagnostics make the model +limitations visible instead of hiding them behind an averaged coordinate. + +Documentation SHOULD demonstrate both modes on the same pairs and MUST explain +that disagreement between them is a scientific-model difference, not +necessarily a numerical error. + +### 10.10 Pair reversal and homonuclear symmetry -Preferred weighting: +For a result on `(A, B, R)` and the reversed result `(B, A, R)`: -\[ -w_i = \left(\frac{f_{\min}}{f_i}\right)^p -\] +- a returned position `x` corresponds to `R-x`; +- method and status remain equivalent after A/B relabeling; +- component densities swap; +- summed density, cutoff regime, search convergence, relative depth gap, and + practical ambiguity remain unchanged; +- an alternative minimum, when present, also transforms to `R-x_alt`; +- `one_atom_dominates` identifies the correspondingly relabeled atom. -with preferred default: +For every homonuclear pair and every valid distance, both public modes return +exactly `R/2` after output-unit conversion within the ordinary conversion +rounding. -\[ -p = 1 -\] +Canonical internal orientation MAY be used to make reversal invariance +structural. -The weighted position is: +### 10.11 Status and failure semantics -\[ -x_{\mathrm{weighted}} -= -\frac{\sum_i w_i x_i}{\sum_i w_i} -\] +The exact vocabulary may be adjusted, but the result model MUST distinguish at +least: -The weighted positional spread is: +```text +ok +low_density_gap +one_atom_dominates +no_resolved_interior_minimum +boundary_dominated +ambiguous_competing_minima +search_unstable +``` -\[ -\sigma_x -= -\sqrt{ -\frac{\sum_i w_i(x_i-x_{\mathrm{weighted}})^2} -{\sum_i w_i} -} -\] +The method field MUST distinguish at least: -Properties: +```text +homonuclear_midpoint +equal_proatom_density +cutoff_gap_midpoint +promolecular_density_minimum +none +``` -- the deepest minimum has weight 1; -- shallower minima have weights below 1; -- equal minima receive equal weights; -- converting density units does not change the result; -- a symmetric homonuclear candidate set averages to the midpoint; -- large spread indicates separated competing minima. +Missing profiles return `None`. A scientifically non-applicable pair with valid +profiles returns a typed result with `position_from_a=None`; it is not treated +as missing data or an exception. -Do not exponentiate a raw dimensional density difference such as -`exp(-(rho_i-rho_min))`. Its numerical result changes with density units unless -an additional scale is defined. +## 11. Pairwise numerical implementation decision -The preferred weighting formula is a Stage 4 review point. Another -dimensionless, monotone depth weighting MAY replace it with explicit owner -approval or a strong documented reason. +The earlier exact all-minima design is retained only as supporting numerical +research. Production Stage 4 uses the smaller contract below. -### 10.10 Boundary behavior +### 11.1 Shared profile preparation and cutoff radii -The objective must also be evaluated at `x=0` and `x=R`. +For every loaded profile, cache: -A global boundary minimum is not an ordinary interior IAS intersection. +- logarithmic radii; +- logarithmic densities; +- ordinary log–log segment coefficients; +- the fixed `1e-4 electron/bohr^3` cutoff radius. -The result MUST make this visible, for example through statuses such as: +Because each packaged profile is strictly decreasing over its stored positive +segments, the cutoff radius is unique. Locate the bracketing stored values and +invert the accepted log–log segment analytically. Do not run a general root +finder for a cached one-dimensional inverse. -```text -ok -multiple_global_minima -boundary_dominated -no_interior_minimum -``` +Dataset validation MUST confirm for every packaged H–Lr profile that: -The exact vocabulary may change. +- all positive segments are strictly decreasing; +- the first stored value is above the cutoff; +- the profile falls below the cutoff before the 20-bohr public limit; +- substituting the cached radius reproduces the cutoff within a documented + floating-point tolerance. -When interior minima exist but a boundary value is lower, the result may still -report the interior minima and their weighted center as diagnostics, but it must -not label the result unconditionally valid. +### 11.2 Boundary-mode algorithm -### 10.11 Pair-reversal invariance +1. Validate and convert `R` to bohr. +2. Apply canonical pair orientation if used. +3. Read the two cached cutoff radii. +4. For identical atoms, return `R/2` immediately. +5. If the cutoff contours have a positive gap, return their midpoint. +6. Otherwise solve -For a result on `(A, B, R)` and the reversed result `(B, A, R)`: + \[ + g(x)=\log\rho_A(x)-\log\rho_B(R-x)=0. + \] -- every position `x` must correspond to `R - x`; -- objective density values must agree within tolerance; -- global-tie counts must agree; -- widths must agree; -- weighted position must transform to `R - x_weighted`; -- weighted spread must remain unchanged; -- status must remain equivalent. + The function is monotone non-increasing under the packaged profile contract, + so bracketed bisection is sufficient. +7. If the endpoint signs do not bracket a root, return + `one_atom_dominates`. +8. Stop bisection when the coordinate bracket is no wider than `1e-10 bohr` or + binary64 adjacency is reached. +9. Evaluate and report component densities using the public Stage 3 profile + convention at the selected coordinate. +10. Transform orientation and units only after the native result is complete. -This is a binding acceptance invariant. +Use the continuous log–log segment representation for the equality comparison. +One-ULP exact-knot value round trips MUST NOT create or remove an equality +crossing. -## 11. IAS numerical implementation decision gate +### 11.3 Practical minimum-mode algorithm -Before implementing the search, Stage 4 MUST add a short design note inside the -module docstring, tests, or this plan resolving: +The search is intentionally resolution-limited. Its public spatial resolution +is: -1. how the candidate mesh or interval partition is constructed; -2. how local minima are detected; -3. how candidate positions are refined; -4. how minima on interpolation knots are handled; -5. how adjacent numerical duplicates are merged; -6. how global ties are defined; -7. how width crossings are located; -8. which default tolerances are used; -9. what performance is expected for one pair and repeated pairs. +```text +IAS_MINIMUM_RESOLUTION_BOHR = 0.01 +``` -### 11.1 Preferred first approach +This is approximately `0.0053 angstrom`, far below the intended chemical-geometry +scale while large enough to coalesce the sub-resolution shell and interpolation +splittings that made exhaustive Stage 4A disproportionately complex. + +For unlike atoms with a nonempty significant-overlap interval `I_c`: + +1. Evaluate `f(x;R)` on a deterministic uniform grid over `I_c` with step no + larger than `0.02 bohr`. Include both interval endpoints, the interval + midpoint, and the equal-proatom-density coordinate when it exists. +2. A sample-resolved valley is an interior sample no higher than its immediate + neighbors. +3. Refine each sampled valley only inside its neighboring sample bracket with a + small dependency-free bounded minimizer. Golden-section search is acceptable + because the bracket is already local and the public contract coalesces + smaller internal structure. +4. Repeat with step no larger than `0.01 bohr`. +5. Combine candidates from both passes. Candidates closer than `0.01 bohr` + belong to one resolved valley; retain the lowest sampled/refined value as + that valley’s representative. +6. If the selected resolved valley changes by more than `0.01 bohr`, or its + density changes by more than `1e-4` relative, run one fallback pass with step + no larger than `0.005 bohr`. +7. After the fallback, return the finest supported representative and set + `search_converged` according to the same practical criteria. Do not invoke + Decimal arithmetic merely to classify smaller structure. +8. Sort resolved valleys by density. Retain the selected valley and at most one + runner-up separated by at least `0.01 bohr`. +9. Mark `ambiguous_competing_minima` when the runner-up is within `1e-4` + relative density of the selected valley. This is a declared practical + equivalence policy, not a binary64 equality tolerance. +10. Compare `f(0)` and `f(R)` with the selected interior value. If either is + lower by more than the ordinary floating-point comparison envelope, use + `boundary_dominated`. + +For a homonuclear pair, return `R/2` without running this search. The +implementation MAY evaluate nearby samples to report whether the midpoint also +behaves as a resolved minimum, but this diagnostic MUST NOT move the returned +coordinate. + +If no interior valley is found on the finest pass, return +`no_resolved_interior_minimum`. Do not clamp a minimum to a cutoff endpoint or +nucleus. + +### 11.4 Tolerance categories + +Keep these concepts separate: + +- **tail cutoff:** `1e-4 electron/bohr^3`; a fixed model policy; +- **minimum spatial resolution:** `0.01 bohr`; a fixed practical-resolution + policy; +- **competitive depth gap:** `1e-4` relative; a user-visible ambiguity policy; +- **equality bisection tolerance:** `1e-10 bohr`; a numerical convergence + tolerance; +- **ordinary floating-point envelope:** only for comparisons whose outcome is + not already defined by the practical policies above. + +Do not reuse one large `isclose` tolerance for cutoff contact, equality, +minimum competition, and binary64 roundoff. Do not tune tolerances only to make +individual examples pass. + +### 11.5 No exact-knot or all-minima machinery in production + +The production algorithm MUST NOT add: + +- an exact Decimal event sweep; +- exhaustive one-sided derivative topology; +- sub-ULP cell reconstruction; +- proximity merging of mathematical basins; +- global-tie lists; +- watershed-width construction; +- weighted averaging across minima. + +Exact profile knots are ordinary samples under the Stage 3 evaluator. A +sub-resolution feature that changes only under exact-event reconstruction is +outside the declared `minimum`-mode resolution and cannot affect +`boundary` mode. + +The archived Stage 4A design and review cases remain useful as a conservative +development reference and as supporting information explaining this decision. + +### 11.6 Performance guardrail + +The intended downstream use includes repeated pair evaluation. + +Record local, non-CI timings for: -A reasonable first approach is: +- first call including data load; +- cached cutoff-radius access; +- one `boundary` pair; +- one ordinary `minimum` pair; +- one fallback-pass `minimum` pair; +- a deterministic 1,000-pair batch in each mode. -1. Construct a deterministic candidate grid from relevant A profile knots and - transformed B profile knots `R - r_j` within `[0, R]`. -2. Add interval midpoints or another minimal set of probes needed to avoid - missing an interior basin. -3. Evaluate the objective on this grid. -4. Identify candidate minima from neighboring values. -5. Refine only candidate neighborhoods with a small dependency-free bounded - minimizer such as golden-section search. -6. Evaluate exact knot candidates explicitly. -7. Merge candidates that represent the same basin. -8. Evaluate boundary values separately. -9. Compute ties, widths, weighted center, spread, and status. +Targets on the recorded development machine are: -This approach is preferred because it uses the actual interpolation structure -rather than an arbitrary fixed global step. +```text +cached boundary mode: comfortably below 0.2 ms/pair +cached minimum mode: comfortably below 1.0 ms/pair in the ordinary case +``` -However, the repository owner expects to discuss or propose an alternative IAS -algorithm. Therefore this section is not binding implementation detail. +These are design targets, not portable CI thresholds. No benchmarking or +numerical runtime dependency may be added. -### 11.2 Performance guardrail +### 11.7 Validation reference and supporting study -Correctness comes first, but the implementation is intended for structure -algorithms and must not be unnecessarily expensive. +Stage 4 validation MUST use two references for different purposes: -Add a lightweight benchmark or timing-oriented test outside fragile CI -thresholds. Record approximate local timings for: +1. a slower fine-grid-and-refinement search inside `I_c` to validate the + practical minimum contract at and above `0.01 bohr` resolution; +2. the archived exact-event Stage 4A cases to confirm that intentionally omitted + microstructure cannot change boundary mode and is either coalesced or + reported as unresolved/ambiguous in minimum mode. -- first call including data load; -- cached density evaluation; -- one IAS pair; -- a modest batch of representative IAS pairs. +The executed notebook: -Do not add a benchmarking dependency. +```text +notebooks/04-ias-method-selection-study.ipynb +``` -If the preferred knot-based approach is too slow, optimize the implementation -without changing public semantics. Possible optimizations include: +and its compact documentation page MUST record: -- cached logarithmic arrays; -- index-aware interval evaluation; -- reduced candidate probing; -- precomputed per-profile segment coefficients; -- a coarse-to-fine search validated against a slower reference. +- cutoff-radius coverage for H–Lr; +- representative boundary/minimum comparisons; +- homonuclear Li–Li behavior; +- low-density-gap behavior; +- practical-reference agreement and timings; +- the numerical pathologies that motivated abandoning exhaustive all-minima + production semantics; +- the final two-mode decision and limitations. -Do not optimize by assuming one global minimum. +Agreement with actual molecular-density QTAIM coordinates is scientifically +valuable and SHOULD be added when a curated benchmark is available. It is not a +claim made by `0.2.0`, and absence of such a benchmark must remain explicit in +the documentation. ## 12. Documentation and notebook strategy @@ -1430,9 +1649,10 @@ Do not optimize by assuming one global minimum. `0.2.0` must contain enough documentation to use the new API safely: - accurate public docstrings for new objects; -- one concise density/IAS guide or equivalent section; -- exact provenance, units, range, and limitations; -- one executed feature notebook with plots; +- one concise density/pairwise-boundary guide or equivalent section; +- exact provenance, units, range, cutoff, mode semantics, and limitations; +- one executed Stage 4 method-selection notebook; +- one executed user-facing feature notebook with plots; - a changelog entry and working README mention. The broad documentation redesign belongs to `0.2.1` after the API is accepted. @@ -1440,15 +1660,39 @@ The broad documentation redesign belongs to `0.2.1` after the API is accepted. Do not move the README marketing rewrite, complete existing-notebook rewrite, or all-module docstring conversion into the critical path of `0.2.0`. -### 12.2 `0.2.0` feature notebook is mandatory +### 12.2 `0.2.0` Stage 4 supporting notebook is mandatory + +Add and execute: + +```text +notebooks/04-ias-method-selection-study.ipynb +``` + +This notebook is supporting information for the numerical and scientific +decision. It MUST: + +- clearly label all helper functions as research prototypes rather than public + API; +- explain the fixed per-atom cutoff; +- compare `boundary` and `minimum` definitions; +- reproduce representative cases and practical timings; +- summarize the archived exact all-minima failure modes; +- explain why exhaustive all-minima enumeration is not the production default; +- state that neither mode is an exact QTAIM surface; +- be linked from a compact page at `docs/dev/ias_method_selection.md`. + +Saved plots are encouraged. Matplotlib may be an optional +notebook/documentation dependency and MUST NOT become a runtime dependency. + +### 12.3 `0.2.0` feature notebook is mandatory Add: ```text -notebooks/04-proatomic-density-and-ias.ipynb +notebooks/05-proatomic-density-and-ias.ipynb ``` -The committed notebook MUST: +The committed feature notebook MUST: - be executed; - contain saved execution counts; @@ -1466,21 +1710,19 @@ Required content: 2. profile retrieval and scalar evaluation with independent radius/density units; 3. one or two radial-density plots, preferably with a logarithmic density axis; -4. one ordinary heteronuclear IAS example; -5. a plot of `rho_A(x)`, `rho_B(R-x)`, and their sum; -6. markers for local minima, globally tied minima, and the weighted position; -7. interpretation of minimum width and weighted spread; -8. at least one example that demonstrates why multiple minima are exposed; -9. a concise limitations section. +4. one ordinary heteronuclear `boundary` example; +5. the same pair in `minimum` mode; +6. a plot of `rho_A(x)`, `rho_B(R-x)`, and their sum; +7. a homonuclear midpoint example; +8. a low-density-gap example; +9. one no-resolved-minimum or one-atom-dominance example; +10. a concise limitations and mode-selection section. -Matplotlib may be an optional notebook/documentation dependency. It must not -become a runtime dependency. +For `0.2.0`, the notebooks may be linked from the docs/GitHub even if the +current Markdown exporter cannot faithfully embed rich plot output. The saved +`.ipynb` files remain the source artifacts. -For `0.2.0`, the notebook may be linked from the docs/GitHub even if the current -Markdown exporter cannot faithfully embed plots. The saved `.ipynb` itself is -the source artifact. - -### 12.3 Current notebook behavior to replace in `0.2.1` +### 12.4 Current notebook behavior to replace in `0.2.1` The current repository tools: @@ -1513,7 +1755,7 @@ Preferred compact workflow: Avoid adding separate “execute,” “export,” and “check” tools when one tool with two modes is sufficient. -### 12.4 Direct notebook rendering in `0.2.1` +### 12.5 Direct notebook rendering in `0.2.1` Preferred documentation approach: @@ -1532,7 +1774,7 @@ naturally. A different direct-rendering solution may be used if it is materially better, but do not keep both direct rendering and generated Markdown copies. -### 12.5 Explanatory notebook writing standard +### 12.6 Explanatory notebook writing standard Every shipped notebook should contain: @@ -1547,7 +1789,7 @@ Not every one-line import needs its own essay. The standard is that a reader should understand why each logical block exists and what to inspect in its output. -### 12.6 Full API reference with MkDocs +### 12.7 Full API reference with MkDocs Do not switch away from MkDocs solely to obtain Sphinx-like API pages. @@ -1604,7 +1846,7 @@ Notes: Type annotations remain the source of type information; docstrings explain meaning, units, behavior, and constraints. -### 12.7 README/home-page redesign in `0.2.1` +### 12.8 README/home-page redesign in `0.2.1` `docs/index.md` remains the source of `README.md`. @@ -1622,13 +1864,13 @@ The opening should communicate an identity close to: > electron densities through a small Python API for crystallographic, > quantum-chemical, and molecular-structure algorithms. -Show working radii, profile, and IAS examples near the top. Move detailed policy +Show working radii, profile, boundary-mode, and minimum-mode examples near the top. Move detailed policy terminology and nested-transfer mechanics lower. Avoid leading with negative positioning such as “not another periodic-table encyclopedia.” -### 12.8 Keep the durable docs compact +### 12.9 Keep the durable docs compact The preferred durable structure after `0.2.1` is still small: @@ -1641,7 +1883,7 @@ notebooks/*.ipynb ``` Remove the duplicated public development-plan page. Do not create separate pages -for interpolation, IAS mathematics, provenance, licensing, and every result +for interpolation, pairwise method semantics, provenance, licensing, and every result field unless one combined guide becomes genuinely difficult to use. ## 13. Staged implementation plan @@ -1983,87 +2225,111 @@ feat: add proatomic density evaluation --- -### Stage 4 — IAS minima analysis +### Stage 4 — Pairwise proatomic boundary and practical IAS proxy #### Goal -Implement pairwise promolecular line-density analysis and return raw minima plus -a useful averaged IAS estimate. +Implement the two-mode pairwise contract from Sections 10 and 11 without the +superseded exhaustive all-minima machinery. + +#### Stage 4A — finalize and preserve the numerical decision -#### Stage 4A — resolve the numerical design +Before public implementation: -Before coding the public function: +1. accept the revised Sections 10 and 11; +2. retain the archived exact-event Stage 4A reports as development evidence, + not as the production contract; +3. add and execute `notebooks/04-ias-method-selection-study.ipynb`; +4. add `docs/dev/ias_method_selection.md` and link the notebook; +5. verify all H–Lr cutoff radii and the monotonicity assumptions; +6. record representative agreement and timing for the practical minimum search; +7. resolve final public function/result names without changing the two-mode + semantics. -1. review Sections 10 and 11; -2. compare the preferred search with any owner-proposed alternative; -3. prototype candidate algorithms on representative pairs; -4. compare against a slower dense reference scan; -5. select merge, tie, width, and weighting tolerances; -6. record the chosen behavior in tests/docstrings and update this plan if - necessary. +Stage 4A MUST NOT modify production pairwise code beyond documentation/test +fixtures needed to preserve the decision record. Representative development pairs should include: ```text H-H -C-C +Li-Li C-O Fe-O -La-O -U-O +H-U H-heavy-element -a pair with multiple shallow candidates -a pair with globally tied or symmetry-related candidates -a short-distance boundary-dominated case +one low-density-gap pair +one one-atom-dominance pair +one boundary-dominated minimum case +one pair with competing resolved minima +one archived sub-resolution exact-event case ``` -#### Stage 4B — implement result types and search +#### Stage 4B — implement shared result, direct functions, and dispatcher -Implement immutable results exposing all binding information from Section 10. +Implement: -The API MUST not hide: +- the fixed cached cutoff radius; +- the shared immutable result type; +- `estimate_proatomic_boundary()`; +- `estimate_promolecular_density_minimum()`; +- `estimate_ias_position(..., mode="boundary" | "minimum")`; +- exact homonuclear midpoint handling; +- equal-contribution bisection; +- cutoff-gap midpoint behavior; +- the resolution-limited minimum search; +- explicit statuses and provenance; +- canonical reversal handling and unit conversion. -- multiple minima; -- ties; -- boundary domination; -- weighting definition; -- width definition; -- dataset and unit provenance. +Do not implement: + +- exhaustive local-minimum enumeration; +- exact global-tie machinery; +- widths or watershed clipping; +- weighted centers or spreads; +- Decimal event topology; +- a universal minimum-distance ban; +- hidden fallback between modes. #### Stage 4C — validation Required tests cover: -- independent dense-reference agreement; -- pair reversal; -- homonuclear symmetry; -- narrow and broad minima; -- width clipping; -- dimensionless weighting; -- density-unit invariance; -- boundary-dominated and no-interior-minimum cases; -- invalid distance and units; +- all H–Lr cutoff radii are unique, finite, and reproduce the cutoff; +- exact homonuclear `R/2` in both modes; +- equal component densities in ordinary boundary-overlap cases; +- cutoff-gap midpoint geometry; +- continuous agreement of the two boundary branches at cutoff contact; +- explicit one-atom dominance; +- minimum search limited to the significant-overlap interval; +- practical-reference agreement at the declared `0.01 bohr` resolution; +- no-resolved-minimum, boundary-dominated, competing-minimum, and unstable-search + statuses; +- pair reversal for both primary and alternative coordinates; +- distance-unit and density-unit invariance; +- invalid mode, distance, and units; - missing elements; -- representative element classes; -- practical cached performance. +- representative light, transition, lanthanide, and actinide pairs; +- practical cached performance for both modes; +- regression cases from the Stage 4A supporting study. #### Completion criteria -- all relevant local minima are inspectable; -- global ties are retained; -- width and weighted center/spread are defined and tested; -- reversal and symmetry hold; -- invalid/boundary situations are explicit; -- no exact molecular-topology claim is made. +- the default returns a stable, interpretable proatomic divider; +- the optional mode returns one cutoff-bounded, practically resolved + promolecular minimum or an explicit non-result/diagnostic status; +- identical atoms always return `R/2`; +- no arbitrary short-distance ban is present; +- pair reversal, units, cutoff, provenance, and failures are tested; +- no exact molecular-QTAIM claim is made; +- no all-minima production machinery remains in scope. #### Suggested commit ```text -feat: add approximate IAS position analysis +feat: add two-mode proatomic IAS position estimates ``` ---- - ### Stage 5 — Executed feature notebook and `0.2.0` release #### Goal @@ -2076,10 +2342,10 @@ release `0.2.0` without performing the full documentation overhaul. Add and execute: ```text -notebooks/04-proatomic-density-and-ias.ipynb +notebooks/05-proatomic-density-and-ias.ipynb ``` -It MUST satisfy Section 12.2. +It MUST satisfy Section 12.3. For `0.2.0`, extend existing notebook validation minimally so CI verifies: @@ -2095,7 +2361,7 @@ Do not build a second large notebook pipeline during this stage. - add the new notebook to the notebook overview; - link the saved notebook from docs; -- add accurate density/IAS guide material; +- add accurate density, cutoff, mode-selection, and IAS-proxy guide material; - add the new module API page; - mention the feature on the home page without the full marketing rewrite; - document exact provenance, units, 20-bohr domain, and limitations; @@ -2129,8 +2395,14 @@ rho = ar.get_proatomic_density( ) assert rho is not None and rho > 0 -result = ar.estimate_ias_position("C", "O", 1.43) -assert result is not None +boundary = ar.estimate_proatomic_boundary("C", "O", 1.43) +assert boundary is not None and boundary.position_from_a is not None + +minimum = ar.estimate_promolecular_density_minimum("C", "O", 1.43) +assert minimum is not None + +result = ar.estimate_ias_position("C", "O", 1.43, mode="boundary") +assert result == boundary ``` #### Final commands @@ -2164,7 +2436,7 @@ Stage 6 rather than implementing a plot-specific Markdown exporter. #### Suggested commits ```text -docs: add proatomic density and IAS notebook +docs: add IAS method study and proatomic density feature notebook chore: prepare atomref 0.2.0 ``` @@ -2342,41 +2614,56 @@ chore: prepare atomref 0.2.1 - [ ] Loading is lazy/cached through generic dataset machinery. - [ ] Shared package data cannot be mutated through public objects. -### 14.4 `0.2.0` IAS API +### 14.4 `0.2.0` pairwise boundary and IAS-proxy API - [ ] Distance is measured from A toward B and documented. - [ ] Distance must be `0 < R <= 20 bohr`. - [ ] Distance and density units are independent. -- [ ] All distinct interior local minima are exposed. -- [ ] Each minimum includes position and density decomposition. -- [ ] Global ties are retained. -- [ ] Minimum width is defined, dimensioned, and tested. -- [ ] Weighted center uses dimensionless weights. -- [ ] Weighted spread or equivalent ambiguity measure is exposed. -- [ ] Density-unit conversion does not change IAS geometry. -- [ ] Pair reversal transforms positions correctly. -- [ ] Homonuclear weighted centers equal `R/2`. -- [ ] Boundary-dominated cases are explicit. +- [ ] The fixed per-atom cutoff is `1e-4 electron/bohr^3` and is reported. +- [ ] Every packaged H–Lr profile has one validated cutoff radius. +- [ ] `estimate_proatomic_boundary()` is available. +- [ ] `estimate_promolecular_density_minimum()` is available. +- [ ] `estimate_ias_position()` dispatches explicitly by mode. +- [ ] The default mode is `boundary`. +- [ ] `minimum` mode never silently falls back to `boundary`. +- [ ] Identical atoms return exactly `R/2` in both modes. +- [ ] Overlapping unlike atoms can return the equal-proatom-density divider. +- [ ] Separated cutoff contours return their geometric midpoint in boundary mode. +- [ ] Cutoff contact is continuous. +- [ ] One-atom dominance is explicit. +- [ ] Minimum search is confined to the significant-overlap interval. +- [ ] Minimum search uses the declared `0.01 bohr` practical resolution. +- [ ] At most one selected minimum and one competitive alternative are exposed. +- [ ] Low-density-gap, no-resolved-minimum, boundary-dominated, competing, and + unstable cases are explicit. +- [ ] Density-unit conversion does not change geometry, method, or status. +- [ ] Pair reversal transforms primary and alternative positions correctly. - [ ] Missing profiles return `None`. -- [ ] Invalid distances and units raise. -- [ ] Dataset/interpolation provenance is present. -- [ ] Representative results agree with an independent reference search. -- [ ] The algorithm does not assume global unimodality. +- [ ] Invalid distances, modes, and units raise. +- [ ] Dataset, interpolation, cutoff, and pairwise-contract provenance is present. +- [ ] Practical minimum results agree with an independent slower search at the + declared resolution. +- [ ] The API does not claim an exact QTAIM surface or critical point. +- [ ] Exhaustive local-minimum, tie, width, and weighted-center machinery is not + part of the production API. ### 14.5 `0.2.0` notebook and packaging -- [ ] Proatomic density/IAS notebook exists. -- [ ] It is executed and saved with outputs. -- [ ] It contains explanatory Markdown before logical code sections. -- [ ] It contains at least two informative plots. -- [ ] It demonstrates local/global/weighted minima. -- [ ] It executes cleanly in CI. -- [ ] Minimal public docs describe source, units, range, and limitations. +- [ ] Stage 4 method-selection supporting notebook exists. +- [ ] Supporting notebook is executed and saved with outputs. +- [ ] Supporting notebook is linked from `docs/dev/ias_method_selection.md`. +- [ ] Proatomic density/pairwise feature notebook exists. +- [ ] Feature notebook is executed and saved with outputs. +- [ ] Both notebooks contain explanatory Markdown before logical code sections. +- [ ] The notebooks contain informative plots where appropriate. +- [ ] The feature notebook demonstrates both modes, homonuclear symmetry, a + low-density gap, and an explicit non-ordinary case. +- [ ] Notebook execution is deterministic and validated. +- [ ] Minimal public docs describe source, units, range, cutoff, mode selection, + and limitations. - [ ] README is regenerated and mentions the feature accurately. -- [ ] Strict docs build passes. -- [ ] Full release check passes. -- [ ] Installed wheel loads the radial set, evaluates density, and performs IAS - analysis. +- [ ] Installed wheel loads the radial set, evaluates density, and performs both + pairwise modes. ### 14.6 `0.2.1` notebook documentation @@ -2396,7 +2683,7 @@ chore: prepare atomref 0.2.1 - [ ] Public docstrings use one structured style. - [ ] API cross-references work. - [ ] README/home page explains value immediately. -- [ ] Working radii, density, and IAS examples appear near the top. +- [ ] Working radii, density, boundary-mode, and minimum-mode examples appear near the top. - [ ] Detailed policy terminology is moved below first-use content. - [ ] Duplicate development-plan docs are removed. - [ ] Strict docs and full release checks pass. @@ -2413,7 +2700,7 @@ Possible small follow-up work only when justified by evidence: from the element table; - improve storage-kind validation if Stage 1 reveals weak points; - add a static type-checking CI job; -- optimize repeated IAS evaluation if profiling shows a bottleneck; +- optimize repeated pairwise boundary/minimum evaluation if profiling shows a bottleneck; - add scalar batch evaluation if downstream use proves it necessary; - improve mixed-license artifact checks. @@ -2517,7 +2804,7 @@ Important design questions: Recommended direction: -- stabilize and validate scalar radial evaluation and IAS first; +- stabilize and validate scalar radial evaluation and the two pairwise modes first; - gather requirements from actual crystallographic grid formats; - design nonperiodic and periodic behavior together; - use an optional NumPy-based extra rather than making NumPy a core runtime @@ -2552,8 +2839,8 @@ Preferred linear history for `0.2.0`: refactor: generalize built-in dataset loading feat(data): add neutral proatomic density snapshot feat: add proatomic density evaluation -feat: add approximate IAS position analysis -docs: add proatomic density and IAS notebook +feat: add two-mode proatomic IAS position estimates +docs: add IAS method study and proatomic density feature notebook chore: prepare atomref 0.2.0 ``` @@ -2623,24 +2910,25 @@ Review before the corresponding implementation stage: - or `ProatomicDensitySet`. 3. Exact generic `get_builtin_set()` return annotation and scalar narrowing helper names. -4. Final public module and density/IAS function names. -5. Canonical density unit strings and accepted aliases. -6. Exact IAS candidate-search algorithm. -7. Candidate merge and global-tie tolerances. -8. Whether the 5%-rise width is the preferred width definition. -9. Final dimensionless weighting function and default power. -10. Exact result dataclass fields and status vocabulary. -11. Whether weighted position or global-minimum position is the primary - convenience coordinate. -12. Whether the `0.2.0` feature notebook is linked only as `.ipynb` or given a - temporary static docs representation before `0.2.1`. -13. Final `0.2.1` notebook renderer and normalization details. -14. Final MkDocs/mkdocstrings visual settings after a local rendered preview. -15. The future grid-density requirements once actual target formats are +4. Final public spelling of the three pairwise functions and result type. +5. Final status and method vocabulary, without changing their required + distinctions. +6. Whether `IAS_MINIMUM_RESOLUTION_BOHR` and the practical competitive-depth + threshold should be public constants or provenance-only fields. +7. Whether the minimum result should expose one explicit runner-up object or + flattened optional fields. +8. Whether the supporting notebook remains a permanent documentation artifact + after `0.2.1` adopts direct notebook rendering. +9. Final `0.2.1` notebook renderer and normalization details. +10. Final MkDocs/mkdocstrings visual settings after a local rendered preview. +11. The future grid-density requirements once actual target formats are collected. +12. A future curated molecular-QTAIM benchmark for comparing midpoint, + equal-contribution, and promolecular-minimum coordinates. -These are review points, not permission to weaken the binding scientific, -provenance, range, dataset-unification, unit, or compatibility contracts. +These are review points, not permission to change the fixed cutoff, default +mode, homonuclear midpoint, explicit mode separation, provenance, range, +dataset-unification, unit, or compatibility contracts. ## 20. Plan closeout and replanning record diff --git a/docs/assets/ias-method-study/c-o-method-comparison.png b/docs/assets/ias-method-study/c-o-method-comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..3bed37c8a44dac0a3078ec63efc33bbbedfaea10 GIT binary patch literal 80965 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 literal 0 HcmV?d00001 diff --git a/docs/assets/ias-method-study/li-li-symmetry.png b/docs/assets/ias-method-study/li-li-symmetry.png new file mode 100644 index 0000000000000000000000000000000000000000..e690b253c2025af3f4518b8643bef3c5d019e1dd GIT binary patch literal 73845 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 literal 0 HcmV?d00001 diff --git a/docs/dev/dev_plan.md b/docs/dev/dev_plan.md index 94cdaac..a029466 100644 --- a/docs/dev/dev_plan.md +++ b/docs/dev/dev_plan.md @@ -1,33 +1,21 @@ # Development plan -## Current status (implemented in the `0.1.x` line) +The authoritative implementation plan is the root-level +[`DEV_PLAN.md`](https://github.com/DeloneCommons/atomref/blob/main/DEV_PLAN.md). +This compact page exists only for the current documentation structure and will +be removed during the planned `0.2.1` documentation cleanup. -- stable element metadata -- curated covalent, van der Waals, and atomic-radius support datasets -- explicit provenance and coverage metadata -- generic value-policy core plus radii and X–H convenience wrappers -- substitution and linear transfer -- custom element-indexed scalar sets -- policy-backed transfer sources -- nested-policy safeguards, transfer-depth tracking, and cycle detection -- provisional X–H support via `csd_legacy_xh_cno`, `XHPolicy`, and - `DEFAULT_XH_POLICY` +## Current `0.2.0` direction -## Planned for `0.2.x` +The accepted density work provides neutral H–Lr spherical proatomic profiles +and scalar log–log evaluation. Stage 4 now defines two explicit pairwise modes: -- broader X–H datasets and policies -- experimental plus computational support sets -- pairwise helper logic such as reference sums and normalization schemes -- restoration of incomplete experimental data from broader-support predictors +- a default stable proatomic boundary based on homonuclear symmetry, equal + proatomic contributions, and a fixed low-density contour fallback; +- an optional, cutoff-bounded and resolution-limited promolecular density + minimum for Bader-oriented comparison. -## Longer-term design ideas +The mode-selection evidence and limitations are summarized in +[Pairwise boundary and IAS-proxy method selection](ias_method_selection.md). -- radial atomic reference functions -- simple proto-density support based on spherically averaged atomic data - -## Possible future directions - -- more radii sets -- uncertainty and confidence flags -- ion-specific or atom-type-specific domains -- density-derived radii and related reference transforms +Neither mode is an exact molecular-density QTAIM surface. diff --git a/docs/dev/ias_method_selection.md b/docs/dev/ias_method_selection.md new file mode 100644 index 0000000..db23fe8 --- /dev/null +++ b/docs/dev/ias_method_selection.md @@ -0,0 +1,116 @@ +# Pairwise boundary and IAS-proxy method selection + +Stage 4 originally considered a numerically exhaustive search for every local +minimum of the neutral-promolecular line density between two atoms. The review +work showed that this contract is possible, but it makes interpolation-scale +features, one-ULP knot behavior, sub-ULP event ordering, and exact tie handling +part of the public science. + +That is more precision than the intended geometry workflow needs. The revised +Stage 4 keeps two explicit methods instead of hiding one policy behind a single +number. + +## Recommended default: proatomic boundary + +The default method returns a stable neutral-proatom divider: + +- identical atoms use the exact midpoint; +- overlapping unlike atoms use the point where the two neutral proatomic + densities are equal; +- separated low-density contours use the midpoint of the contour gap; +- complete one-atom dominance is reported explicitly. + +The fixed per-atom tail cutoff is +`1e-4 electron/bohr^3`. It is a model policy for ignoring weak neutral-proatom +tails, not a universal QTAIM interaction threshold. + +![Neutral-proatom cutoff radii across H–Lr](../assets/ias-method-study/cutoff-radii.png) + +## Optional: practical promolecular minimum + +The optional minimum method is retained for Bader-oriented comparison and +calibration. It searches the sum of the two proatomic densities only where both +components remain above the cutoff. + +The search has a declared spatial resolution of `0.01 bohr`. It returns one +resolved minimum and, when useful, one competitive alternative. It does not +attempt to preserve every mathematical microminimum. + +For identical atoms, symmetry still fixes the returned coordinate at `R/2`. +The Li–Li example shows why this rule is necessary: the raw interpolant has +slightly deeper symmetric off-centre valleys, but either one alone is a poor +separator for identical atoms. + +![Li–Li raw valleys and symmetry midpoint](../assets/ias-method-study/li-li-symmetry.png) + +The two methods are scientifically different. For C–O at 1.5 bohr, the +balanced-contribution coordinate and the promolecular minimum do not coincide. +That disagreement is expected rather than a solver defect. + +![C–O method comparison](../assets/ias-method-study/c-o-method-comparison.png) + +## Numerical evidence + +The executed study compared the practical minimum search with a slower +`0.001 bohr` reference over 300 deterministic H–Lr cases and seven adversarial +cases from the Stage 4A review. + +- All 298 cases where both methods returned a minimum agreed within + `0.01 bohr`. +- The largest coordinate difference was about `0.00129 bohr`. +- Two extremely short-distance reference minima were intentionally reported as + unresolved because they were narrower than the public practical resolution + and lay immediately beside a nucleus. +- On the recorded review machine, the research prototype took roughly + `0.05–0.06 ms` per cached boundary estimate and about `0.8 ms` per cached + practical minimum estimate. + +These figures are evidence, not portable performance guarantees. + +## Why exhaustive all-minima search is not the default + +The exact Stage 4A investigation found real edge cases: + +- a minimum only 27 binary64 coordinates from a profile knot; +- one-ULP inversions at exact knots; +- multiple shallow or symmetric minima, including five for Li–Li at 5 bohr; +- fourteen H–U minima in a case where a boundary value was lower; +- distinct exact events that rounded to one float; +- value errors large enough to complicate exact global-tie classification. + +Those facts matter under an “expose every local minimum” contract. They do not +materially improve a stable pairwise divider, and the revised minimum mode +coalesces or rejects structure below its declared resolution. + +## Proposed public shape + +```python +estimate_proatomic_boundary(...) +estimate_promolecular_density_minimum(...) +estimate_ias_position(..., mode="boundary" | "minimum") +``` + +`boundary` is the default. The dispatcher never silently switches modes. +Neither result is an exact molecular-density QTAIM surface. + +## Scientific context + +The interpretation follows three established ideas while keeping their scopes +separate: + +- QTAIM basin boundaries are zero-flux surfaces of the molecular electron + density: Bader, *Chemical Reviews* **91** (1991), 893–928, + [doi:10.1021/cr00005a013](https://doi.org/10.1021/cr00005a013). +- Hirshfeld stockholder weights are proportional to free-atom reference + densities, which motivates equal pairwise contributions as a stable divider: + Hirshfeld, *Theoretica Chimica Acta* **44** (1977), 129–138, + [doi:10.1007/BF00549096](https://doi.org/10.1007/BF00549096). +- Radial-density atom and bond constructions are useful model definitions but + require comparison with molecular-density results: Warburton, Poirier, and + Nippard, *J. Phys. Chem. A* **115** (2011), 852–867, + [doi:10.1021/jp1093417](https://doi.org/10.1021/jp1093417). + +The complete calculations, representative profiles, adversarial cases, and +local timing record are in the executed +[`04-ias-method-selection-study.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/04-ias-method-selection-study.ipynb) +notebook. diff --git a/docs/guide/notebooks.md b/docs/guide/notebooks.md index 2ad0045..43a7c0b 100644 --- a/docs/guide/notebooks.md +++ b/docs/guide/notebooks.md @@ -1,13 +1,10 @@ # Notebook gallery -`atomref` ships example Jupyter notebooks that cover the main workflows. -Each notebook is available in two forms: - -- the original `.ipynb` file in the repository, -- a rendered Markdown copy included in these docs. - -That way users can either run the notebooks locally or read them directly on the -documentation site. +`atomref` ships example and supporting-information Jupyter notebooks. +The three user-workflow notebooks have generated Markdown copies in these docs. +The Stage 4 method-selection notebook instead has a compact documentation page +with its main plots and conclusions, while the complete executed `.ipynb` +remains in the repository. ## Available notebooks @@ -17,9 +14,13 @@ documentation site. — overrides, transfer-backed policies, and policy summaries. - [Custom sets and discovery notebook](../notebooks/03-custom-sets-and-discovery.md) — user-defined sets, catalog inspection, and metadata exploration. +- [IAS method-selection study](../dev/ias_method_selection.md) — supporting + numerical evidence for the two-mode pairwise design and the decision not to + expose every interpolation-scale local minimum. The original notebook files are also in the repository: - [`01-quickstart.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/01-quickstart.ipynb) - [`02-policies-and-assessment.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/02-policies-and-assessment.ipynb) - [`03-custom-sets-and-discovery.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/03-custom-sets-and-discovery.ipynb) +- [`04-ias-method-selection-study.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/04-ias-method-selection-study.ipynb) diff --git a/mkdocs.yml b/mkdocs.yml index ef01733..2cbdde1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,6 +39,7 @@ nav: - Development: - Architecture: dev/architecture.md - Data curation: dev/data_curation.md + - IAS method selection: dev/ias_method_selection.md - Development plan: dev/dev_plan.md - API: - Overview: api/index.md diff --git a/notebooks/04-ias-method-selection-study.ipynb b/notebooks/04-ias-method-selection-study.ipynb new file mode 100644 index 0000000..b2d73ce --- /dev/null +++ b/notebooks/04-ias-method-selection-study.ipynb @@ -0,0 +1,1079 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cdde20c5", + "metadata": {}, + "source": [ + "\n", + "# Choosing a practical neutral-proatom IAS proxy\n", + "\n", + "This notebook is supporting information for the Stage 4 numerical decision in\n", + "`atomref`. It compares two pairwise quantities derived from the packaged neutral\n", + "spherical proatomic densities:\n", + "\n", + "1. a **stable proatomic boundary**, based on equal proatomic contributions or a\n", + " midpoint between fixed low-density contours;\n", + "2. an optional **promolecular line-density minimum**, searched only where both\n", + " proatomic contributions remain above the cutoff and only to a declared\n", + " practical spatial resolution.\n", + "\n", + "The notebook does **not** implement the public API and does not claim to locate\n", + "an exact QTAIM zero-flux surface. Its helper functions are research prototypes\n", + "used to test the revised contract.\n" + ] + }, + { + "cell_type": "markdown", + "id": "59023f54", + "metadata": {}, + "source": [ + "\n", + "## Numerical policy examined here\n", + "\n", + "The per-atom neutral-tail cutoff is\n", + "\n", + "\\[\n", + "\\rho_c = 10^{-4}\\ \\mathrm{electron/bohr^3}.\n", + "\\]\n", + "\n", + "The default boundary mode uses homonuclear symmetry, equal proatomic\n", + "contributions in the overlap region, and the midpoint between cutoff contours\n", + "when a low-density gap opens. The optional minimum mode searches only the\n", + "interval where both contributions are at least \\(\\rho_c\\), and coalesces\n", + "features below `0.01 bohr` (about `0.0053 Å`).\n" + ] + }, + { + "cell_type": "markdown", + "id": "fcdd3855", + "metadata": {}, + "source": [ + "## Scientific context and references\n", + "\n", + "The two modes answer different questions.\n", + "\n", + "- QTAIM defines atomic basins through zero-flux surfaces of the **molecular**\n", + " electron density; a critical point on an internuclear path is therefore a\n", + " molecular-density object, not something that neutral spherical proatoms can\n", + " reproduce exactly. See R. F. W. Bader, *Chemical Reviews* **91** (1991),\n", + " 893–928, [doi:10.1021/cr00005a013](https://doi.org/10.1021/cr00005a013).\n", + "- Hirshfeld's stockholder construction assigns molecular density in proportion\n", + " to free-atom reference densities. In the present two-proatom line model,\n", + " equal neutral-proatom densities therefore give equal pairwise stockholder\n", + " contributions. See F. L. Hirshfeld, *Theoretica Chimica Acta* **44** (1977),\n", + " 129–138, [doi:10.1007/BF00549096](https://doi.org/10.1007/BF00549096).\n", + "- Radial-density constructions can be useful atom/bond models, but they remain\n", + " model definitions that need comparison with molecular-density results. See\n", + " P. L. Warburton, R. A. Poirier, and D. Nippard, *J. Phys. Chem. A* **115**\n", + " (2011), 852–867, [doi:10.1021/jp1093417](https://doi.org/10.1021/jp1093417).\n", + "\n", + "These references motivate the interpretation. The numerical experiments below\n", + "justify only the implementation policy for the supplied neutral-proatom data;\n", + "they do not validate either mode as an exact QTAIM surface.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "51dd32b7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-13T17:50:59.619558Z", + "iopub.status.busy": "2026-07-13T17:50:59.619410Z", + "iopub.status.idle": "2026-07-13T17:50:59.929755Z", + "shell.execute_reply": "2026-07-13T17:50:59.928308Z" + } + }, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "\n", + "from bisect import bisect_right\n", + "from dataclasses import dataclass\n", + "from functools import lru_cache\n", + "import math\n", + "import random\n", + "import time\n", + "\n", + "from pathlib import Path\n", + "import sys\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"src\" / \"atomref\").is_dir():\n", + " for candidate in (ROOT.parent, ROOT.parent.parent):\n", + " if (candidate / \"src\" / \"atomref\").is_dir():\n", + " ROOT = candidate\n", + " break\n", + "if not (ROOT / \"src\" / \"atomref\").is_dir():\n", + " raise RuntimeError(\"Run this notebook from the atomref repository checkout\")\n", + "sys.path.insert(0, str(ROOT / \"src\"))\n", + "\n", + "import atomref as ar\n", + "\n", + "try:\n", + " import matplotlib.pyplot as plt\n", + "except ImportError: # The package itself has no plotting dependency.\n", + " plt = None\n", + "\n", + "ASSET_DIR = ROOT / \"docs\" / \"assets\" / \"ias-method-study\"\n", + "ASSET_DIR.mkdir(parents=True, exist_ok=True)\n", + "\n", + "TAIL_CUTOFF = 1.0e-4\n", + "PRACTICAL_RESOLUTION = 0.01\n", + "INITIAL_STEP = 0.02\n", + "CONFIRM_STEP = 0.01\n", + "FALLBACK_STEP = 0.005\n", + "COMPETITIVE_RELATIVE_GAP = 1.0e-4\n", + "\n", + "\n", + "@dataclass(frozen=True)\n", + "class StudyEstimate:\n", + " method: str\n", + " status: str\n", + " position: float | None\n", + " rho_a: float | None\n", + " rho_b: float | None\n", + " rho_sum: float | None\n", + " contour_separation: float\n", + " search_resolution: float | None = None\n", + " search_converged: bool | None = None\n", + " alternative_position: float | None = None\n", + " relative_depth_gap: float | None = None\n", + " probes: int = 0\n", + "\n", + "\n", + "@lru_cache(maxsize=None)\n", + "def profile(symbol: str):\n", + " result = ar.get_proatomic_density_profile(symbol)\n", + " if result is None:\n", + " raise ValueError(f\"missing profile: {symbol}\")\n", + " return result\n", + "\n", + "\n", + "@lru_cache(maxsize=None)\n", + "def cutoff_radius(symbol: str) -> float:\n", + " p = profile(symbol)\n", + " right = next(i for i, value in enumerate(p.densities) if value <= TAIL_CUTOFF)\n", + " if p.densities[right] == TAIL_CUTOFF:\n", + " return p.radii[right]\n", + " left = right - 1\n", + " lr0 = math.log(p.radii[left])\n", + " lr1 = math.log(p.radii[right])\n", + " ld0 = math.log(p.densities[left])\n", + " ld1 = math.log(p.densities[right])\n", + " fraction = (math.log(TAIL_CUTOFF) - ld0) / (ld1 - ld0)\n", + " return math.exp(lr0 + fraction * (lr1 - lr0))\n", + "\n", + "\n", + "def rho(symbol: str, radius: float) -> float:\n", + " return profile(symbol)._evaluate_bohr(radius)\n", + "\n", + "\n", + "def objective(atom_a: str, atom_b: str, x: float, distance: float) -> float:\n", + " return rho(atom_a, x) + rho(atom_b, distance - x)\n", + "\n", + "\n", + "def continuous_log_density(symbol: str, radius: float) -> float:\n", + " p = profile(symbol)\n", + " if radius <= p.radii[0]:\n", + " return math.log(p.densities[0])\n", + " index = min(bisect_right(p.radii, radius) - 1, len(p.radii) - 2)\n", + " lr0 = math.log(p.radii[index])\n", + " lr1 = math.log(p.radii[index + 1])\n", + " ld0 = math.log(p.densities[index])\n", + " ld1 = math.log(p.densities[index + 1])\n", + " fraction = (math.log(radius) - lr0) / (lr1 - lr0)\n", + " return ld0 + fraction * (ld1 - ld0)\n", + "\n", + "\n", + "def equal_contribution_position(atom_a: str, atom_b: str, distance: float) -> float | None:\n", + " if atom_a == atom_b:\n", + " return distance / 2.0\n", + "\n", + " def difference(x: float) -> float:\n", + " return continuous_log_density(atom_a, x) - continuous_log_density(\n", + " atom_b, distance - x\n", + " )\n", + "\n", + " left_value = difference(0.0)\n", + " right_value = difference(distance)\n", + " if left_value == 0.0:\n", + " return 0.0\n", + " if right_value == 0.0:\n", + " return distance\n", + " if left_value * right_value > 0.0:\n", + " return None\n", + "\n", + " left = 0.0\n", + " right = distance\n", + " for _ in range(100):\n", + " midpoint = (left + right) / 2.0\n", + " value = difference(midpoint)\n", + " if right - left <= 1.0e-10:\n", + " break\n", + " if value > 0.0:\n", + " left = midpoint\n", + " else:\n", + " right = midpoint\n", + " return (left + right) / 2.0\n", + "\n", + "\n", + "def component_values(atom_a: str, atom_b: str, distance: float, x: float):\n", + " value_a = rho(atom_a, x)\n", + " value_b = rho(atom_b, distance - x)\n", + " return value_a, value_b, value_a + value_b\n", + "\n", + "\n", + "def boundary_estimate(atom_a: str, atom_b: str, distance: float) -> StudyEstimate:\n", + " radius_a = cutoff_radius(atom_a)\n", + " radius_b = cutoff_radius(atom_b)\n", + " separation = distance - radius_a - radius_b\n", + "\n", + " if atom_a == atom_b:\n", + " x = distance / 2.0\n", + " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", + " status = \"low_density_gap\" if separation > 0.0 else \"ok\"\n", + " return StudyEstimate(\n", + " \"homonuclear_midpoint\", status, x, value_a, value_b, total, separation\n", + " )\n", + "\n", + " if separation > 0.0:\n", + " x = (radius_a + distance - radius_b) / 2.0\n", + " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", + " return StudyEstimate(\n", + " \"cutoff_gap_midpoint\",\n", + " \"low_density_gap\",\n", + " x,\n", + " value_a,\n", + " value_b,\n", + " total,\n", + " separation,\n", + " )\n", + "\n", + " x = equal_contribution_position(atom_a, atom_b, distance)\n", + " if x is None or not (0.0 < x < distance):\n", + " return StudyEstimate(\n", + " \"none\", \"one_atom_dominates\", None, None, None, None, separation\n", + " )\n", + " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", + " return StudyEstimate(\n", + " \"equal_proatom_density\", \"ok\", x, value_a, value_b, total, separation\n", + " )\n", + "\n", + "\n", + "def overlap_interval(atom_a: str, atom_b: str, distance: float) -> tuple[float, float]:\n", + " return (\n", + " max(0.0, distance - cutoff_radius(atom_b)),\n", + " min(distance, cutoff_radius(atom_a)),\n", + " )\n", + "\n", + "\n", + "def golden_minimize(function, left: float, right: float):\n", + " ratio = (math.sqrt(5.0) - 1.0) / 2.0\n", + " x1 = right - ratio * (right - left)\n", + " x2 = left + ratio * (right - left)\n", + " f1 = function(x1)\n", + " f2 = function(x2)\n", + " for _ in range(70):\n", + " if f1 <= f2:\n", + " right, x2, f2 = x2, x1, f1\n", + " x1 = right - ratio * (right - left)\n", + " f1 = function(x1)\n", + " else:\n", + " left, x1, f1 = x1, x2, f2\n", + " x2 = left + ratio * (right - left)\n", + " f2 = function(x2)\n", + " return (x1, f1) if f1 <= f2 else (x2, f2)\n", + "\n", + "\n", + "def grid_valleys(atom_a: str, atom_b: str, distance: float, step: float):\n", + " left, right = overlap_interval(atom_a, atom_b, distance)\n", + " if right <= left:\n", + " return [], 0\n", + "\n", + " count = max(3, math.ceil((right - left) / step) + 1)\n", + " coordinates = [left + (right - left) * i / (count - 1) for i in range(count)]\n", + " equal = equal_contribution_position(atom_a, atom_b, distance)\n", + " if equal is not None and left < equal < right:\n", + " coordinates.append(equal)\n", + " coordinates = sorted(set(coordinates))\n", + "\n", + " values = [objective(atom_a, atom_b, x, distance) for x in coordinates]\n", + " function = lambda x: objective(atom_a, atom_b, x, distance)\n", + " candidates = []\n", + " for index in range(1, len(coordinates) - 1):\n", + " if values[index] <= values[index - 1] and values[index] <= values[index + 1]:\n", + " candidates.append(\n", + " golden_minimize(\n", + " function, coordinates[index - 1], coordinates[index + 1]\n", + " )\n", + " )\n", + " return candidates, len(coordinates)\n", + "\n", + "\n", + "def coalesce(candidates, resolution: float = PRACTICAL_RESOLUTION):\n", + " if not candidates:\n", + " return []\n", + " ordered = sorted(candidates, key=lambda item: item[0])\n", + " groups: list[list[tuple[float, float]]] = []\n", + " for candidate in ordered:\n", + " if not groups or candidate[0] - groups[-1][-1][0] > resolution:\n", + " groups.append([candidate])\n", + " else:\n", + " groups[-1].append(candidate)\n", + " return [min(group, key=lambda item: item[1]) for group in groups]\n", + "\n", + "\n", + "def selections_compatible(first, second) -> bool:\n", + " if first is None or second is None:\n", + " return first is second\n", + " position_ok = abs(first[0] - second[0]) <= PRACTICAL_RESOLUTION\n", + " density_ok = abs(first[1] - second[1]) <= (\n", + " COMPETITIVE_RELATIVE_GAP * min(first[1], second[1])\n", + " )\n", + " return position_ok and density_ok\n", + "\n", + "\n", + "def practical_minimum_estimate(atom_a: str, atom_b: str, distance: float) -> StudyEstimate:\n", + " separation = distance - cutoff_radius(atom_a) - cutoff_radius(atom_b)\n", + " if atom_a == atom_b:\n", + " x = distance / 2.0\n", + " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", + " status = \"low_density_gap\" if separation > 0.0 else \"ok\"\n", + " return StudyEstimate(\n", + " \"homonuclear_midpoint\",\n", + " status,\n", + " x,\n", + " value_a,\n", + " value_b,\n", + " total,\n", + " separation,\n", + " search_resolution=PRACTICAL_RESOLUTION,\n", + " search_converged=True,\n", + " )\n", + "\n", + " left, right = overlap_interval(atom_a, atom_b, distance)\n", + " if right <= left:\n", + " return StudyEstimate(\n", + " \"none\", \"low_density_gap\", None, None, None, None, separation\n", + " )\n", + "\n", + " all_candidates = []\n", + " pass_best = []\n", + " probes = 0\n", + " for step in (INITIAL_STEP, CONFIRM_STEP):\n", + " candidates, count = grid_valleys(atom_a, atom_b, distance, step)\n", + " probes += count\n", + " all_candidates.extend(candidates)\n", + " pass_best.append(min(candidates, key=lambda item: item[1]) if candidates else None)\n", + "\n", + " need_fallback = not selections_compatible(pass_best[0], pass_best[1])\n", + " if need_fallback:\n", + " candidates, count = grid_valleys(atom_a, atom_b, distance, FALLBACK_STEP)\n", + " probes += count\n", + " all_candidates.extend(candidates)\n", + " pass_best.append(min(candidates, key=lambda item: item[1]) if candidates else None)\n", + "\n", + " resolved = coalesce(all_candidates)\n", + " if not resolved:\n", + " return StudyEstimate(\n", + " \"none\",\n", + " \"no_resolved_interior_minimum\",\n", + " None,\n", + " None,\n", + " None,\n", + " None,\n", + " separation,\n", + " search_resolution=FALLBACK_STEP if need_fallback else CONFIRM_STEP,\n", + " search_converged=not need_fallback,\n", + " probes=probes,\n", + " )\n", + "\n", + " ordered = sorted(resolved, key=lambda item: item[1])\n", + " selected = ordered[0]\n", + " alternative = ordered[1] if len(ordered) > 1 else None\n", + " converged = (\n", + " selections_compatible(pass_best[-2], pass_best[-1])\n", + " if need_fallback\n", + " else True\n", + " )\n", + "\n", + " status = \"ok\"\n", + " relative_gap = None\n", + " if alternative is not None:\n", + " relative_gap = (alternative[1] - selected[1]) / selected[1]\n", + " if relative_gap <= COMPETITIVE_RELATIVE_GAP:\n", + " status = \"ambiguous_competing_minima\"\n", + " if not converged:\n", + " status = \"search_unstable\"\n", + "\n", + " boundary_value = min(\n", + " objective(atom_a, atom_b, 0.0, distance),\n", + " objective(atom_a, atom_b, distance, distance),\n", + " )\n", + " if boundary_value < selected[1] * (1.0 - 2.0e-14):\n", + " status = \"boundary_dominated\"\n", + "\n", + " value_a, value_b, total = component_values(\n", + " atom_a, atom_b, distance, selected[0]\n", + " )\n", + " return StudyEstimate(\n", + " \"promolecular_density_minimum\",\n", + " status,\n", + " selected[0],\n", + " value_a,\n", + " value_b,\n", + " total,\n", + " separation,\n", + " search_resolution=FALLBACK_STEP if need_fallback else CONFIRM_STEP,\n", + " search_converged=converged,\n", + " alternative_position=None if alternative is None else alternative[0],\n", + " relative_depth_gap=relative_gap,\n", + " probes=probes,\n", + " )\n", + "\n", + "\n", + "def dense_reference_minimum(atom_a: str, atom_b: str, distance: float, step=0.001):\n", + " if atom_a == atom_b:\n", + " x = distance / 2.0\n", + " return x, objective(atom_a, atom_b, x, distance)\n", + " candidates, _ = grid_valleys(atom_a, atom_b, distance, step)\n", + " if not candidates:\n", + " return None\n", + " return min(candidates, key=lambda item: item[1])\n", + "\n", + "\n", + "def deterministic_cases(count: int, seed: int = 20260713):\n", + " symbols = [element.symbol for element in ar.iter_elements() if element.z <= 103]\n", + " generator = random.Random(seed)\n", + " cases = []\n", + " for index in range(count):\n", + " atom_a = generator.choice(symbols)\n", + " atom_b = generator.choice(symbols)\n", + " if index % 2:\n", + " distance = generator.uniform(0.2, 12.0)\n", + " else:\n", + " distance = 10.0 ** generator.uniform(math.log10(0.2), math.log10(12.0))\n", + " cases.append((atom_a, atom_b, distance))\n", + " return cases\n", + "\n", + "\n", + "def run_comparison(count=300):\n", + " cases = deterministic_cases(count)\n", + " practical = []\n", + " reference = []\n", + " start = time.perf_counter()\n", + " for case in cases:\n", + " practical.append(practical_minimum_estimate(*case))\n", + " practical_seconds = time.perf_counter() - start\n", + "\n", + " start = time.perf_counter()\n", + " for case in cases:\n", + " reference.append(dense_reference_minimum(*case))\n", + " reference_seconds = time.perf_counter() - start\n", + "\n", + " status_mismatch = 0\n", + " errors = []\n", + " for case, estimate, expected in zip(cases, practical, reference):\n", + " found = estimate.position is not None\n", + " expected_found = expected is not None\n", + " if found != expected_found:\n", + " # A sub-resolution dense-reference minimum can intentionally be absent.\n", + " status_mismatch += 1\n", + " continue\n", + " if found:\n", + " dx = abs(estimate.position - expected[0])\n", + " relative_density = abs(estimate.rho_sum - expected[1]) / expected[1]\n", + " errors.append((dx, relative_density, case, estimate, expected))\n", + "\n", + " return {\n", + " \"cases\": cases,\n", + " \"practical\": practical,\n", + " \"reference\": reference,\n", + " \"practical_seconds\": practical_seconds,\n", + " \"reference_seconds\": reference_seconds,\n", + " \"status_mismatch\": status_mismatch,\n", + " \"errors\": errors,\n", + " }\n" + ] + }, + { + "cell_type": "markdown", + "id": "bedc9faf", + "metadata": {}, + "source": [ + "\n", + "## Cutoff radii across H–Lr\n", + "\n", + "Every packaged profile is monotone over its stored positive segments, so the\n", + "cutoff radius is unique and can be obtained by directly inverting one log–log\n", + "segment. The plot shows that the chosen contour is deliberately diffuse: the\n", + "radii span roughly 1.8–4.2 Å.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e69152d0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-13T17:50:59.932778Z", + "iopub.status.busy": "2026-07-13T17:50:59.932476Z", + "iopub.status.idle": "2026-07-13T17:51:00.494663Z", + "shell.execute_reply": "2026-07-13T17:51:00.493410Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Smallest cutoff radius: He = 3.373985 bohr (1.785436 Å)\n", + "Largest cutoff radius: Ba = 7.966363 bohr (4.215618 Å)\n", + "Representative radii:\n", + " H: 4.128012 bohr = 2.184450 Å\n", + " He: 3.373985 bohr = 1.785436 Å\n", + " Li: 6.469571 bohr = 3.423549 Å\n", + " C: 4.895923 bohr = 2.590811 Å\n", + " O: 4.333035 bohr = 2.292943 Å\n", + " Fe: 6.132435 bohr = 3.245145 Å\n", + " U: 7.398370 bohr = 3.915049 Å\n", + " Lr: 7.736203 bohr = 4.093822 Å\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_7667/2764722380.py:32: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", + " plt.show()\n" + ] + } + ], + "source": [ + "\n", + "cutoff_rows = []\n", + "for element in ar.iter_elements():\n", + " if element.z <= 103:\n", + " radius_bohr = cutoff_radius(element.symbol)\n", + " cutoff_rows.append(\n", + " (element.z, element.symbol, radius_bohr, radius_bohr * ar.proatoms.BOHR_TO_ANGSTROM)\n", + " )\n", + "\n", + "smallest = min(cutoff_rows, key=lambda row: row[2])\n", + "largest = max(cutoff_rows, key=lambda row: row[2])\n", + "print(\n", + " f\"Smallest cutoff radius: {smallest[1]} = {smallest[2]:.6f} bohr \"\n", + " f\"({smallest[3]:.6f} Å)\"\n", + ")\n", + "print(\n", + " f\"Largest cutoff radius: {largest[1]} = {largest[2]:.6f} bohr \"\n", + " f\"({largest[3]:.6f} Å)\"\n", + ")\n", + "print(\"Representative radii:\")\n", + "for symbol in (\"H\", \"He\", \"Li\", \"C\", \"O\", \"Fe\", \"U\", \"Lr\"):\n", + " row = next(item for item in cutoff_rows if item[1] == symbol)\n", + " print(f\" {symbol:>2}: {row[2]:.6f} bohr = {row[3]:.6f} Å\")\n", + "\n", + "if plt is not None:\n", + " figure, axis = plt.subplots()\n", + " axis.plot([row[0] for row in cutoff_rows], [row[3] for row in cutoff_rows])\n", + " axis.set_xlabel(\"Atomic number\")\n", + " axis.set_ylabel(\"Cutoff radius (Å)\")\n", + " axis.set_title(r\"Neutral-proatom radius at $\\rho=10^{-4}$ electron/bohr$^3$\")\n", + " figure.tight_layout()\n", + " figure.savefig(ASSET_DIR / \"cutoff-radii.png\", dpi=160)\n", + " plt.show()\n", + "else:\n", + " print(\"Matplotlib is unavailable; the cutoff-radius plot was skipped.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "31b82eb4", + "metadata": {}, + "source": [ + "\n", + "## Representative pairwise results\n", + "\n", + "The same profiles can support two useful but different answers. The boundary\n", + "mode is the recommended geometry-facing default. The minimum mode is retained\n", + "as a Bader-oriented neutral-promolecular proxy and may explicitly decline to\n", + "return a resolved minimum.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b318ce07", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-13T17:51:00.496829Z", + "iopub.status.busy": "2026-07-13T17:51:00.496644Z", + "iopub.status.idle": "2026-07-13T17:51:00.503842Z", + "shell.execute_reply": "2026-07-13T17:51:00.502753Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pair R/bohr boundary method boundary x minimum status minimum x\n", + "Li-Li 5.0000 homonuclear_midpoint 2.500000000 ok 2.500000000\n", + "H-U 3.8000 equal_proatom_density 1.184253035 ok 1.191871522\n", + "C-O 1.5000 equal_proatom_density 0.556215252 ok 0.611760239\n", + "H-O 12.0000 cutoff_gap_midpoint 5.897488625 low_density_gap None\n", + "H-U 1.5800 equal_proatom_density 0.017084468 no_resolved_interior_minimum None\n" + ] + } + ], + "source": [ + "\n", + "examples = [\n", + " (\"Li\", \"Li\", 5.0),\n", + " (\"H\", \"U\", 3.8),\n", + " (\"C\", \"O\", 1.5),\n", + " (\"H\", \"O\", 12.0),\n", + " (\"H\", \"U\", 1.58),\n", + "]\n", + "\n", + "header = (\n", + " \"pair\", \"R/bohr\", \"boundary method\", \"boundary x\", \"minimum status\", \"minimum x\"\n", + ")\n", + "print(f\"{header[0]:<8} {header[1]:>9} {header[2]:<25} {header[3]:>12} {header[4]:<31} {header[5]:>12}\")\n", + "for atom_a, atom_b, distance in examples:\n", + " boundary = boundary_estimate(atom_a, atom_b, distance)\n", + " minimum = practical_minimum_estimate(atom_a, atom_b, distance)\n", + " bx = \"None\" if boundary.position is None else f\"{boundary.position:.9f}\"\n", + " mx = \"None\" if minimum.position is None else f\"{minimum.position:.9f}\"\n", + " print(\n", + " f\"{atom_a+'-'+atom_b:<8} {distance:9.4f} {boundary.method:<25} {bx:>12} \"\n", + " f\"{minimum.status:<31} {mx:>12}\"\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "id": "69d51b79", + "metadata": {}, + "source": [ + "\n", + "## Why homonuclear symmetry overrides raw shell minima\n", + "\n", + "For Li–Li at 5 bohr, the exact all-minima investigation found symmetric\n", + "off-centre minima slightly below the midpoint. Returning either off-centre\n", + "point would be a poor pairwise separator for identical atoms. Both revised\n", + "modes therefore return \\(R/2\\) and preserve the raw profile behaviour only as a\n", + "diagnostic.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f8592476", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-13T17:51:00.505976Z", + "iopub.status.busy": "2026-07-13T17:51:00.505758Z", + "iopub.status.idle": "2026-07-13T17:51:00.724501Z", + "shell.execute_reply": "2026-07-13T17:51:00.723118Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Raw fine-grid valley coordinates near the internuclear region:\n", + " x=2.118430478685 bohr, rho_sum=0.00654500903481724\n", + " x=2.472517881659 bohr, rho_sum=0.00658890668816022\n", + " x=2.500000037765 bohr, rho_sum=0.00658920833374835\n", + " x=2.527482056015 bohr, rho_sum=0.00658890668816022\n", + " x=2.881569505107 bohr, rho_sum=0.00654500903481724\n", + "Symmetry-selected coordinate: 2.500000000000 bohr\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_7667/2275166722.py:24: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", + " plt.show()\n" + ] + } + ], + "source": [ + "\n", + "atom_a, atom_b, distance = \"Li\", \"Li\", 5.0\n", + "xs = [1.75 + (3.25 - 1.75) * i / 1600 for i in range(1601)]\n", + "ys = [objective(atom_a, atom_b, x, distance) for x in xs]\n", + "raw_valleys, _ = grid_valleys(atom_a, atom_b, distance, 0.001)\n", + "\n", + "print(\"Raw fine-grid valley coordinates near the internuclear region:\")\n", + "for x, value in raw_valleys:\n", + " if 1.75 <= x <= 3.25:\n", + " print(f\" x={x:.12f} bohr, rho_sum={value:.15g}\")\n", + "print(f\"Symmetry-selected coordinate: {distance/2:.12f} bohr\")\n", + "\n", + "if plt is not None:\n", + " figure, axis = plt.subplots()\n", + " axis.plot(xs, ys, label=r\"$\\rho_{Li}(x)+\\rho_{Li}(R-x)$\")\n", + " shown = [(x, value) for x, value in raw_valleys if 1.75 <= x <= 3.25]\n", + " axis.scatter([item[0] for item in shown], [item[1] for item in shown], label=\"raw local valleys\")\n", + " axis.axvline(distance / 2.0, linestyle=\"--\", label=\"symmetry midpoint\")\n", + " axis.set_xlabel(\"Position from atom A (bohr)\")\n", + " axis.set_ylabel(r\"Promolecular line density (electron/bohr$^3$)\")\n", + " axis.set_title(\"Li–Li at R = 5 bohr\")\n", + " axis.legend()\n", + " figure.tight_layout()\n", + " figure.savefig(ASSET_DIR / \"li-li-symmetry.png\", dpi=160)\n", + " plt.show()\n", + "else:\n", + " print(\"Matplotlib is unavailable; the Li–Li plot was skipped.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "aa43c79c", + "metadata": {}, + "source": [ + "\n", + "## Boundary and minimum are different scientific definitions\n", + "\n", + "For C–O at 1.5 bohr, the equal-contribution point and the selected promolecular\n", + "minimum differ visibly. This is not a solver failure: the first balances the two\n", + "reference-atom contributions, while the second minimizes their sum.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9f5d836a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-13T17:51:00.727097Z", + "iopub.status.busy": "2026-07-13T17:51:00.726871Z", + "iopub.status.idle": "2026-07-13T17:51:00.926802Z", + "shell.execute_reply": "2026-07-13T17:51:00.925663Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Boundary coordinate: 0.556215252014 bohr\n", + "Minimum coordinate: 0.611760239435 bohr\n", + "Coordinate difference: 0.055544987420 bohr\n", + "Boundary component densities: 0.414069174671, 0.414069174631\n", + "Minimum component densities: 0.325849689041, 0.475793723795\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_7667/4039951775.py:30: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", + " plt.show()\n" + ] + } + ], + "source": [ + "\n", + "atom_a, atom_b, distance = \"C\", \"O\", 1.5\n", + "boundary = boundary_estimate(atom_a, atom_b, distance)\n", + "minimum = practical_minimum_estimate(atom_a, atom_b, distance)\n", + "xs = [distance * i / 1000 for i in range(1001)]\n", + "rho_a_values = [rho(atom_a, x) for x in xs]\n", + "rho_b_values = [rho(atom_b, distance - x) for x in xs]\n", + "total_values = [a + b for a, b in zip(rho_a_values, rho_b_values)]\n", + "\n", + "print(f\"Boundary coordinate: {boundary.position:.12f} bohr\")\n", + "print(f\"Minimum coordinate: {minimum.position:.12f} bohr\")\n", + "print(f\"Coordinate difference: {abs(boundary.position-minimum.position):.12f} bohr\")\n", + "print(f\"Boundary component densities: {boundary.rho_a:.12g}, {boundary.rho_b:.12g}\")\n", + "print(f\"Minimum component densities: {minimum.rho_a:.12g}, {minimum.rho_b:.12g}\")\n", + "\n", + "if plt is not None:\n", + " figure, axis = plt.subplots()\n", + " axis.plot(xs, rho_a_values, label=r\"$\\rho_C(x)$\")\n", + " axis.plot(xs, rho_b_values, label=r\"$\\rho_O(R-x)$\")\n", + " axis.plot(xs, total_values, label=\"sum\")\n", + " axis.axvline(boundary.position, linestyle=\"--\", label=\"proatomic boundary\")\n", + " axis.axvline(minimum.position, linestyle=\":\", label=\"promolecular minimum\")\n", + " axis.set_xlim(0.35, 0.90)\n", + " axis.set_ylim(0.15, 2.8)\n", + " axis.set_xlabel(\"Position from C (bohr)\")\n", + " axis.set_ylabel(r\"Density (electron/bohr$^3$)\")\n", + " axis.set_title(\"C–O interatomic region at R = 1.5 bohr\")\n", + " axis.legend(loc=\"upper right\")\n", + " figure.tight_layout()\n", + " figure.savefig(ASSET_DIR / \"c-o-method-comparison.png\", dpi=160)\n", + " plt.show()\n", + "else:\n", + " print(\"Matplotlib is unavailable; the C–O plot was skipped.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "442a4cb8", + "metadata": {}, + "source": [ + "\n", + "## Practical minimum search against a slower reference\n", + "\n", + "The production proposal is not an all-minima theorem. It asks whether a valley\n", + "is resolved at 0.01 bohr and checks it at two grid scales, with one finer\n", + "fallback. The comparison below uses 300 deterministic H–Lr cases, plus seven\n", + "adversarial cases discovered during Stage 4A, against a slower 0.001-bohr\n", + "fine-grid search over the same cutoff-bounded interval.\n", + "\n", + "A dense-reference minimum may be intentionally rejected when it is narrower\n", + "than the public resolution or lies immediately beside a nucleus at an extreme\n", + "short distance.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0767eaed", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-13T17:51:00.929029Z", + "iopub.status.busy": "2026-07-13T17:51:00.928793Z", + "iopub.status.idle": "2026-07-13T17:51:02.256214Z", + "shell.execute_reply": "2026-07-13T17:51:02.255336Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cases: 307\n", + "Both methods returned a minimum: 298\n", + "Both returned no resolved minimum: 7\n", + "Practical-resolution nonmatches: 2\n", + "Joint results within 0.01 bohr: 298/298\n", + "Largest position difference: 0.0012907 bohr for C-O at R=1.5 bohr\n", + "Largest relative density difference: 5.96583e-06\n", + "Practical search time: 0.244 s\n", + "Slower reference time: 1.076 s\n", + "\n", + "Intentionally unresolved dense-reference minima:\n", + " C-Lu R=0.221730136797: status=no_resolved_interior_minimum, reference_x=0.00284120268288 bohr\n", + " He-Ru R=0.655290907435: status=no_resolved_interior_minimum, reference_x=0.0112558333887 bohr\n" + ] + } + ], + "source": [ + "\n", + "adversarial_cases = [\n", + " (\"Ni\", \"Te\", 1.5897045597171517),\n", + " (\"Al\", \"Pb\", 1.3645306063699802),\n", + " (\"Pr\", \"Re\", 0.7592738817632778),\n", + " (\"C\", \"Lu\", 0.2217301367970158),\n", + " (\"He\", \"Ru\", 0.6552909074349161),\n", + " (\"H\", \"U\", 1.58),\n", + " (\"C\", \"O\", 1.5),\n", + "]\n", + "cases = deterministic_cases(300) + adversarial_cases\n", + "\n", + "start = time.perf_counter()\n", + "practical_results = [practical_minimum_estimate(*case) for case in cases]\n", + "practical_seconds = time.perf_counter() - start\n", + "\n", + "start = time.perf_counter()\n", + "reference_results = [dense_reference_minimum(*case) for case in cases]\n", + "reference_seconds = time.perf_counter() - start\n", + "\n", + "joint_errors = []\n", + "intentional_nonmatches = []\n", + "both_absent = 0\n", + "for case, practical, reference in zip(cases, practical_results, reference_results):\n", + " if practical.position is None and reference is None:\n", + " both_absent += 1\n", + " elif (practical.position is None) != (reference is None):\n", + " intentional_nonmatches.append((case, practical, reference))\n", + " else:\n", + " joint_errors.append(\n", + " (\n", + " abs(practical.position - reference[0]),\n", + " abs(practical.rho_sum - reference[1]) / reference[1],\n", + " case,\n", + " )\n", + " )\n", + "\n", + "max_position = max(joint_errors, key=lambda item: item[0])\n", + "max_density = max(joint_errors, key=lambda item: item[1])\n", + "within_resolution = sum(error[0] <= PRACTICAL_RESOLUTION for error in joint_errors)\n", + "\n", + "print(f\"Cases: {len(cases)}\")\n", + "print(f\"Both methods returned a minimum: {len(joint_errors)}\")\n", + "print(f\"Both returned no resolved minimum: {both_absent}\")\n", + "print(f\"Practical-resolution nonmatches: {len(intentional_nonmatches)}\")\n", + "print(f\"Joint results within 0.01 bohr: {within_resolution}/{len(joint_errors)}\")\n", + "print(\n", + " f\"Largest position difference: {max_position[0]:.6g} bohr \"\n", + " f\"for {max_position[2][0]}-{max_position[2][1]} at R={max_position[2][2]:.6g} bohr\"\n", + ")\n", + "print(f\"Largest relative density difference: {max_density[1]:.6g}\")\n", + "print(f\"Practical search time: {practical_seconds:.3f} s\")\n", + "print(f\"Slower reference time: {reference_seconds:.3f} s\")\n", + "\n", + "if intentional_nonmatches:\n", + " print(\"\\nIntentionally unresolved dense-reference minima:\")\n", + " for case, practical, reference in intentional_nonmatches:\n", + " print(\n", + " f\" {case[0]}-{case[1]} R={case[2]:.12g}: \"\n", + " f\"status={practical.status}, reference_x={reference[0]:.12g} bohr\"\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "id": "b9d1be2c", + "metadata": {}, + "source": [ + "\n", + "## Cached timing comparison\n", + "\n", + "Timing is machine-dependent, so these figures are evidence rather than portable\n", + "thresholds. The important point is the scale difference: the boundary mode\n", + "needs one cached contour calculation plus a monotone bisection, while the\n", + "optional minimum mode remains below about a millisecond per ordinary pair in\n", + "this pure-Python prototype.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6a476f18", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-13T17:51:02.258944Z", + "iopub.status.busy": "2026-07-13T17:51:02.258712Z", + "iopub.status.idle": "2026-07-13T17:51:06.515839Z", + "shell.execute_reply": "2026-07-13T17:51:06.514778Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "boundary: median 0.0541 ms/pair; runs=[0.05746927700010929, 0.055642846000182544, 0.05410611999991488, 0.05177689599986479, 0.050906101000009585]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " minimum: median 0.7887 ms/pair; runs=[0.8100914009999087, 0.7820286119999764, 0.7892370699998992, 0.7887017269999888, 0.7813305360000413]\n" + ] + } + ], + "source": [ + "\n", + "import statistics\n", + "\n", + "timing_cases = deterministic_cases(1000)\n", + "for case in timing_cases[:30]:\n", + " boundary_estimate(*case)\n", + " practical_minimum_estimate(*case)\n", + "\n", + "for label, function in (\n", + " (\"boundary\", boundary_estimate),\n", + " (\"minimum\", practical_minimum_estimate),\n", + "):\n", + " samples = []\n", + " for _ in range(5):\n", + " start = time.perf_counter()\n", + " [function(*case) for case in timing_cases]\n", + " samples.append((time.perf_counter() - start) * 1000.0 / len(timing_cases))\n", + " print(f\"{label:>8}: median {statistics.median(samples):.4f} ms/pair; runs={samples}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "48e2df3d", + "metadata": {}, + "source": [ + "\n", + "## What the exhaustive Stage 4A work established\n", + "\n", + "The archived Stage 4A design, validation report, and two independent reviews\n", + "remain useful supporting evidence. They showed that an exact “return every\n", + "mathematical local minimum” contract really does require disproportionate\n", + "numerical machinery:\n", + "\n", + "| Case | Exact-topology observation | Product consequence |\n", + "|---|---|---|\n", + "| H–F near a knot | A minimum occurred 27 binary64 coordinates below a knot and needed high-precision derivative signs. | Important only when every microminimum is binding. |\n", + "| C–O exact knot | The stored knot and its immediate neighbor could differ by one density ULP. | Exact-knot topology is below the new 0.01-bohr resolution. |\n", + "| Li–Li, 5 bohr | Five local minima were found; the two off-centre minima were slightly deepest. | A raw minimum can violate the desired homonuclear separator symmetry. |\n", + "| H–U, 1.58 bohr | Fourteen isolated minima were found while a boundary value was lower. | Counting minima does not produce a useful IAS estimate. |\n", + "| At–Bk event collision | Two exact-distinct profile events rounded to one float and caused orientation-dependent candidate counts in the prototype. | Exact event identity is expensive and unnecessary for the revised contract. |\n", + "| Ne–Ne open segment | Ordinary log/exp value error exceeded the previous global-tie budget. | Numerical tie lists require a separate high-precision value model. |\n", + "\n", + "These are real numerical facts, not mistakes to hide. The revised API simply\n", + "places them outside the production semantics: boundary mode is unaffected, and\n", + "minimum mode deliberately coalesces or rejects sub-resolution structure.\n" + ] + }, + { + "cell_type": "markdown", + "id": "9abe91dc", + "metadata": {}, + "source": [ + "\n", + "## Decision recorded by this study\n", + "\n", + "The recommended Stage 4 API has three entry points:\n", + "\n", + "```python\n", + "estimate_proatomic_boundary(...)\n", + "estimate_promolecular_density_minimum(...)\n", + "estimate_ias_position(..., mode=\"boundary\" | \"minimum\")\n", + "```\n", + "\n", + "`boundary` is the default. It is the best fit for stable geometry and\n", + "Laguerre/Voronoi calibration. `minimum` remains available for Bader-oriented\n", + "research, but it searches only the significant-overlap region, returns one\n", + "resolved candidate rather than every local minimum, and reports ambiguity or a\n", + "non-result explicitly.\n", + "\n", + "For identical atoms, both modes return exactly `R/2`. No universal 0.5 Å ban is\n", + "needed: extreme short-distance cases naturally become `one_atom_dominates`,\n", + "`no_resolved_interior_minimum`, or another explicit diagnostic.\n", + "\n", + "The remaining scientific limitation is deliberate: neither neutral-proatom\n", + "mode has yet been calibrated against a curated set of molecular-density QTAIM\n", + "interatomic-surface coordinates.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/meta/test_notebooks.py b/tests/meta/test_notebooks.py index d420476..092b28c 100644 --- a/tests/meta/test_notebooks.py +++ b/tests/meta/test_notebooks.py @@ -17,6 +17,7 @@ def test_notebook_files_exist() -> None: "01-quickstart.ipynb", "02-policies-and-assessment.ipynb", "03-custom-sets-and-discovery.ipynb", + "04-ias-method-selection-study.ipynb", } actual = {path.name for path in NOTEBOOKS.glob("*.ipynb")} assert expected.issubset(actual) diff --git a/tools/check_notebooks.py b/tools/check_notebooks.py index 51d9dfa..7844111 100644 --- a/tools/check_notebooks.py +++ b/tools/check_notebooks.py @@ -20,6 +20,7 @@ "01-quickstart.ipynb", "02-policies-and-assessment.ipynb", "03-custom-sets-and-discovery.ipynb", + "04-ias-method-selection-study.ipynb", ) From 9799bcdd35f3be783719e06fe43440febd0c3a87 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Mon, 13 Jul 2026 23:40:47 +0300 Subject: [PATCH 06/23] feat: add two-mode proatomic IAS position estimates --- docs/api/index.md | 4 + docs/dev/ias_method_selection.md | 10 +- docs/guide/proatomic_density.md | 39 + src/atomref/__init__.py | 12 + src/atomref/proatoms.py | 1233 +++++++++++++++++++++- tests/meta/test_public_api.py | 6 + tests/proatoms/test_ias.py | 1432 ++++++++++++++++++++++++++ tests/proatoms/test_ias_reference.py | 770 ++++++++++++++ tools/release_check.py | 10 +- 9 files changed, 3510 insertions(+), 6 deletions(-) create mode 100644 tests/proatoms/test_ias.py create mode 100644 tests/proatoms/test_ias_reference.py diff --git a/docs/api/index.md b/docs/api/index.md index f56eb7c..5ed901b 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -10,6 +10,9 @@ documented because they expose the actual data model behind the package. - get a single value: use `get_covalent_radius(...)`, `get_vdw_radius(...)`, or `get_xh_bond_length(...)` +- evaluate a neutral proatomic profile or pairwise estimate: use + `get_proatomic_density(...)`, `estimate_proatomic_boundary(...)`, or + `estimate_promolecular_density_minimum(...)` - inspect provenance: use `lookup_covalent_radius(...)`, `lookup_vdw_radius(...)`, `lookup_xh_bond_length(...)`, or the generic `lookup_value(...)` @@ -29,5 +32,6 @@ documented because they expose the actual data model behind the package. - [Registry and packaged datasets](registry.md) - [Transfer models](transfer.md) - [Generic policy core](policy.md) +- [Proatomic density and pairwise estimates](proatoms.md) - [Radii API](radii.md) - [X–H API](xh.md) diff --git a/docs/dev/ias_method_selection.md b/docs/dev/ias_method_selection.md index db23fe8..620d5bb 100644 --- a/docs/dev/ias_method_selection.md +++ b/docs/dev/ias_method_selection.md @@ -34,7 +34,13 @@ components remain above the cutoff. The search has a declared spatial resolution of `0.01 bohr`. It returns one resolved minimum and, when useful, one competitive alternative. It does not -attempt to preserve every mathematical microminimum. +attempt to preserve every mathematical microminimum. Raw refined candidates +from the required `0.02` and `0.01 bohr` passes, and from the `0.005 bohr` +fallback when used, are combined before one resolution-coalescing step. +Position-sorted candidates connected by successive gaps below `0.01 bohr` +represent one resolved valley. Distinct adjacent binary64 grid coordinates are +retained so both endpoints and the midpoint remain available near cutoff +contact. Refined cutoff endpoints and nuclei are discarded rather than clamped. For identical atoms, symmetry still fixes the returned coordinate at `R/2`. The Li–Li example shows why this rule is necessary: the raw interpolant has @@ -82,7 +88,7 @@ Those facts matter under an “expose every local minimum” contract. They do n materially improve a stable pairwise divider, and the revised minimum mode coalesces or rejects structure below its declared resolution. -## Proposed public shape +## Public API ```python estimate_proatomic_boundary(...) diff --git a/docs/guide/proatomic_density.md b/docs/guide/proatomic_density.md index 2136135..d51a79a 100644 --- a/docs/guide/proatomic_density.md +++ b/docs/guide/proatomic_density.md @@ -46,3 +46,42 @@ The ZIP snapshot loads lazily through `get_builtin_set()`. Loaded sets, shared grids, stored values, and cached profile views are immutable. This API describes method-, basis-, state-, and sphericalization-defined isolated-atom references, not unique basis-independent atomic observables or molecular densities. + +## Pairwise estimates + +Three functions expose the Stage 4 pairwise model: + +```python +boundary = ar.estimate_proatomic_boundary("C", "O", 1.5, distance_unit="bohr") +minimum = ar.estimate_promolecular_density_minimum( + "C", "O", 1.5, distance_unit="bohr" +) +same_boundary = ar.estimate_ias_position( + "C", "O", 1.5, mode="boundary", distance_unit="bohr" +) +``` + +Coordinates are measured from the first atom toward the second. `boundary` is +the dispatcher default: it returns the equal-neutral-proatom divider while the +two fixed tail contours overlap, and the midpoint of their gap after they +separate. `minimum` instead returns one practically resolved minimum of the +summed promolecular line density, searched only where both components reach +the fixed `1e-4 electron/bohr^3` cutoff. Its declared spatial resolution is +`0.01 bohr`, and it never silently switches to boundary mode. For unlike +atoms, any returned primary or alternative minimum lies strictly inside that +overlap interval. Raw candidates from every executed grid pass are combined +before one position-connected resolution grouping; distinct adjacent binary64 +grid points are retained. A refinement that lands on a cutoff endpoint or +nucleus is discarded; when no strict-interior valley remains, the typed result +reports `no_resolved_interior_minimum` without a coordinate. + +Both functions return an immutable `IASPositionResult` containing the method, +status, units, component densities, cutoff geometry, search diagnostics, and +dataset/interpolation/numerical-contract provenance. Valid pairs for which a +mode is not scientifically applicable return a typed result with no coordinate; +missing profiles return `None`. + +These are neutral-proatom estimates. Neither mode locates a molecular QTAIM +zero-flux surface or an exact molecular-density critical point. The numerical +choice and its limitations are documented in the +[IAS method-selection study](../dev/ias_method_selection.md). diff --git a/src/atomref/__init__.py b/src/atomref/__init__.py index af9f4db..e7e8682 100644 --- a/src/atomref/__init__.py +++ b/src/atomref/__init__.py @@ -12,8 +12,14 @@ from .proatoms import ( BOHR_TO_ANGSTROM, DEFAULT_PROATOMIC_DENSITY_SET, + IAS_MINIMUM_RESOLUTION_BOHR, + PROATOMIC_TAIL_CUTOFF, + IASPositionResult, ProatomicDensityProfile, ProatomicDensitySet, + estimate_ias_position, + estimate_proatomic_boundary, + estimate_promolecular_density_minimum, get_proatomic_density, get_proatomic_density_profile, get_proatomic_density_set, @@ -95,8 +101,14 @@ "get_value", "BOHR_TO_ANGSTROM", "DEFAULT_PROATOMIC_DENSITY_SET", + "PROATOMIC_TAIL_CUTOFF", + "IAS_MINIMUM_RESOLUTION_BOHR", + "IASPositionResult", "ProatomicDensityProfile", "ProatomicDensitySet", + "estimate_proatomic_boundary", + "estimate_promolecular_density_minimum", + "estimate_ias_position", "list_proatomic_density_sets", "list_proatomic_density_set_infos", "get_proatomic_density_set", diff --git a/src/atomref/proatoms.py b/src/atomref/proatoms.py index 2f406d5..bd7a4ca 100644 --- a/src/atomref/proatoms.py +++ b/src/atomref/proatoms.py @@ -2,8 +2,8 @@ from __future__ import annotations -from bisect import bisect_left -from collections.abc import Mapping +from bisect import bisect_left, bisect_right +from collections.abc import Callable, Mapping from dataclasses import dataclass, field from functools import lru_cache import math @@ -35,6 +35,91 @@ _ANGSTROM_DENSITY_UNIT = "electron/angstrom^3" _INTERPOLATION_CONTRACT = "loglog_positive_bracketed_v1" +PROATOMIC_TAIL_CUTOFF = 1.0e-4 +"""Fixed per-atom tail cutoff in electron/bohr^3 for pairwise estimates.""" + +IAS_MINIMUM_RESOLUTION_BOHR = 0.01 +"""Declared spatial resolution of the practical minimum search, in bohr.""" + +_PAIRWISE_CONTRACT = "neutral_proatom_pairwise_cutoff_1e-4_resolution_0.01_v1" +_MINIMUM_INITIAL_SPACING_BOHR = 0.02 +_MINIMUM_CONFIRM_SPACING_BOHR = 0.01 +_MINIMUM_FALLBACK_SPACING_BOHR = 0.005 +_COMPETITIVE_RELATIVE_DEPTH = 1.0e-4 +_EQUALITY_BRACKET_TOLERANCE_BOHR = 1.0e-10 +_CUTOFF_REPRODUCTION_REL_TOL = 5.0e-14 +_FLOAT_COMPARISON_REL_TOL = 64.0 * 2.220446049250313e-16 + + +@dataclass(frozen=True, slots=True) +class IASPositionResult: + """Immutable result of one pairwise neutral-proatom estimate. + + Coordinates are measured from ``atom_a`` toward ``atom_b`` and are + reported in ``distance_unit``. Density-valued fields are reported in + ``density_unit``. A valid but scientifically non-applicable request uses + ``method="none"`` and leaves its coordinate fields as :data:`None`. + + ``boundary_dominated`` takes status precedence over ``search_unstable``, + which takes precedence over ``ambiguous_competing_minima``. The separate + ``search_converged`` and ``ambiguous`` fields preserve all diagnostics. + + At an odd subnormal homonuclear distance, the mathematical midpoint is not + representable in binary64. In that case the coordinate is the ordinary + floating-point result of ``R / 2``, the complementary coordinate preserves + the requested distance, and the fraction is derived from the exposed + coordinate rather than claiming an unrepresentable ``0.5`` position. + """ + + atom_a: str + atom_b: str + distance: float + distance_unit: str + density_unit: str + requested_mode: Literal["boundary", "minimum"] + method: Literal[ + "homonuclear_midpoint", + "equal_proatom_density", + "cutoff_gap_midpoint", + "promolecular_density_minimum", + "none", + ] + status: Literal[ + "ok", + "low_density_gap", + "one_atom_dominates", + "no_resolved_interior_minimum", + "boundary_dominated", + "ambiguous_competing_minima", + "search_unstable", + ] + position_from_a: float | None + position_from_b: float | None + fraction_from_a: float | None + rho_a: float | None + rho_b: float | None + rho_sum: float | None + cutoff_density: float + cutoff_radius_a: float + cutoff_radius_b: float + contour_separation: float + cutoff_regime: Literal["overlap", "contact", "gap"] + dominant_atom: str | None + dominant_atom_role: Literal["atom_a", "atom_b"] | None + alternative_position_from_a: float | None + alternative_position_from_b: float | None + alternative_rho_sum: float | None + relative_depth_gap: float | None + ambiguous: bool + search_resolution: float | None + search_converged: bool | None + search_passes: int | None + dataset_id: str + interpolation_contract: str + pairwise_contract: str + coordinate_orientation: str = "from_atom_a_toward_atom_b" + + ProatomicDensitySet = ElementRadialSet """Loaded immutable element-indexed proatomic-density dataset.""" @@ -297,7 +382,7 @@ def _radius_to_bohr( def _density_from_native( value: float, *, - density_unit: Literal["electron/bohr^3", "electron/angstrom^3"], + density_unit: str, ) -> float: """Convert electron/bohr^3 to the selected output density unit.""" @@ -440,3 +525,1145 @@ def get_proatomic_density( radius_unit=radius_unit, density_unit=density_unit, ) + + +@dataclass(frozen=True, slots=True) +class _PreparedPairwiseProfile: + """Cached native numerical representation used by pairwise estimates.""" + + profile: ProatomicDensityProfile + loglog_slopes: tuple[float, ...] + cutoff_radius_bohr: float + + +@dataclass(frozen=True, slots=True) +class _MinimumCandidate: + """One locally refined minimum candidate in native units.""" + + position_bohr: float + density: float + + +@dataclass(frozen=True, slots=True) +class _MinimumPass: + """Raw refined candidates from one deterministic grid pass.""" + + max_spacing_bohr: float + candidates: tuple[_MinimumCandidate, ...] + + @property + def selected(self) -> _MinimumCandidate | None: + """Return the lowest refined candidate, if any.""" + + return min( + self.candidates, + key=lambda candidate: ( + candidate.density, + candidate.position_bohr, + ), + default=None, + ) + + +@dataclass(frozen=True, slots=True) +class _NativeIASResult: + """Pairwise result before requested orientation and unit conversion.""" + + requested_mode: str + method: str + status: str + position_bohr: float | None + rho_a: float | None + rho_b: float | None + rho_sum: float | None + cutoff_radius_a_bohr: float + cutoff_radius_b_bohr: float + contour_separation_bohr: float + cutoff_regime: str + dominant_side: str | None = None + alternative_position_bohr: float | None = None + alternative_rho_sum: float | None = None + relative_depth_gap: float | None = None + ambiguous: bool = False + search_resolution_bohr: float | None = None + search_converged: bool | None = None + search_passes: int | None = None + + +def _prepare_pairwise_profile( + profile: ProatomicDensityProfile, +) -> _PreparedPairwiseProfile: + """Validate and prepare one profile for the fixed pairwise cutoff. + + The inverse cutoff coordinate is obtained analytically from the one + bracketing log-log segment. ``_CUTOFF_REPRODUCTION_REL_TOL`` is a + conservative binary64 envelope; the packaged H-Lr maximum observed error + is substantially smaller. + """ + + densities = profile.densities + if any(right >= left for left, right in zip(densities, densities[1:])): + raise DatasetError( + "pairwise proatomic profiles must be strictly decreasing for " + f"{profile.ref!r}, Z={profile.atomic_number}" + ) + if densities[0] <= PROATOMIC_TAIL_CUTOFF: + raise DatasetError( + "first proatomic density must exceed the pairwise tail cutoff for " + f"{profile.ref!r}, Z={profile.atomic_number}" + ) + + try: + right = next( + index + for index, density in enumerate(densities) + if density <= PROATOMIC_TAIL_CUTOFF + ) + except StopIteration as exc: + raise DatasetError( + "proatomic density does not fall below the pairwise tail cutoff for " + f"{profile.ref!r}, Z={profile.atomic_number}" + ) from exc + if not any( + radius < profile.public_max_radius_bohr + and density < PROATOMIC_TAIL_CUTOFF + for radius, density in zip(profile.radii, densities) + ): + raise DatasetError( + "proatomic density must fall below the pairwise tail cutoff before " + f"the public limit for {profile.ref!r}, Z={profile.atomic_number}" + ) + + slopes = tuple( + (right_log_density - left_log_density) + / (right_log_radius - left_log_radius) + for left_log_radius, right_log_radius, left_log_density, right_log_density + in zip( + profile._log_radii, + profile._log_radii[1:], + profile._log_densities, + profile._log_densities[1:], + ) + ) + + if densities[right] == PROATOMIC_TAIL_CUTOFF: + cutoff_radius = profile.radii[right] + else: + left = right - 1 + cutoff_log_radius = profile._log_radii[left] + ( + math.log(PROATOMIC_TAIL_CUTOFF) - profile._log_densities[left] + ) / slopes[left] + cutoff_radius = math.exp(cutoff_log_radius) + + if not math.isfinite(cutoff_radius) or not ( + 0.0 < cutoff_radius < profile.public_max_radius_bohr + ): + raise DatasetError( + "pairwise tail cutoff is not reached before the public radius limit " + f"for {profile.ref!r}, Z={profile.atomic_number}" + ) + + reproduced_log_density = profile._log_densities[right - 1] + slopes[ + right - 1 + ] * (math.log(cutoff_radius) - profile._log_radii[right - 1]) + reproduced = math.exp(reproduced_log_density) + if not math.isclose( + reproduced, + PROATOMIC_TAIL_CUTOFF, + rel_tol=_CUTOFF_REPRODUCTION_REL_TOL, + abs_tol=0.0, + ): + raise DatasetError( + "analytical pairwise tail-cutoff inversion failed for " + f"{profile.ref!r}, Z={profile.atomic_number}" + ) + + return _PreparedPairwiseProfile( + profile=profile, + loglog_slopes=slopes, + cutoff_radius_bohr=cutoff_radius, + ) + + +@lru_cache(maxsize=None) +def _get_prepared_pairwise_profile_cached( + ref: DatasetRef, + atomic_number: int, +) -> _PreparedPairwiseProfile: + """Return one cached pairwise representation by canonical dataset key.""" + + return _prepare_pairwise_profile(_get_profile_cached(ref, atomic_number)) + + +def _prepared_pairwise_profile( + profile: ProatomicDensityProfile, +) -> _PreparedPairwiseProfile: + """Return the cached pairwise representation for a packaged profile.""" + + return _get_prepared_pairwise_profile_cached( + profile.ref, + profile.atomic_number, + ) + + +def _continuous_log_density_bohr( + prepared: _PreparedPairwiseProfile, + radius_bohr: float, +) -> float: + """Evaluate the continuous accepted log-log representation in log space.""" + + profile = prepared.profile + radii = profile.radii + if radius_bohr <= radii[0]: + return profile._log_densities[0] + + left = min(bisect_right(radii, radius_bohr) - 1, len(radii) - 2) + return profile._log_densities[left] + prepared.loglog_slopes[left] * ( + math.log(radius_bohr) - profile._log_radii[left] + ) + + +def _evaluate_prepared_density_bohr( + prepared: _PreparedPairwiseProfile, + radius_bohr: float, +) -> float: + """Evaluate the exact Stage 3 convention using cached segment slopes.""" + + profile = prepared.profile + radii = profile.radii + if radius_bohr <= radii[0]: + return profile.densities[0] + right = bisect_left(radii, radius_bohr) + if right < len(radii) and radii[right] == radius_bohr: + return profile.densities[right] + left = right - 1 + return math.exp( + profile._log_densities[left] + + prepared.loglog_slopes[left] + * (math.log(radius_bohr) - profile._log_radii[left]) + ) + + +def _component_values( + profile_a: _PreparedPairwiseProfile, + profile_b: _PreparedPairwiseProfile, + distance_bohr: float, + position_bohr: float, +) -> tuple[float, float, float]: + """Evaluate pair components and their sum in native units.""" + + rho_a = _evaluate_prepared_density_bohr(profile_a, position_bohr) + rho_b = _evaluate_prepared_density_bohr( + profile_b, + distance_bohr - position_bohr, + ) + return rho_a, rho_b, rho_a + rho_b + + +def _objective( + profile_a: _PreparedPairwiseProfile, + profile_b: _PreparedPairwiseProfile, + distance_bohr: float, + position_bohr: float, +) -> float: + """Return the native promolecular line-density sum.""" + + return ( + _evaluate_prepared_density_bohr(profile_a, position_bohr) + + _evaluate_prepared_density_bohr( + profile_b, + distance_bohr - position_bohr, + ) + ) + + +def _cutoff_regime( + distance_bohr: float, + cutoff_radius_a_bohr: float, + cutoff_radius_b_bohr: float, +) -> tuple[float, str]: + """Return signed contour separation and its exact-sign regime.""" + + radius_sum = cutoff_radius_a_bohr + cutoff_radius_b_bohr + separation = distance_bohr - radius_sum + if separation == 0.0: + return separation, "contact" + if separation > 0.0: + return separation, "gap" + return separation, "overlap" + + +def _equal_contribution_position( + profile_a: _PreparedPairwiseProfile, + profile_b: _PreparedPairwiseProfile, + distance_bohr: float, +) -> tuple[float | None, str | None]: + """Solve the continuous log-density equality by bracketed bisection.""" + + def difference(position: float) -> float: + return _continuous_log_density_bohr( + profile_a, + position, + ) - _continuous_log_density_bohr( + profile_b, + distance_bohr - position, + ) + + left = 0.0 + right = distance_bohr + left_value = difference(left) + right_value = difference(right) + + if left_value <= 0.0: + return None, "b" + if right_value >= 0.0: + return None, "a" + + while right - left > _EQUALITY_BRACKET_TOLERANCE_BOHR: + midpoint = (left + right) / 2.0 + if midpoint == left or midpoint == right: + break + value = difference(midpoint) + if value == 0.0: + return midpoint, None + if value > 0.0: + left = midpoint + else: + right = midpoint + if math.nextafter(left, right) == right: + break + + return (left + right) / 2.0, None + + +def _native_boundary_estimate( + profile_a: _PreparedPairwiseProfile, + profile_b: _PreparedPairwiseProfile, + distance_bohr: float, +) -> _NativeIASResult: + """Compute boundary mode in canonical orientation and native units.""" + + cutoff_a = profile_a.cutoff_radius_bohr + cutoff_b = profile_b.cutoff_radius_bohr + separation, regime = _cutoff_regime(distance_bohr, cutoff_a, cutoff_b) + + if profile_a.profile.atomic_number == profile_b.profile.atomic_number: + position = distance_bohr / 2.0 + rho_a, rho_b, rho_sum = _component_values( + profile_a, + profile_b, + distance_bohr, + position, + ) + return _NativeIASResult( + requested_mode="boundary", + method="homonuclear_midpoint", + status="low_density_gap" if regime == "gap" else "ok", + position_bohr=position, + rho_a=rho_a, + rho_b=rho_b, + rho_sum=rho_sum, + cutoff_radius_a_bohr=cutoff_a, + cutoff_radius_b_bohr=cutoff_b, + contour_separation_bohr=separation, + cutoff_regime=regime, + ) + + if regime == "gap": + position = (distance_bohr + cutoff_a - cutoff_b) / 2.0 + rho_a, rho_b, rho_sum = _component_values( + profile_a, + profile_b, + distance_bohr, + position, + ) + return _NativeIASResult( + requested_mode="boundary", + method="cutoff_gap_midpoint", + status="low_density_gap", + position_bohr=position, + rho_a=rho_a, + rho_b=rho_b, + rho_sum=rho_sum, + cutoff_radius_a_bohr=cutoff_a, + cutoff_radius_b_bohr=cutoff_b, + contour_separation_bohr=separation, + cutoff_regime=regime, + ) + + position, dominant_side = _equal_contribution_position( + profile_a, + profile_b, + distance_bohr, + ) + if position is None: + return _NativeIASResult( + requested_mode="boundary", + method="none", + status="one_atom_dominates", + position_bohr=None, + rho_a=None, + rho_b=None, + rho_sum=None, + cutoff_radius_a_bohr=cutoff_a, + cutoff_radius_b_bohr=cutoff_b, + contour_separation_bohr=separation, + cutoff_regime=regime, + dominant_side=dominant_side, + ) + + rho_a, rho_b, rho_sum = _component_values( + profile_a, + profile_b, + distance_bohr, + position, + ) + return _NativeIASResult( + requested_mode="boundary", + method="equal_proatom_density", + status="ok", + position_bohr=position, + rho_a=rho_a, + rho_b=rho_b, + rho_sum=rho_sum, + cutoff_radius_a_bohr=cutoff_a, + cutoff_radius_b_bohr=cutoff_b, + contour_separation_bohr=separation, + cutoff_regime=regime, + ) + + +def _bounded_minimum( + function: Callable[[float], float], + left: float, + center: float, + right: float, + center_value: float, +) -> _MinimumCandidate: + """Refine one sample-resolved valley with local golden-section search.""" + + ratio = (math.sqrt(5.0) - 1.0) / 2.0 + x1 = right - ratio * (right - left) + x2 = left + ratio * (right - left) + f1 = function(x1) + f2 = function(x2) + + for _ in range(36): + if math.nextafter(left, right) == right: + break + if f1 <= f2: + right, x2, f2 = x2, x1, f1 + x1 = right - ratio * (right - left) + f1 = function(x1) + else: + left, x1, f1 = x1, x2, f2 + x2 = left + ratio * (right - left) + f2 = function(x2) + + position, density = min( + ((center, center_value), (x1, f1), (x2, f2)), + key=lambda item: (item[1], item[0]), + ) + return _MinimumCandidate(position, density) + + +def _coalesce_minimum_candidates( + candidates: tuple[_MinimumCandidate, ...], +) -> tuple[_MinimumCandidate, ...]: + """Coalesce position-connected candidates at the public resolution.""" + + ordered = sorted(candidates, key=lambda candidate: candidate.position_bohr) + groups: list[list[_MinimumCandidate]] = [] + for candidate in ordered: + if ( + not groups + or candidate.position_bohr - groups[-1][-1].position_bohr + >= IAS_MINIMUM_RESOLUTION_BOHR + ): + groups.append([candidate]) + else: + groups[-1].append(candidate) + representatives = [ + min( + group, + key=lambda candidate: ( + candidate.density, + candidate.position_bohr, + ), + ) + for group in groups + ] + return tuple( + sorted( + representatives, + key=lambda candidate: (candidate.density, candidate.position_bohr), + ) + ) + + +def _minimum_grid_pass( + profile_a: _PreparedPairwiseProfile, + profile_b: _PreparedPairwiseProfile, + distance_bohr: float, + overlap_left: float, + overlap_right: float, + max_spacing_bohr: float, + equal_position_bohr: float | None, +) -> _MinimumPass: + """Run one deterministic practical-resolution valley search pass.""" + + width = overlap_right - overlap_left + segments = max(2, math.ceil(width / max_spacing_bohr)) + coordinates = [ + overlap_left + width * index / segments + for index in range(segments + 1) + ] + coordinates[0] = overlap_left + coordinates[-1] = overlap_right + if segments % 2: + midpoint = (overlap_left + overlap_right) / 2.0 + if midpoint not in coordinates: + coordinates.append(midpoint) + if ( + equal_position_bohr is not None + and overlap_left < equal_position_bohr < overlap_right + and equal_position_bohr not in coordinates + ): + coordinates.append(equal_position_bohr) + coordinates = sorted(set(coordinates)) + + def objective(position: float) -> float: + return _objective( + profile_a, + profile_b, + distance_bohr, + position, + ) + values = [objective(position) for position in coordinates] + candidates: list[_MinimumCandidate] = [] + for index in range(1, len(coordinates) - 1): + if ( + values[index] <= values[index - 1] + and values[index] <= values[index + 1] + ): + candidate = _bounded_minimum( + objective, + coordinates[index - 1], + coordinates[index], + coordinates[index + 1], + values[index], + ) + if overlap_left < candidate.position_bohr < overlap_right: + candidates.append(candidate) + + return _MinimumPass( + max_spacing_bohr=max_spacing_bohr, + candidates=tuple( + sorted( + candidates, + key=lambda candidate: ( + candidate.density, + candidate.position_bohr, + ), + ) + ), + ) + + +def _selections_compatible( + first: _MinimumCandidate | None, + second: _MinimumCandidate | None, +) -> bool: + """Return whether two pass selections agree at the public resolution.""" + + if first is None or second is None: + return first is second + position_agrees = ( + abs(first.position_bohr - second.position_bohr) + <= IAS_MINIMUM_RESOLUTION_BOHR + ) + density_agrees = abs(first.density - second.density) <= ( + _COMPETITIVE_RELATIVE_DEPTH * min(first.density, second.density) + ) + return position_agrees and density_agrees + + +def _combine_confirmed_minimum_candidates( + *passes: _MinimumPass, + candidate_filter: Callable[[_MinimumCandidate], bool] | None = None, +) -> tuple[_MinimumCandidate, ...]: + """Combine candidates from every executed pass before coalescing.""" + + return _coalesce_minimum_candidates( + tuple( + candidate + for minimum_pass in passes + for candidate in minimum_pass.candidates + if candidate_filter is None or candidate_filter(candidate) + ) + ) + + +def _minimum_candidate_is_publicly_strict( + candidate: _MinimumCandidate, + distance_bohr: float, + cutoff_a_bohr: float, + cutoff_b_bohr: float, + extra_presentations: tuple[tuple[float, float], ...] = (), +) -> bool: + """Return whether a minimum stays interior in every public presentation. + + A binary64 subtraction used for pair reversal, or the multiplication used + for angstrom output, can round a native strict-interior coordinate onto an + exposed cutoff endpoint. Reject such a candidate in native space for both + supported units and orientations so this representability decision cannot + depend on how the caller labels the pair or names the distance unit. + """ + + presentations = ( + (distance_bohr, 1.0), + (distance_bohr * BOHR_TO_ANGSTROM, BOHR_TO_ANGSTROM), + *extra_presentations, + ) + for distance, scale in presentations: + cutoff_a = cutoff_a_bohr * scale + cutoff_b = cutoff_b_bohr * scale + position_a = candidate.position_bohr * scale + position_b = distance - position_a + + overlap_left_a = max(0.0, distance - cutoff_b) + overlap_right_a = min(distance, cutoff_a) + overlap_left_b = max(0.0, distance - cutoff_a) + overlap_right_b = min(distance, cutoff_b) + if not ( + overlap_left_a < position_a < overlap_right_a + and overlap_left_b < position_b < overlap_right_b + ): + return False + return True + + +def _less_beyond_roundoff(first: float, second: float) -> bool: + """Return whether ``first`` is meaningfully below ``second`` in binary64.""" + + envelope = _FLOAT_COMPARISON_REL_TOL * max(abs(first), abs(second)) + return first < second - envelope + + +def _native_minimum_estimate( + profile_a: _PreparedPairwiseProfile, + profile_b: _PreparedPairwiseProfile, + distance_bohr: float, + *, + public_presentations: tuple[tuple[float, float], ...] = (), +) -> _NativeIASResult: + """Compute practical minimum mode in canonical orientation and native units.""" + + cutoff_a = profile_a.cutoff_radius_bohr + cutoff_b = profile_b.cutoff_radius_bohr + separation, regime = _cutoff_regime(distance_bohr, cutoff_a, cutoff_b) + + if profile_a.profile.atomic_number == profile_b.profile.atomic_number: + position = distance_bohr / 2.0 + rho_a, rho_b, rho_sum = _component_values( + profile_a, + profile_b, + distance_bohr, + position, + ) + return _NativeIASResult( + requested_mode="minimum", + method="homonuclear_midpoint", + status="low_density_gap" if regime == "gap" else "ok", + position_bohr=position, + rho_a=rho_a, + rho_b=rho_b, + rho_sum=rho_sum, + cutoff_radius_a_bohr=cutoff_a, + cutoff_radius_b_bohr=cutoff_b, + contour_separation_bohr=separation, + cutoff_regime=regime, + search_resolution_bohr=IAS_MINIMUM_RESOLUTION_BOHR, + search_converged=True, + search_passes=0, + ) + + if regime in ("contact", "gap"): + return _NativeIASResult( + requested_mode="minimum", + method="none", + status="low_density_gap", + position_bohr=None, + rho_a=None, + rho_b=None, + rho_sum=None, + cutoff_radius_a_bohr=cutoff_a, + cutoff_radius_b_bohr=cutoff_b, + contour_separation_bohr=separation, + cutoff_regime=regime, + search_resolution_bohr=IAS_MINIMUM_RESOLUTION_BOHR, + search_converged=None, + search_passes=0, + ) + + overlap_left = max(0.0, distance_bohr - cutoff_b) + overlap_right = min(distance_bohr, cutoff_a) + + equal_position, _ = _equal_contribution_position( + profile_a, + profile_b, + distance_bohr, + ) + coarse = _minimum_grid_pass( + profile_a, + profile_b, + distance_bohr, + overlap_left, + overlap_right, + _MINIMUM_INITIAL_SPACING_BOHR, + equal_position, + ) + fine = _minimum_grid_pass( + profile_a, + profile_b, + distance_bohr, + overlap_left, + overlap_right, + _MINIMUM_CONFIRM_SPACING_BOHR, + equal_position, + ) + + executed_passes = [coarse, fine] + final_spacing_bohr = fine.max_spacing_bohr + search_passes = 2 + search_converged = True + if not _selections_compatible(coarse.selected, fine.selected): + fallback = _minimum_grid_pass( + profile_a, + profile_b, + distance_bohr, + overlap_left, + overlap_right, + _MINIMUM_FALLBACK_SPACING_BOHR, + equal_position, + ) + executed_passes.append(fallback) + final_spacing_bohr = fallback.max_spacing_bohr + search_passes = 3 + search_converged = _selections_compatible( + fine.selected, + fallback.selected, + ) + + final_pass = _MinimumPass( + max_spacing_bohr=final_spacing_bohr, + candidates=_combine_confirmed_minimum_candidates( + *executed_passes, + candidate_filter=lambda candidate: ( + overlap_left < candidate.position_bohr < overlap_right + and _minimum_candidate_is_publicly_strict( + candidate, + distance_bohr, + cutoff_a, + cutoff_b, + public_presentations, + ) + ), + ), + ) + + selected = final_pass.selected + if selected is None: + return _NativeIASResult( + requested_mode="minimum", + method="none", + status="no_resolved_interior_minimum", + position_bohr=None, + rho_a=None, + rho_b=None, + rho_sum=None, + cutoff_radius_a_bohr=cutoff_a, + cutoff_radius_b_bohr=cutoff_b, + contour_separation_bohr=separation, + cutoff_regime=regime, + search_resolution_bohr=final_pass.max_spacing_bohr, + search_converged=search_converged, + search_passes=search_passes, + ) + + alternative = ( + final_pass.candidates[1] if len(final_pass.candidates) > 1 else None + ) + relative_depth_gap = None + ambiguous = False + if alternative is not None: + relative_depth_gap = max( + 0.0, + (alternative.density - selected.density) / selected.density, + ) + ambiguous = relative_depth_gap <= _COMPETITIVE_RELATIVE_DEPTH + + status = "ambiguous_competing_minima" if ambiguous else "ok" + if not search_converged: + status = "search_unstable" + + boundary_density = min( + _objective(profile_a, profile_b, distance_bohr, 0.0), + _objective(profile_a, profile_b, distance_bohr, distance_bohr), + ) + if _less_beyond_roundoff(boundary_density, selected.density): + status = "boundary_dominated" + + rho_a, rho_b, rho_sum = _component_values( + profile_a, + profile_b, + distance_bohr, + selected.position_bohr, + ) + return _NativeIASResult( + requested_mode="minimum", + method="promolecular_density_minimum", + status=status, + position_bohr=selected.position_bohr, + rho_a=rho_a, + rho_b=rho_b, + rho_sum=rho_sum, + cutoff_radius_a_bohr=cutoff_a, + cutoff_radius_b_bohr=cutoff_b, + contour_separation_bohr=separation, + cutoff_regime=regime, + alternative_position_bohr=( + None if alternative is None else alternative.position_bohr + ), + alternative_rho_sum=( + None if alternative is None else alternative.density + ), + relative_depth_gap=relative_depth_gap, + ambiguous=ambiguous, + search_resolution_bohr=final_pass.max_spacing_bohr, + search_converged=search_converged, + search_passes=search_passes, + ) + + +def _validate_pair_distance( + distance: float, + *, + distance_unit: str, +) -> tuple[float, float]: + """Validate a public pair distance and return requested/native values.""" + + if distance_unit not in ("angstrom", "bohr"): + raise ValueError(f"unknown distance unit: {distance_unit!r}") + if isinstance(distance, bool): + raise ValueError("distance must be a finite positive scalar") + try: + requested_distance = float(distance) + except (TypeError, ValueError) as exc: + raise ValueError("distance must be a finite positive scalar") from exc + if not math.isfinite(requested_distance): + raise ValueError("distance must be finite") + if requested_distance <= 0.0: + raise ValueError("distance must be strictly positive") + + public_max = ( + 20.0 if distance_unit == "bohr" else 20.0 * BOHR_TO_ANGSTROM + ) + if requested_distance > public_max: + raise ValueError("distance exceeds the public limit of 20 bohr") + if distance_unit == "bohr": + return requested_distance, requested_distance + return requested_distance, min(requested_distance / BOHR_TO_ANGSTROM, 20.0) + + +def _density_output_factor(density_unit: str) -> float: + """Validate an output density unit and return its native scale factor.""" + + return _density_from_native(1.0, density_unit=density_unit) + + +def _assemble_pairwise_result( + native: _NativeIASResult, + *, + requested_profile_a: ProatomicDensityProfile, + requested_profile_b: ProatomicDensityProfile, + canonical_profile_a: _PreparedPairwiseProfile, + canonical_profile_b: _PreparedPairwiseProfile, + canonical_was_reversed: bool, + requested_distance: float, + distance_bohr: float, + distance_unit: str, + density_unit: str, +) -> IASPositionResult: + """Apply requested orientation and units to one complete native result.""" + + distance_factor = 1.0 if distance_unit == "bohr" else BOHR_TO_ANGSTROM + density_factor = _density_output_factor(density_unit) + + def oriented_coordinate_pair( + canonical_position: float | None, + ) -> tuple[float | None, float | None, float | None]: + if canonical_position is None: + return None, None, None + canonical_from_a = ( + requested_distance / 2.0 + if native.method == "homonuclear_midpoint" + else canonical_position * distance_factor + ) + canonical_from_b = requested_distance - canonical_from_a + canonical_fraction = canonical_from_a / requested_distance + if canonical_was_reversed: + return ( + canonical_from_b, + canonical_from_a, + canonical_from_b / requested_distance, + ) + return canonical_from_a, canonical_from_b, canonical_fraction + + position_a, position_b, fraction_a = oriented_coordinate_pair( + native.position_bohr + ) + alternative_a, alternative_b, _ = oriented_coordinate_pair( + native.alternative_position_bohr + ) + + if canonical_was_reversed: + rho_a_native, rho_b_native = native.rho_b, native.rho_a + cutoff_a_native = native.cutoff_radius_b_bohr + cutoff_b_native = native.cutoff_radius_a_bohr + else: + rho_a_native, rho_b_native = native.rho_a, native.rho_b + cutoff_a_native = native.cutoff_radius_a_bohr + cutoff_b_native = native.cutoff_radius_b_bohr + + dominant_atom = None + dominant_atom_role = None + if native.dominant_side is not None: + canonical_dominant = ( + canonical_profile_a + if native.dominant_side == "a" + else canonical_profile_b + ) + dominant_atom = canonical_dominant.profile.symbol + dominant_is_requested_a = ( + native.dominant_side == "a" and not canonical_was_reversed + ) or (native.dominant_side == "b" and canonical_was_reversed) + dominant_atom_role = "atom_a" if dominant_is_requested_a else "atom_b" + + return IASPositionResult( + atom_a=requested_profile_a.symbol, + atom_b=requested_profile_b.symbol, + distance=requested_distance, + distance_unit=distance_unit, + density_unit=density_unit, + requested_mode=native.requested_mode, # type: ignore[arg-type] + method=native.method, # type: ignore[arg-type] + status=native.status, # type: ignore[arg-type] + position_from_a=position_a, + position_from_b=position_b, + fraction_from_a=fraction_a, + rho_a=None if rho_a_native is None else rho_a_native * density_factor, + rho_b=None if rho_b_native is None else rho_b_native * density_factor, + rho_sum=( + None if native.rho_sum is None else native.rho_sum * density_factor + ), + cutoff_density=PROATOMIC_TAIL_CUTOFF * density_factor, + cutoff_radius_a=cutoff_a_native * distance_factor, + cutoff_radius_b=cutoff_b_native * distance_factor, + contour_separation=native.contour_separation_bohr * distance_factor, + cutoff_regime=native.cutoff_regime, # type: ignore[arg-type] + dominant_atom=dominant_atom, + dominant_atom_role=dominant_atom_role, # type: ignore[arg-type] + alternative_position_from_a=alternative_a, + alternative_position_from_b=alternative_b, + alternative_rho_sum=( + None + if native.alternative_rho_sum is None + else native.alternative_rho_sum * density_factor + ), + relative_depth_gap=native.relative_depth_gap, + ambiguous=native.ambiguous, + search_resolution=( + None + if native.search_resolution_bohr is None + else native.search_resolution_bohr * distance_factor + ), + search_converged=native.search_converged, + search_passes=native.search_passes, + dataset_id=requested_profile_a.ref.set_id, + interpolation_contract=requested_profile_a.interpolation_contract, + pairwise_contract=_PAIRWISE_CONTRACT, + ) + + +def _estimate_pairwise( + atom_a: str | int | None, + atom_b: str | int | None, + distance: float, + *, + mode: Literal["boundary", "minimum"], + distance_unit: str, + density_unit: str, + set_id: str, +) -> IASPositionResult | None: + """Validate, canonicalize, compute, and assemble one pairwise estimate.""" + + requested_distance, distance_bohr = _validate_pair_distance( + distance, + distance_unit=distance_unit, + ) + _density_output_factor(density_unit) + + resolved_a = _resolve_density_element(atom_a) + resolved_b = _resolve_density_element(atom_b) + if resolved_a is None or resolved_b is None: + return None + + dataset = get_proatomic_density_set(set_id) + if dataset.get(resolved_a.z) is None or dataset.get(resolved_b.z) is None: + return None + requested_profile_a = _get_profile_cached(dataset.ref, resolved_a.z) + requested_profile_b = _get_profile_cached(dataset.ref, resolved_b.z) + + prepared_a = _prepared_pairwise_profile(requested_profile_a) + prepared_b = _prepared_pairwise_profile(requested_profile_b) + canonical_was_reversed = ( + prepared_a.profile.atomic_number > prepared_b.profile.atomic_number + ) + if canonical_was_reversed: + canonical_a, canonical_b = prepared_b, prepared_a + else: + canonical_a, canonical_b = prepared_a, prepared_b + + contact_distance_bohr = ( + canonical_a.cutoff_radius_bohr + canonical_b.cutoff_radius_bohr + ) + if distance_unit == "bohr": + contact_distances_requested = (contact_distance_bohr,) + else: + contact_distances_requested = ( + contact_distance_bohr * BOHR_TO_ANGSTROM, + canonical_a.cutoff_radius_bohr * BOHR_TO_ANGSTROM + + canonical_b.cutoff_radius_bohr * BOHR_TO_ANGSTROM, + ) + if requested_distance in contact_distances_requested: + # Preserve an exact physical cutoff contact across the named input + # units without broadening the binding native ``d_c > 0`` branch. + distance_bohr = contact_distance_bohr + + if mode == "boundary": + native = _native_boundary_estimate( + canonical_a, + canonical_b, + distance_bohr, + ) + else: + distance_factor = ( + 1.0 if distance_unit == "bohr" else BOHR_TO_ANGSTROM + ) + native = _native_minimum_estimate( + canonical_a, + canonical_b, + distance_bohr, + public_presentations=((requested_distance, distance_factor),), + ) + + return _assemble_pairwise_result( + native, + requested_profile_a=requested_profile_a, + requested_profile_b=requested_profile_b, + canonical_profile_a=canonical_a, + canonical_profile_b=canonical_b, + canonical_was_reversed=canonical_was_reversed, + requested_distance=requested_distance, + distance_bohr=distance_bohr, + distance_unit=distance_unit, + density_unit=density_unit, + ) + + +def estimate_proatomic_boundary( + atom_a: str | int | None, + atom_b: str | int | None, + distance: float, + *, + distance_unit: Literal["angstrom", "bohr"] = "angstrom", + density_unit: Literal[ + "electron/bohr^3", "electron/angstrom^3" + ] = "electron/bohr^3", + set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, +) -> IASPositionResult | None: + """Estimate a stable pairwise neutral-proatom boundary. + + The coordinate is measured from ``atom_a`` toward ``atom_b``. Overlapping + unlike profiles use equal neutral-proatom density; separated fixed-cutoff + contours use the midpoint of their gap. This is not a molecular QTAIM + zero-flux surface. + """ + + return _estimate_pairwise( + atom_a, + atom_b, + distance, + mode="boundary", + distance_unit=distance_unit, + density_unit=density_unit, + set_id=set_id, + ) + + +def estimate_promolecular_density_minimum( + atom_a: str | int | None, + atom_b: str | int | None, + distance: float, + *, + distance_unit: Literal["angstrom", "bohr"] = "angstrom", + density_unit: Literal[ + "electron/bohr^3", "electron/angstrom^3" + ] = "electron/bohr^3", + set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, +) -> IASPositionResult | None: + """Estimate one practically resolved promolecular line-density minimum. + + The search is confined to the interval where both neutral proatomic + components meet the fixed tail cutoff. It has a declared resolution of + :data:`IAS_MINIMUM_RESOLUTION_BOHR`, exposes only strict-interior unlike-atom + candidates, and never falls back to boundary mode. + """ + + return _estimate_pairwise( + atom_a, + atom_b, + distance, + mode="minimum", + distance_unit=distance_unit, + density_unit=density_unit, + set_id=set_id, + ) + + +def estimate_ias_position( + atom_a: str | int | None, + atom_b: str | int | None, + distance: float, + *, + mode: Literal["boundary", "minimum"] = "boundary", + distance_unit: Literal["angstrom", "bohr"] = "angstrom", + density_unit: Literal[ + "electron/bohr^3", "electron/angstrom^3" + ] = "electron/bohr^3", + set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, +) -> IASPositionResult | None: + """Dispatch explicitly to one of the two pairwise neutral-proatom modes.""" + + if mode not in ("boundary", "minimum"): + raise ValueError(f"unknown IAS position mode: {mode!r}") + return _estimate_pairwise( + atom_a, + atom_b, + distance, + mode=mode, + distance_unit=distance_unit, + density_unit=density_unit, + set_id=set_id, + ) diff --git a/tests/meta/test_public_api.py b/tests/meta/test_public_api.py index de199aa..73a8f34 100644 --- a/tests/meta/test_public_api.py +++ b/tests/meta/test_public_api.py @@ -17,6 +17,9 @@ 'DEFAULT_VDW_POLICY', 'BOHR_TO_ANGSTROM', 'DEFAULT_PROATOMIC_DENSITY_SET', + 'PROATOMIC_TAIL_CUTOFF', + 'IAS_MINIMUM_RESOLUTION_BOHR', + 'IASPositionResult', 'ProatomicDensityProfile', 'ProatomicDensitySet', 'LinearTransfer', @@ -43,6 +46,9 @@ 'get_proatomic_density_profile', 'get_proatomic_density_set', 'get_proatomic_density_set_info', + 'estimate_proatomic_boundary', + 'estimate_promolecular_density_minimum', + 'estimate_ias_position', 'list_proatomic_density_sets', 'list_proatomic_density_set_infos', } diff --git a/tests/proatoms/test_ias.py b/tests/proatoms/test_ias.py new file mode 100644 index 0000000..d1dacb9 --- /dev/null +++ b/tests/proatoms/test_ias.py @@ -0,0 +1,1432 @@ +from __future__ import annotations + +from dataclasses import FrozenInstanceError +import inspect +import math +from types import MappingProxyType + +import pytest + +import atomref as ar +from atomref.errors import DatasetError +import atomref.proatoms as proatoms + + +_DATASET_ID = "pbe0_sfx2c_dyallv4z_h-lr_neutral_v2" +_INTERPOLATION_CONTRACT = "loglog_positive_bracketed_v1" +_PAIRWISE_CONTRACT = "neutral_proatom_pairwise_cutoff_1e-4_resolution_0.01_v1" + +_AMBIGUOUS_CASE = ("Mn", "Gd", 9.699418444855935) +_BOUNDARY_DOMINATED_CASE = ("Zn", "He", 0.742706534980151) +_NO_RESOLVED_CASE = ("C", "Lu", 0.2217301367970158) +_UNSTABLE_CASE = ("Be", "Cm", 5.209173731112204) + + +def _result(value: ar.IASPositionResult | None) -> ar.IASPositionResult: + assert value is not None + return value + + +def _profile(symbol: str) -> ar.ProatomicDensityProfile: + profile = ar.get_proatomic_density_profile(symbol) + assert profile is not None + return profile + + +def _independent_cutoff_radius(profile: ar.ProatomicDensityProfile) -> float: + cutoff = ar.PROATOMIC_TAIL_CUTOFF + right = next( + index + for index, density in enumerate(profile.densities) + if density <= cutoff + ) + if profile.densities[right] == cutoff: + return profile.radii[right] + left = right - 1 + log_radius_left = math.log(profile.radii[left]) + log_radius_right = math.log(profile.radii[right]) + log_density_left = math.log(profile.densities[left]) + log_density_right = math.log(profile.densities[right]) + fraction = (math.log(cutoff) - log_density_left) / ( + log_density_right - log_density_left + ) + return math.exp( + log_radius_left + fraction * (log_radius_right - log_radius_left) + ) + + +def _pair_density_sum( + profile_a: ar.ProatomicDensityProfile, + profile_b: ar.ProatomicDensityProfile, + distance_bohr: float, + position_bohr: float, +) -> float: + return profile_a(position_bohr, radius_unit="bohr") + profile_b( + distance_bohr - position_bohr, + radius_unit="bohr", + ) + + +def _synthetic_profile( + densities: tuple[float, ...], + *, + radii: tuple[float, ...] = (0.1, 1.0, 4.0, 10.0, 20.0, 21.0), + set_id: str, +) -> ar.ProatomicDensityProfile: + ref = ar.DatasetRef("proatomic_density", set_id) + info = ar.DatasetInfo( + ref=ref, + domain="element", + units="electron/bohr^3", + name="Synthetic pairwise profile", + storage=MappingProxyType( + { + "native_coordinate_unit": "bohr", + "native_density_unit": "electron/bohr^3", + "public_max_radius_bohr": 20.0, + "interpolation_contract": _INTERPOLATION_CONTRACT, + } + ), + ) + dataset = ar.ElementRadialSet( + ref=ref, + info=info, + radii=radii, + profiles_by_z=(None, densities), + ) + return ar.ProatomicDensityProfile(dataset=dataset, atomic_number=1) + + +def _assert_reversal( + forward: ar.IASPositionResult, + reverse: ar.IASPositionResult, + distance_bohr: float, +) -> None: + assert reverse.method == forward.method + assert reverse.status == forward.status + assert reverse.cutoff_regime == forward.cutoff_regime + assert reverse.search_converged == forward.search_converged + assert reverse.search_passes == forward.search_passes + assert reverse.search_resolution == forward.search_resolution + assert reverse.relative_depth_gap == forward.relative_depth_gap + assert reverse.ambiguous == forward.ambiguous + assert reverse.cutoff_radius_a == forward.cutoff_radius_b + assert reverse.cutoff_radius_b == forward.cutoff_radius_a + assert reverse.contour_separation == forward.contour_separation + + if forward.position_from_a is None: + assert reverse.position_from_a is None + assert reverse.position_from_b is None + assert reverse.fraction_from_a is None + else: + assert reverse.position_from_a == pytest.approx( + distance_bohr - forward.position_from_a, + abs=2.0e-12, + ) + assert reverse.position_from_b == pytest.approx( + forward.position_from_a, + abs=2.0e-12, + ) + assert reverse.fraction_from_a == pytest.approx( + 1.0 - forward.fraction_from_a, + abs=2.0e-15, + ) + assert reverse.rho_a == pytest.approx(forward.rho_b, rel=2.0e-14) + assert reverse.rho_b == pytest.approx(forward.rho_a, rel=2.0e-14) + assert reverse.rho_sum == pytest.approx(forward.rho_sum, rel=2.0e-14) + + if forward.alternative_position_from_a is None: + assert reverse.alternative_position_from_a is None + assert reverse.alternative_position_from_b is None + assert reverse.alternative_rho_sum is None + else: + assert reverse.alternative_position_from_a == pytest.approx( + distance_bohr - forward.alternative_position_from_a, + abs=2.0e-12, + ) + assert reverse.alternative_position_from_b == pytest.approx( + forward.alternative_position_from_a, + abs=2.0e-12, + ) + assert reverse.alternative_rho_sum == pytest.approx( + forward.alternative_rho_sum, + rel=2.0e-14, + ) + + +def _assert_minimum_coordinates_strictly_inside_overlap( + result: ar.IASPositionResult, +) -> None: + """Check that exposed unlike-atom minima are not overlap endpoints.""" + + if result.requested_mode != "minimum" or result.atom_a == result.atom_b: + return + overlap_left = max(0.0, result.distance - result.cutoff_radius_b) + overlap_right = min(result.distance, result.cutoff_radius_a) + for position in ( + result.position_from_a, + result.alternative_position_from_a, + ): + if position is not None: + assert overlap_left < position < overlap_right + assert 0.0 < position < result.distance + + +def test_public_functions_and_dispatcher_default_are_consistent() -> None: + signature = inspect.signature(ar.estimate_ias_position) + assert signature.parameters["mode"].default == "boundary" + + direct_boundary = ar.estimate_proatomic_boundary( + "C", "O", 3.0, distance_unit="bohr" + ) + direct_minimum = ar.estimate_promolecular_density_minimum( + "C", "O", 3.0, distance_unit="bohr" + ) + assert ar.estimate_ias_position( + "C", "O", 3.0, distance_unit="bohr" + ) == direct_boundary + assert ar.estimate_ias_position( + "C", "O", 3.0, mode="boundary", distance_unit="bohr" + ) == direct_boundary + assert ar.estimate_ias_position( + "C", "O", 3.0, mode="minimum", distance_unit="bohr" + ) == direct_minimum + assert _result(direct_boundary).requested_mode == "boundary" + assert _result(direct_minimum).requested_mode == "minimum" + + +def test_result_is_frozen_slotted_and_carries_complete_provenance() -> None: + result = _result( + ar.estimate_proatomic_boundary("C", "O", 3.0, distance_unit="bohr") + ) + assert isinstance(result, ar.IASPositionResult) + assert not hasattr(result, "__dict__") + with pytest.raises(FrozenInstanceError): + result.status = "low_density_gap" + + assert result.atom_a == "C" + assert result.atom_b == "O" + assert result.distance == 3.0 + assert result.distance_unit == "bohr" + assert result.density_unit == "electron/bohr^3" + assert result.coordinate_orientation == "from_atom_a_toward_atom_b" + assert result.dataset_id == _DATASET_ID + assert result.interpolation_contract == _INTERPOLATION_CONTRACT + assert result.pairwise_contract == _PAIRWISE_CONTRACT + assert result.cutoff_density == ar.PROATOMIC_TAIL_CUTOFF + assert result.position_from_a is not None + assert result.position_from_b is not None + assert result.fraction_from_a is not None + assert result.rho_a is not None + assert result.rho_b is not None + assert result.rho_sum is not None + assert result.position_from_a + result.position_from_b == pytest.approx(3.0) + assert result.fraction_from_a == pytest.approx(result.position_from_a / 3.0) + assert result.rho_sum == pytest.approx(result.rho_a + result.rho_b) + assert result.rho_a == pytest.approx( + _profile("C")(result.position_from_a, radius_unit="bohr") + ) + assert result.rho_b == pytest.approx( + _profile("O")(result.position_from_b, radius_unit="bohr") + ) + + +def test_every_packaged_profile_has_one_analytical_cutoff_radius() -> None: + seen = 0 + for element in ar.iter_elements(): + if element.z > 103: + break + profile = _profile(element.symbol) + assert all( + right < left + for left, right in zip(profile.densities, profile.densities[1:]) + ), element.symbol + assert profile.densities[0] > ar.PROATOMIC_TAIL_CUTOFF + crossing_indices = [ + index + for index in range(1, len(profile.densities)) + if ( + profile.densities[index - 1] > ar.PROATOMIC_TAIL_CUTOFF + and profile.densities[index] <= ar.PROATOMIC_TAIL_CUTOFF + ) + ] + assert len(crossing_indices) == 1, element.symbol + + prepared = proatoms._prepared_pairwise_profile(profile) + expected = _independent_cutoff_radius(profile) + assert math.isfinite(prepared.cutoff_radius_bohr), element.symbol + assert 0.0 < prepared.cutoff_radius_bohr < 20.0, element.symbol + assert prepared.cutoff_radius_bohr == pytest.approx( + expected, + rel=2.0e-14, + ), element.symbol + reproduced = profile( + prepared.cutoff_radius_bohr, + radius_unit="bohr", + ) + assert reproduced == pytest.approx( + ar.PROATOMIC_TAIL_CUTOFF, + rel=5.0e-14, + ), element.symbol + seen += 1 + assert seen == 103 + + +def test_pairwise_profile_preparation_is_lazy_cached_and_shared( + monkeypatch: pytest.MonkeyPatch, +) -> None: + profile = _profile("O") + proatoms._get_prepared_pairwise_profile_cached.cache_clear() + real_prepare = proatoms._prepare_pairwise_profile + calls = 0 + + def counting_prepare( + candidate: ar.ProatomicDensityProfile, + ) -> proatoms._PreparedPairwiseProfile: + nonlocal calls + calls += 1 + return real_prepare(candidate) + + monkeypatch.setattr(proatoms, "_prepare_pairwise_profile", counting_prepare) + profile(1.0, radius_unit="bohr") + ar.get_proatomic_density_set_info() + assert calls == 0 + + first = proatoms._prepared_pairwise_profile(profile) + second = proatoms._prepared_pairwise_profile(profile) + assert first is second + assert calls == 1 + cache_info = proatoms._get_prepared_pairwise_profile_cached.cache_info() + assert cache_info.misses == 1 + assert cache_info.hits == 1 + + +def test_cutoff_preparation_accepts_an_exact_cutoff_knot() -> None: + profile = _synthetic_profile( + (1.0, 0.1, 0.01, 0.0001, 0.00001, 0.000001), + radii=(0.1, 1.0, 4.0, 10.0, 15.0, 20.0), + set_id="synthetic_exact_cutoff", + ) + prepared = proatoms._prepare_pairwise_profile(profile) + assert prepared.cutoff_radius_bohr == profile.radii[3] + + +@pytest.mark.parametrize( + ("densities", "set_id", "message"), + [ + ( + (1.0, 0.1, 0.1, 0.001, 0.0001, 0.00001), + "synthetic_not_strict", + "strictly decreasing", + ), + ( + (0.0001, 0.00009, 0.00008, 0.00007, 0.00006, 0.00005), + "synthetic_first_below", + "first proatomic density", + ), + ( + (1.0, 0.1, 0.01, 0.001, 0.0002, 0.00011), + "synthetic_never_below", + "does not fall below", + ), + ( + (1.0, 0.1, 0.01, 0.001, 0.0002, 0.00001), + "synthetic_after_public_limit", + "before the public limit", + ), + ], +) +def test_cutoff_preparation_rejects_invalid_profile_contracts( + densities: tuple[float, ...], + set_id: str, + message: str, +) -> None: + profile = _synthetic_profile(densities, set_id=set_id) + with pytest.raises(DatasetError, match=message): + proatoms._prepare_pairwise_profile(profile) + + +@pytest.mark.parametrize("symbol", ["H", "Li"]) +@pytest.mark.parametrize("mode", ["boundary", "minimum"]) +def test_homonuclear_pairs_return_exact_midpoint( + symbol: str, + mode: str, +) -> None: + distance = 5.0 + result = _result( + ar.estimate_ias_position( + symbol, + symbol, + distance, + mode=mode, + distance_unit="bohr", + ) + ) + assert result.method == "homonuclear_midpoint" + assert result.position_from_a == distance / 2.0 + assert result.position_from_b == distance / 2.0 + assert result.fraction_from_a == 0.5 + assert result.rho_a == result.rho_b + + +@pytest.mark.parametrize("mode", ["boundary", "minimum"]) +def test_homonuclear_cutoff_gap_keeps_midpoint(mode: str) -> None: + distance = 10.0 + result = _result( + ar.estimate_ias_position( + "H", + "H", + distance, + mode=mode, + distance_unit="bohr", + ) + ) + assert result.status == "low_density_gap" + assert result.cutoff_regime == "gap" + assert result.method == "homonuclear_midpoint" + assert result.position_from_a == 5.0 + + +@pytest.mark.parametrize( + ("atom_a", "atom_b", "distance"), + [ + ("H", "O", 2.0), + ("H", "Lr", 2.0), + ("Fe", "La", 5.0), + ("La", "U", 6.0), + ], +) +def test_overlapping_unlike_boundary_balances_components( + atom_a: str, + atom_b: str, + distance: float, +) -> None: + result = _result( + ar.estimate_proatomic_boundary( + atom_a, + atom_b, + distance, + distance_unit="bohr", + ) + ) + assert result.method == "equal_proatom_density" + assert result.status == "ok" + assert result.cutoff_regime == "overlap" + assert result.position_from_a is not None + assert 0.0 < result.position_from_a < distance + assert result.rho_a == pytest.approx(result.rho_b, rel=2.0e-9) + + +def test_boundary_cutoff_gap_uses_geometric_contour_midpoint() -> None: + distance = 10.0 + result = _result( + ar.estimate_proatomic_boundary("H", "O", distance, distance_unit="bohr") + ) + expected = ( + distance + result.cutoff_radius_a - result.cutoff_radius_b + ) / 2.0 + assert result.method == "cutoff_gap_midpoint" + assert result.status == "low_density_gap" + assert result.cutoff_regime == "gap" + assert result.contour_separation > 0.0 + assert result.position_from_a == expected + + +def test_boundary_branches_are_continuous_at_cutoff_contact() -> None: + cutoff_a = proatoms._prepared_pairwise_profile( + _profile("H") + ).cutoff_radius_bohr + cutoff_b = proatoms._prepared_pairwise_profile( + _profile("O") + ).cutoff_radius_bohr + contact_distance = cutoff_a + cutoff_b + overlap = _result( + ar.estimate_proatomic_boundary( + "H", + "O", + contact_distance - 1.0e-7, + distance_unit="bohr", + ) + ) + contact = _result( + ar.estimate_proatomic_boundary( + "H", "O", contact_distance, distance_unit="bohr" + ) + ) + gap = _result( + ar.estimate_proatomic_boundary( + "H", + "O", + contact_distance + 1.0e-7, + distance_unit="bohr", + ) + ) + assert overlap.method == "equal_proatom_density" + assert overlap.cutoff_regime == "overlap" + assert contact.method == "equal_proatom_density" + assert contact.cutoff_regime == "contact" + assert gap.method == "cutoff_gap_midpoint" + assert gap.cutoff_regime == "gap" + assert contact.position_from_a == pytest.approx(cutoff_a, abs=1.0e-9) + assert overlap.position_from_a == pytest.approx(cutoff_a, abs=2.0e-7) + assert gap.position_from_a == pytest.approx(cutoff_a, abs=2.0e-7) + + +@pytest.mark.parametrize("mode", ["boundary", "minimum"]) +def test_exact_cutoff_contact_is_distance_unit_invariant(mode: str) -> None: + cutoff_a = proatoms._prepared_pairwise_profile( + _profile("F") + ).cutoff_radius_bohr + cutoff_b = proatoms._prepared_pairwise_profile( + _profile("Ne") + ).cutoff_radius_bohr + distance_bohr = cutoff_a + cutoff_b + distance_angstrom = distance_bohr * ar.BOHR_TO_ANGSTROM + + native = _result( + ar.estimate_ias_position( + "F", "Ne", distance_bohr, mode=mode, distance_unit="bohr" + ) + ) + converted = _result( + ar.estimate_ias_position( + "F", "Ne", distance_angstrom, mode=mode, distance_unit="angstrom" + ) + ) + metadata_distance_angstrom = ( + native.cutoff_radius_a * ar.BOHR_TO_ANGSTROM + + native.cutoff_radius_b * ar.BOHR_TO_ANGSTROM + ) + reconstructed = _result( + ar.estimate_ias_position( + "F", + "Ne", + metadata_distance_angstrom, + mode=mode, + distance_unit="angstrom", + ) + ) + + assert ( + native.cutoff_regime + == converted.cutoff_regime + == reconstructed.cutoff_regime + == "contact" + ) + assert native.method == converted.method == reconstructed.method + assert native.status == converted.status == reconstructed.status + assert converted.contour_separation == 0.0 + if native.position_from_a is None: + assert converted.position_from_a is None + else: + assert converted.position_from_a == pytest.approx( + native.position_from_a * ar.BOHR_TO_ANGSTROM, + rel=2.0e-15, + ) + + +@pytest.mark.parametrize(("atom_a", "atom_b"), [("H", "O"), ("O", "H")]) +@pytest.mark.parametrize("distance_unit", ["bohr", "angstrom"]) +def test_minimum_rejects_near_contact_cutoff_endpoint( + atom_a: str, + atom_b: str, + distance_unit: str, +) -> None: + cutoff_a = proatoms._prepared_pairwise_profile( + _profile("H") + ).cutoff_radius_bohr + cutoff_b = proatoms._prepared_pairwise_profile( + _profile("O") + ).cutoff_radius_bohr + contact = cutoff_a + cutoff_b + distance_bohr = math.nextafter(math.nextafter(contact, 0.0), 0.0) + distance = ( + distance_bohr + if distance_unit == "bohr" + else distance_bohr * ar.BOHR_TO_ANGSTROM + ) + + result = _result( + ar.estimate_promolecular_density_minimum( + atom_a, + atom_b, + distance, + distance_unit=distance_unit, + ) + ) + + assert result.cutoff_regime == "overlap" + assert result.method == "none" + assert result.status == "no_resolved_interior_minimum" + assert result.position_from_a is None + assert result.position_from_b is None + assert result.alternative_position_from_a is None + assert result.alternative_position_from_b is None + + +def test_minimum_contact_neighbors_and_representable_interiors() -> None: + cutoff_a = proatoms._prepared_pairwise_profile( + _profile("H") + ).cutoff_radius_bohr + cutoff_b = proatoms._prepared_pairwise_profile( + _profile("He") + ).cutoff_radius_bohr + contact = cutoff_a + cutoff_b + distances = [contact] + distance = contact + for _ in range(5): + distance = math.nextafter(distance, 0.0) + distances.append(distance) + + interior_counts: list[int] = [] + for distance in (distances[0], *distances[2:]): + overlap_left = max(0.0, distance - cutoff_b) + overlap_right = min(distance, cutoff_a) + position = math.nextafter(overlap_left, overlap_right) + count = 0 + while position < overlap_right: + count += 1 + position = math.nextafter(position, overlap_right) + interior_counts.append(count) + + result = _result( + ar.estimate_promolecular_density_minimum( + "H", "He", distance, distance_unit="bohr" + ) + ) + _assert_minimum_coordinates_strictly_inside_overlap(result) + + assert interior_counts == [0, 1, 2, 3, 4] + formerly_clamped = _result( + ar.estimate_promolecular_density_minimum( + "H", "He", distances[4], distance_unit="bohr" + ) + ) + assert formerly_clamped.method == "none" + assert formerly_clamped.status == "no_resolved_interior_minimum" + + exact = _result( + ar.estimate_promolecular_density_minimum( + "H", "He", contact, distance_unit="bohr" + ) + ) + above = _result( + ar.estimate_promolecular_density_minimum( + "H", + "He", + math.nextafter(contact, math.inf), + distance_unit="bohr", + ) + ) + assert exact.method == above.method == "none" + assert exact.status == above.status == "low_density_gap" + assert exact.cutoff_regime == "contact" + assert above.cutoff_regime == "gap" + + +@pytest.mark.parametrize( + ("atom_a", "atom_b", "steps_below_contact"), + [("H", "Se", 2), ("Kr", "He", 4)], +) +@pytest.mark.parametrize("distance_unit", ["bohr", "angstrom"]) +def test_near_contact_minimum_survives_no_public_endpoint_rounding( + atom_a: str, + atom_b: str, + steps_below_contact: int, + distance_unit: str, +) -> None: + cutoff_a = proatoms._prepared_pairwise_profile( + _profile(atom_a) + ).cutoff_radius_bohr + cutoff_b = proatoms._prepared_pairwise_profile( + _profile(atom_b) + ).cutoff_radius_bohr + distance_bohr = cutoff_a + cutoff_b + for _ in range(steps_below_contact): + distance_bohr = math.nextafter(distance_bohr, 0.0) + distance = ( + distance_bohr + if distance_unit == "bohr" + else distance_bohr * ar.BOHR_TO_ANGSTROM + ) + + for requested_a, requested_b in ((atom_a, atom_b), (atom_b, atom_a)): + result = _result( + ar.estimate_promolecular_density_minimum( + requested_a, + requested_b, + distance, + distance_unit=distance_unit, + ) + ) + assert result.method == "none" + assert result.status == "no_resolved_interior_minimum" + assert result.position_from_a is None + assert result.position_from_b is None + assert result.alternative_position_from_a is None + assert result.alternative_position_from_b is None + + +def test_raw_angstrom_near_contact_cannot_expose_a_cutoff_endpoint() -> None: + atom_a = "H" + atom_b = "Fe" + cutoff_a = proatoms._prepared_pairwise_profile( + _profile(atom_a) + ).cutoff_radius_bohr + cutoff_b = proatoms._prepared_pairwise_profile( + _profile(atom_b) + ).cutoff_radius_bohr + distance = (cutoff_a + cutoff_b) * ar.BOHR_TO_ANGSTROM + distance = math.nextafter(math.nextafter(distance, 0.0), 0.0) + + for requested_a, requested_b in ((atom_a, atom_b), (atom_b, atom_a)): + result = _result( + ar.estimate_promolecular_density_minimum( + requested_a, + requested_b, + distance, + distance_unit="angstrom", + ) + ) + assert result.method == "none" + assert result.status == "no_resolved_interior_minimum" + assert result.position_from_a is None + assert result.position_from_b is None + assert result.alternative_position_from_a is None + assert result.alternative_position_from_b is None + + +def test_boundary_reports_one_atom_domination_orientation_safely() -> None: + forward = _result( + ar.estimate_proatomic_boundary("H", "Lr", 0.1, distance_unit="bohr") + ) + reverse = _result( + ar.estimate_proatomic_boundary("Lr", "H", 0.1, distance_unit="bohr") + ) + assert forward.method == reverse.method == "none" + assert forward.status == reverse.status == "one_atom_dominates" + assert forward.position_from_a is reverse.position_from_a is None + assert forward.dominant_atom == reverse.dominant_atom == "Lr" + assert forward.dominant_atom_role == "atom_b" + assert reverse.dominant_atom_role == "atom_a" + _assert_reversal(forward, reverse, 0.1) + + +def test_ordinary_minimum_is_resolved_inside_significant_overlap() -> None: + distance = 7.0 + result = _result( + ar.estimate_promolecular_density_minimum( + "H", "O", distance, distance_unit="bohr" + ) + ) + left = max(0.0, distance - result.cutoff_radius_b) + right = min(distance, result.cutoff_radius_a) + assert result.method == "promolecular_density_minimum" + assert result.status == "ok" + assert result.position_from_a is not None + assert left < result.position_from_a < right + assert result.position_from_a not in {left, right, 0.0, distance} + assert result.search_resolution == ar.IAS_MINIMUM_RESOLUTION_BOHR + assert result.search_converged is True + assert result.search_passes == 2 + + +def test_production_coalescing_uses_position_connected_components() -> None: + candidates = tuple( + proatoms._MinimumCandidate(position, density) + for position, density in ( + (0.100, 1.0), + (0.109, 1.0001), + (0.118, 1.00005), + ) + ) + + resolved = proatoms._coalesce_minimum_candidates(candidates) + + assert resolved == (proatoms._MinimumCandidate(0.100, 1.0),) + + +def test_packaged_y_np_bridge_candidates_form_one_resolved_valley() -> None: + distance = 10.212596531891476 + profile_a = proatoms._prepared_pairwise_profile(_profile("Y")) + profile_b = proatoms._prepared_pairwise_profile(_profile("Np")) + overlap_left = max( + 0.0, + distance - profile_b.cutoff_radius_bohr, + ) + overlap_right = min(distance, profile_a.cutoff_radius_bohr) + equal_position, _ = proatoms._equal_contribution_position( + profile_a, + profile_b, + distance, + ) + fallback = proatoms._minimum_grid_pass( + profile_a, + profile_b, + distance, + overlap_left, + overlap_right, + 0.005, + equal_position, + ) + result = _result( + ar.estimate_promolecular_density_minimum( + "Y", + "Np", + distance, + distance_unit="bohr", + ) + ) + reverse = _result( + ar.estimate_promolecular_density_minimum( + "Np", + "Y", + distance, + distance_unit="bohr", + ) + ) + + assert tuple( + candidate.position_bohr for candidate in fallback.candidates + ) == pytest.approx((5.037953988322846, 5.030526030430109)) + assert result.method == "promolecular_density_minimum" + assert result.status == "ok" + assert result.position_from_a == pytest.approx( + 5.0379539928790065, + abs=1.0e-10, + ) + assert result.alternative_position_from_a is None + assert result.alternative_position_from_b is None + assert result.alternative_rho_sum is None + assert result.relative_depth_gap is None + assert result.ambiguous is False + assert result.search_passes == 3 + assert result.search_converged is True + assert reverse.status == "ok" + assert reverse.alternative_position_from_a is None + _assert_reversal(result, reverse, distance) + + +@pytest.mark.parametrize( + ("atom_b", "distance_bohr", "midpoint_bohr", "density"), + [ + ( + "Mg", + 10.427738551450130, + 4.128012047240235, + 0.00020000000000000052, + ), + ( + "K", + 11.554061800270143, + 4.128012047240236, + 0.00020000000000000036, + ), + ], +) +@pytest.mark.parametrize("distance_unit", ["bohr", "angstrom"]) +def test_adjacent_grid_coordinates_preserve_near_contact_midpoint_valley( + atom_b: str, + distance_bohr: float, + midpoint_bohr: float, + density: float, + distance_unit: str, +) -> None: + distance_factor = ( + 1.0 if distance_unit == "bohr" else ar.BOHR_TO_ANGSTROM + ) + distance = distance_bohr * distance_factor + forward = _result( + ar.estimate_promolecular_density_minimum( + "H", + atom_b, + distance, + distance_unit=distance_unit, + ) + ) + reverse = _result( + ar.estimate_promolecular_density_minimum( + atom_b, + "H", + distance, + distance_unit=distance_unit, + ) + ) + + for result in (forward, reverse): + assert result.method == "promolecular_density_minimum" + assert result.status == "ok" + assert result.rho_sum == pytest.approx(density, rel=2.0e-15) + assert result.alternative_position_from_a is None + _assert_minimum_coordinates_strictly_inside_overlap(result) + assert forward.position_from_a == midpoint_bohr * distance_factor + assert reverse.position_from_a == distance - forward.position_from_a + _assert_reversal(forward, reverse, distance) + + converted = _result( + ar.estimate_promolecular_density_minimum( + "H", + atom_b, + distance, + distance_unit=distance_unit, + density_unit="electron/angstrom^3", + ) + ) + assert converted.method == forward.method + assert converted.status == forward.status + assert converted.position_from_a == forward.position_from_a + assert converted.rho_sum == pytest.approx( + density / ar.BOHR_TO_ANGSTROM**3, + rel=2.0e-15, + ) + + +def test_required_grid_passes_retain_coarse_only_competitor( + monkeypatch: pytest.MonkeyPatch, +) -> None: + overlap_left = 0.0 + overlap_right = 1.07 + narrow_center = overlap_right * 26.0 / 54.0 + half_width = 0.002 + + def synthetic_objective( + _profile_a: object, + _profile_b: object, + _distance_bohr: float, + position_bohr: float, + ) -> float: + broad = 1.0 + (position_bohr - 0.2) ** 2 + offset = abs(position_bohr - narrow_center) + if offset >= half_width: + return broad + shape = (1.0 - (offset / half_width) ** 2) ** 2 + depth = 1.0 + (narrow_center - 0.2) ** 2 - 1.00005 + return broad - depth * shape + + monkeypatch.setattr(proatoms, "_objective", synthetic_objective) + coarse = proatoms._minimum_grid_pass( + None, + None, + overlap_right, + overlap_left, + overlap_right, + 0.02, + None, + ) + fine = proatoms._minimum_grid_pass( + None, + None, + overlap_right, + overlap_left, + overlap_right, + 0.01, + None, + ) + + assert len(coarse.candidates) == 2 + assert len(fine.candidates) == 1 + assert proatoms._selections_compatible(coarse.selected, fine.selected) + combined = proatoms._combine_confirmed_minimum_candidates(coarse, fine) + assert len(combined) == 2 + assert combined[1].position_bohr == narrow_center + + fallback = proatoms._MinimumPass(0.005, fine.candidates) + combined_with_fallback = proatoms._combine_confirmed_minimum_candidates( + coarse, + fine, + fallback, + ) + assert len(combined_with_fallback) == 2 + + pass_by_spacing = { + 0.02: coarse, + 0.01: fine, + } + + def controlled_pass( + _profile_a: object, + _profile_b: object, + _distance_bohr: float, + _overlap_left: float, + _overlap_right: float, + max_spacing_bohr: float, + _equal_position_bohr: float | None, + ) -> proatoms._MinimumPass: + return pass_by_spacing[max_spacing_bohr] + + monkeypatch.setattr(proatoms, "_minimum_grid_pass", controlled_pass) + profile_a = proatoms._prepared_pairwise_profile(_profile("H")) + profile_b = proatoms._prepared_pairwise_profile(_profile("O")) + native = proatoms._native_minimum_estimate( + profile_a, + profile_b, + overlap_right, + ) + assert native.status == "ambiguous_competing_minima" + assert native.ambiguous is True + assert native.alternative_position_bohr == narrow_center + assert native.relative_depth_gap == pytest.approx(5.0e-5) + + +def test_fallback_reconciles_candidates_from_every_pass( + monkeypatch: pytest.MonkeyPatch, +) -> None: + coarse_only = proatoms._MinimumCandidate(0.5151851851851852, 1.00005) + fine_primary = proatoms._MinimumCandidate(0.8, 1.0) + coarse = proatoms._MinimumPass(0.02, (coarse_only,)) + fine = proatoms._MinimumPass(0.01, (fine_primary,)) + fallback = proatoms._MinimumPass(0.005, (fine_primary,)) + pass_by_spacing = { + 0.02: coarse, + 0.01: fine, + 0.005: fallback, + } + + def controlled_pass( + _profile_a: object, + _profile_b: object, + _distance_bohr: float, + _overlap_left: float, + _overlap_right: float, + max_spacing_bohr: float, + _equal_position_bohr: float | None, + ) -> proatoms._MinimumPass: + return pass_by_spacing[max_spacing_bohr] + + def synthetic_objective( + _profile_a: object, + _profile_b: object, + _distance_bohr: float, + position_bohr: float, + ) -> float: + return 1.0 + (position_bohr - 0.8) ** 2 + + def synthetic_components( + _profile_a: object, + _profile_b: object, + _distance_bohr: float, + position_bohr: float, + ) -> tuple[float, float, float]: + density = synthetic_objective(None, None, 1.07, position_bohr) + return density / 2.0, density / 2.0, density + + monkeypatch.setattr(proatoms, "_minimum_grid_pass", controlled_pass) + monkeypatch.setattr(proatoms, "_objective", synthetic_objective) + monkeypatch.setattr(proatoms, "_component_values", synthetic_components) + monkeypatch.setattr( + proatoms, + "_equal_contribution_position", + lambda *_args: (None, None), + ) + + profile_a = proatoms._prepared_pairwise_profile(_profile("H")) + profile_b = proatoms._prepared_pairwise_profile(_profile("O")) + native = proatoms._native_minimum_estimate(profile_a, profile_b, 1.07) + + assert native.status == "ambiguous_competing_minima" + assert native.position_bohr == fine_primary.position_bohr + assert native.alternative_position_bohr == coarse_only.position_bohr + assert native.relative_depth_gap == pytest.approx(5.0e-5) + assert native.search_passes == 3 + assert native.search_converged is True + + +def test_minimum_empty_overlap_returns_typed_gap_without_coordinate() -> None: + result = _result( + ar.estimate_promolecular_density_minimum( + "H", "O", 10.0, distance_unit="bohr" + ) + ) + assert result.requested_mode == "minimum" + assert result.method == "none" + assert result.status == "low_density_gap" + assert result.cutoff_regime == "gap" + assert result.position_from_a is None + assert result.rho_sum is None + assert result.search_passes == 0 + + +def test_no_resolved_minimum_does_not_fall_back_to_boundary_mode() -> None: + atom_a, atom_b, distance = _NO_RESOLVED_CASE + minimum = _result( + ar.estimate_promolecular_density_minimum( + atom_a, atom_b, distance, distance_unit="bohr" + ) + ) + boundary = _result( + ar.estimate_proatomic_boundary( + atom_a, atom_b, distance, distance_unit="bohr" + ) + ) + assert minimum.requested_mode == "minimum" + assert minimum.method == "none" + assert minimum.status == "no_resolved_interior_minimum" + assert minimum.position_from_a is None + assert minimum.rho_sum is None + assert boundary.requested_mode == "boundary" + assert boundary.status != minimum.status + + +def test_boundary_dominated_minimum_retains_interior_diagnostic() -> None: + atom_a, atom_b, distance = _BOUNDARY_DOMINATED_CASE + result = _result( + ar.estimate_promolecular_density_minimum( + atom_a, atom_b, distance, distance_unit="bohr" + ) + ) + assert result.requested_mode == "minimum" + assert result.method == "promolecular_density_minimum" + assert result.status == "boundary_dominated" + assert result.position_from_a is not None + assert 0.0 < result.position_from_a < distance + assert result.rho_sum is not None + assert result.search_passes == 3 + _assert_minimum_coordinates_strictly_inside_overlap(result) + + profile_a = _profile(atom_a) + profile_b = _profile(atom_b) + boundary_density = min( + _pair_density_sum(profile_a, profile_b, distance, 0.0), + _pair_density_sum(profile_a, profile_b, distance, distance), + ) + assert boundary_density < result.rho_sum + + +def test_competing_minima_are_limited_to_one_alternative() -> None: + atom_a, atom_b, distance = _AMBIGUOUS_CASE + result = _result( + ar.estimate_promolecular_density_minimum( + atom_a, atom_b, distance, distance_unit="bohr" + ) + ) + assert result.method == "promolecular_density_minimum" + assert result.status == "ambiguous_competing_minima" + assert result.ambiguous is True + assert result.alternative_position_from_a is not None + assert result.alternative_position_from_b is not None + assert result.alternative_rho_sum is not None + assert result.relative_depth_gap is not None + assert result.relative_depth_gap <= 1.0e-4 + assert abs( + result.alternative_position_from_a - result.position_from_a + ) >= ar.IAS_MINIMUM_RESOLUTION_BOHR + assert result.alternative_position_from_a + ( + result.alternative_position_from_b + ) == pytest.approx(distance) + assert result.search_passes == 3 + assert result.search_resolution == 0.005 + assert result.search_converged is True + _assert_minimum_coordinates_strictly_inside_overlap(result) + + +def test_unstable_search_returns_finest_supported_candidate() -> None: + atom_a, atom_b, distance = _UNSTABLE_CASE + result = _result( + ar.estimate_promolecular_density_minimum( + atom_a, atom_b, distance, distance_unit="bohr" + ) + ) + assert result.method == "promolecular_density_minimum" + assert result.status == "search_unstable" + assert result.position_from_a is not None + assert result.search_passes == 3 + assert result.search_resolution == 0.005 + assert result.search_converged is False + _assert_minimum_coordinates_strictly_inside_overlap(result) + + +@pytest.mark.parametrize("case", [_AMBIGUOUS_CASE, _UNSTABLE_CASE]) +def test_minimum_primary_and_alternative_coordinates_reverse( + case: tuple[str, str, float], +) -> None: + atom_a, atom_b, distance = case + forward = _result( + ar.estimate_promolecular_density_minimum( + atom_a, atom_b, distance, distance_unit="bohr" + ) + ) + reverse = _result( + ar.estimate_promolecular_density_minimum( + atom_b, atom_a, distance, distance_unit="bohr" + ) + ) + assert forward.alternative_position_from_a is not None + _assert_minimum_coordinates_strictly_inside_overlap(forward) + _assert_minimum_coordinates_strictly_inside_overlap(reverse) + _assert_reversal(forward, reverse, distance) + + +@pytest.mark.parametrize("mode", ["boundary", "minimum"]) +def test_ordinary_pair_reversal_swaps_components_and_coordinates(mode: str) -> None: + distance = 3.0 + forward = _result( + ar.estimate_ias_position( + "C", "O", distance, mode=mode, distance_unit="bohr" + ) + ) + reverse = _result( + ar.estimate_ias_position( + "O", "C", distance, mode=mode, distance_unit="bohr" + ) + ) + _assert_reversal(forward, reverse, distance) + + +@pytest.mark.parametrize( + ("mode", "atom_a", "atom_b", "distance_bohr"), + [ + ("boundary", "C", "O", 3.0), + ("minimum", *_AMBIGUOUS_CASE), + ], +) +def test_distance_units_preserve_physical_geometry( + mode: str, + atom_a: str, + atom_b: str, + distance_bohr: float, +) -> None: + distance_angstrom = distance_bohr * ar.BOHR_TO_ANGSTROM + native = _result( + ar.estimate_ias_position( + atom_a, + atom_b, + distance_bohr, + mode=mode, + distance_unit="bohr", + ) + ) + converted = _result( + ar.estimate_ias_position( + atom_a, + atom_b, + distance_angstrom, + mode=mode, + distance_unit="angstrom", + ) + ) + assert converted.distance == distance_angstrom + assert converted.method == native.method + assert converted.status == native.status + assert converted.cutoff_regime == native.cutoff_regime + assert converted.position_from_a == pytest.approx( + native.position_from_a * ar.BOHR_TO_ANGSTROM, + rel=2.0e-15, + ) + assert converted.position_from_b == pytest.approx( + native.position_from_b * ar.BOHR_TO_ANGSTROM, + rel=2.0e-15, + ) + assert converted.cutoff_radius_a == pytest.approx( + native.cutoff_radius_a * ar.BOHR_TO_ANGSTROM, + rel=2.0e-15, + ) + assert converted.cutoff_radius_b == pytest.approx( + native.cutoff_radius_b * ar.BOHR_TO_ANGSTROM, + rel=2.0e-15, + ) + assert converted.contour_separation == pytest.approx( + native.contour_separation * ar.BOHR_TO_ANGSTROM, + rel=2.0e-15, + ) + assert converted.alternative_position_from_a == ( + None + if native.alternative_position_from_a is None + else pytest.approx( + native.alternative_position_from_a * ar.BOHR_TO_ANGSTROM, + rel=2.0e-15, + ) + ) + assert converted.alternative_position_from_b == ( + None + if native.alternative_position_from_b is None + else pytest.approx( + native.alternative_position_from_b * ar.BOHR_TO_ANGSTROM, + rel=2.0e-15, + ) + ) + assert converted.search_resolution == ( + None + if native.search_resolution is None + else pytest.approx( + native.search_resolution * ar.BOHR_TO_ANGSTROM, + rel=2.0e-15, + ) + ) + assert converted.rho_sum == pytest.approx(native.rho_sum, rel=2.0e-15) + + +@pytest.mark.parametrize( + ("mode", "atom_a", "atom_b", "distance"), + [ + ("boundary", "C", "O", 3.0), + ("minimum", *_AMBIGUOUS_CASE), + ], +) +def test_density_unit_conversion_cannot_change_scientific_decisions( + mode: str, + atom_a: str, + atom_b: str, + distance: float, +) -> None: + native = _result( + ar.estimate_ias_position( + atom_a, + atom_b, + distance, + mode=mode, + distance_unit="bohr", + ) + ) + converted = _result( + ar.estimate_ias_position( + atom_a, + atom_b, + distance, + mode=mode, + distance_unit="bohr", + density_unit="electron/angstrom^3", + ) + ) + unchanged = ( + "requested_mode", + "method", + "status", + "position_from_a", + "position_from_b", + "fraction_from_a", + "cutoff_radius_a", + "cutoff_radius_b", + "contour_separation", + "cutoff_regime", + "alternative_position_from_a", + "alternative_position_from_b", + "relative_depth_gap", + "ambiguous", + "search_resolution", + "search_converged", + "search_passes", + ) + for field_name in unchanged: + assert getattr(converted, field_name) == getattr(native, field_name) + + factor = 1.0 / ar.BOHR_TO_ANGSTROM**3 + for field_name in ( + "rho_a", + "rho_b", + "rho_sum", + "cutoff_density", + "alternative_rho_sum", + ): + native_value = getattr(native, field_name) + converted_value = getattr(converted, field_name) + if native_value is None: + assert converted_value is None + else: + assert converted_value == pytest.approx( + native_value * factor, + rel=2.0e-15, + ) + + +@pytest.mark.parametrize("mode", ["boundary", "minimum"]) +def test_deuterium_and_tritium_use_the_hydrogen_profile(mode: str) -> None: + hydrogen = ar.estimate_ias_position( + "H", "O", 2.0, mode=mode, distance_unit="bohr" + ) + assert ar.estimate_ias_position( + "D", "O", 2.0, mode=mode, distance_unit="bohr" + ) == hydrogen + assert ar.estimate_ias_position( + "T", "O", 2.0, mode=mode, distance_unit="bohr" + ) == hydrogen + + +@pytest.mark.parametrize("mode", ["boundary", "minimum"]) +def test_missing_profiles_return_none(mode: str) -> None: + assert ( + ar.estimate_ias_position( + "Rf", "O", 2.0, mode=mode, distance_unit="bohr" + ) + is None + ) + assert ( + ar.estimate_ias_position( + "not-an-element", "O", 2.0, mode=mode, distance_unit="bohr" + ) + is None + ) + + +@pytest.mark.parametrize( + "distance", + [0.0, -1.0, math.nan, math.inf, -math.inf, 20.0001, True, None, "bad"], +) +def test_invalid_distances_raise_value_error(distance: object) -> None: + with pytest.raises(ValueError, match="distance"): + ar.estimate_ias_position("H", "O", distance, distance_unit="bohr") + + +def test_distance_endpoint_and_arbitrarily_short_positive_values_are_valid() -> None: + endpoint = _result( + ar.estimate_ias_position("H", "H", 20.0, distance_unit="bohr") + ) + short = _result( + ar.estimate_ias_position("H", "H", 1.0e-12, distance_unit="bohr") + ) + assert endpoint.position_from_a == 10.0 + assert short.position_from_a == 0.5e-12 + + +@pytest.mark.parametrize("mode", ["boundary", "minimum"]) +@pytest.mark.parametrize("distance_unit", ["bohr", "angstrom"]) +def test_smallest_subnormal_homonuclear_midpoint_rounding_is_explicit( + mode: str, + distance_unit: str, +) -> None: + distance = math.ulp(0.0) + result = _result( + ar.estimate_ias_position( + "H", "H", distance, mode=mode, distance_unit=distance_unit + ) + ) + + assert result.method == "homonuclear_midpoint" + assert result.position_from_a == distance / 2.0 == 0.0 + assert result.position_from_b == distance - result.position_from_a + assert result.fraction_from_a == result.position_from_a / distance == 0.0 + + +@pytest.mark.parametrize(("atom_a", "atom_b"), [("H", "O"), ("O", "H")]) +def test_extreme_unlike_distance_cannot_return_a_nucleus_as_minimum( + atom_a: str, + atom_b: str, +) -> None: + result = _result( + ar.estimate_promolecular_density_minimum( + atom_a, + atom_b, + 2.0e-323, + distance_unit="bohr", + ) + ) + + assert result.method == "none" + assert result.status == "no_resolved_interior_minimum" + assert result.position_from_a is None + assert result.position_from_b is None + + +def test_invalid_mode_and_units_raise_value_error() -> None: + with pytest.raises(ValueError, match="mode"): + ar.estimate_ias_position("H", "O", 2.0, mode="automatic") + with pytest.raises(ValueError, match="distance unit"): + ar.estimate_proatomic_boundary("H", "O", 2.0, distance_unit="meter") + with pytest.raises(ValueError, match="density unit"): + ar.estimate_promolecular_density_minimum( + "H", "O", 2.0, density_unit="electron/cm^3" + ) + + +def test_missing_dataset_raises_dataset_error() -> None: + with pytest.raises(DatasetError, match="unknown dataset id"): + ar.estimate_proatomic_boundary("H", "O", 2.0, set_id="missing") diff --git a/tests/proatoms/test_ias_reference.py b/tests/proatoms/test_ias_reference.py new file mode 100644 index 0000000..8c971b0 --- /dev/null +++ b/tests/proatoms/test_ias_reference.py @@ -0,0 +1,770 @@ +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +import math + +import pytest + +import atomref as ar + + +_REFERENCE_GRID_SPACING_BOHR = 0.001 +_RESOLVED_VALLEY_SEPARATION_BOHR = 0.01 +_COMPETING_RELATIVE_DEPTH = 1.0e-4 + + +@dataclass(frozen=True, slots=True) +class _ReferenceCandidate: + """One valley found by the independent dense reference.""" + + position_bohr: float + density: float + + +@dataclass(frozen=True, slots=True) +class _ReferenceSearch: + """Independent cutoff-bounded reference result in native units.""" + + overlap_left_bohr: float + overlap_right_bohr: float + max_grid_spacing_bohr: float + cutoff_radius_a_bohr: float + cutoff_radius_b_bohr: float + raw_candidates: tuple[_ReferenceCandidate, ...] + resolved_candidates: tuple[_ReferenceCandidate, ...] + boundary_density: float + status: str + + @property + def selected(self) -> _ReferenceCandidate | None: + """Return the lowest resolved valley, if present.""" + + if not self.resolved_candidates: + return None + return self.resolved_candidates[0] + + @property + def alternative(self) -> _ReferenceCandidate | None: + """Return the second-lowest separated valley, if present.""" + + if len(self.resolved_candidates) < 2: + return None + return self.resolved_candidates[1] + + +def _profile(symbol: str) -> ar.ProatomicDensityProfile: + profile = ar.get_proatomic_density_profile(symbol) + assert profile is not None + return profile + + +def _independent_cutoff_radius(profile: ar.ProatomicDensityProfile) -> float: + """Invert the public profile knots without a production cutoff helper.""" + + cutoff = ar.PROATOMIC_TAIL_CUTOFF + right = next( + index + for index, density in enumerate(profile.densities) + if density <= cutoff + ) + assert right > 0 + if profile.densities[right] == cutoff: + return profile.radii[right] + + left = right - 1 + log_density_left = math.log(profile.densities[left]) + log_density_right = math.log(profile.densities[right]) + log_radius_left = math.log(profile.radii[left]) + log_radius_right = math.log(profile.radii[right]) + fraction = (math.log(cutoff) - log_density_left) / ( + log_density_right - log_density_left + ) + return math.exp( + log_radius_left + fraction * (log_radius_right - log_radius_left) + ) + + +def _objective( + profile_a: ar.ProatomicDensityProfile, + profile_b: ar.ProatomicDensityProfile, + distance_bohr: float, + position_bohr: float, +) -> float: + """Evaluate the Stage 3 profiles directly in their native units.""" + + return profile_a._evaluate_bohr( + position_bohr + ) + profile_b._evaluate_bohr(distance_bohr - position_bohr) + + +def _refine_by_fixed_subdivision( + function: Callable[[float], float], + left: float, + center: float, + right: float, +) -> _ReferenceCandidate: + """Refine a bracket by repeated nine-point subdivision, not golden search.""" + + center_value = function(center) + best = _ReferenceCandidate(center, center_value) + for _ in range(18): + if math.nextafter(left, right) == right: + break + width = right - left + points = tuple(left + width * index / 8 for index in range(9)) + values = tuple(function(point) for point in points) + best_index = min( + range(len(points)), + key=lambda index: (values[index], points[index]), + ) + local = _ReferenceCandidate( + points[best_index], + values[best_index], + ) + if (local.density, local.position_bohr) < ( + best.density, + best.position_bohr, + ): + best = local + + lower_index = max(0, best_index - 1) + upper_index = min(8, best_index + 1) + if lower_index == upper_index: + break + left, right = points[lower_index], points[upper_index] + return best + + +def _scan_reference_objective( + function: Callable[[float], float], + overlap_left: float, + overlap_right: float, + max_spacing_bohr: float, +) -> tuple[float, tuple[_ReferenceCandidate, ...]]: + """Find strict-interior valleys on one independent uniform reference grid.""" + + if overlap_right <= overlap_left: + return 0.0, () + width = overlap_right - overlap_left + segment_count = max(2, math.ceil(width / max_spacing_bohr)) + max_grid_spacing = width / segment_count + coordinates = [ + overlap_left + width * index / segment_count + for index in range(segment_count + 1) + ] + # An odd segment count misses the exact interval midpoint. An even count + # already contains it mathematically, so do not manufacture an adjacent-ULP + # duplicate by evaluating the equivalent expression a second way. + if segment_count % 2: + coordinates.append((overlap_left + overlap_right) / 2.0) + coordinates.sort() + + values = tuple(function(position) for position in coordinates) + candidates: list[_ReferenceCandidate] = [] + for index in range(1, len(coordinates) - 1): + if ( + values[index] <= values[index - 1] + and values[index] <= values[index + 1] + ): + candidate = _refine_by_fixed_subdivision( + function, + coordinates[index - 1], + coordinates[index], + coordinates[index + 1], + ) + if overlap_left < candidate.position_bohr < overlap_right: + candidates.append(candidate) + return max_grid_spacing, tuple(candidates) + + +def _coalesce_at_public_resolution( + candidates: tuple[_ReferenceCandidate, ...], + overlap_left: float, + overlap_right: float, +) -> tuple[_ReferenceCandidate, ...]: + """Group strict-interior candidates by position-connected components.""" + + ordered = sorted( + ( + candidate + for candidate in candidates + if overlap_left < candidate.position_bohr < overlap_right + ), + key=lambda candidate: candidate.position_bohr, + ) + groups: list[list[_ReferenceCandidate]] = [] + for candidate in ordered: + if ( + not groups + or candidate.position_bohr - groups[-1][-1].position_bohr + >= _RESOLVED_VALLEY_SEPARATION_BOHR + ): + groups.append([candidate]) + else: + groups[-1].append(candidate) + representatives = [ + min( + group, + key=lambda candidate: ( + candidate.density, + candidate.position_bohr, + ), + ) + for group in groups + ] + return tuple( + sorted( + representatives, + key=lambda candidate: ( + candidate.density, + candidate.position_bohr, + ), + ) + ) + + +def _reference_status( + candidates: tuple[_ReferenceCandidate, ...], + boundary_density: float, +) -> str: + """Classify the independently selected practical result.""" + + if not candidates: + return "no_resolved_interior_minimum" + + selected = candidates[0] + roundoff = 128.0 * math.ulp( + max(abs(boundary_density), abs(selected.density), 1.0) + ) + if boundary_density < selected.density - roundoff: + return "boundary_dominated" + + if len(candidates) > 1: + relative_gap = max( + 0.0, + (candidates[1].density - selected.density) / selected.density, + ) + if relative_gap <= _COMPETING_RELATIVE_DEPTH: + return "ambiguous_competing_minima" + return "ok" + + +def _independent_reference_search( + atom_a: str, + atom_b: str, + distance_bohr: float, + *, + max_spacing_bohr: float = _REFERENCE_GRID_SPACING_BOHR, +) -> _ReferenceSearch: + """Search only ``I_c`` using an independent uniform-grid implementation.""" + + profile_a = _profile(atom_a) + profile_b = _profile(atom_b) + cutoff_a = _independent_cutoff_radius(profile_a) + cutoff_b = _independent_cutoff_radius(profile_b) + overlap_left = max(0.0, distance_bohr - cutoff_b) + overlap_right = min(distance_bohr, cutoff_a) + + def function(position: float) -> float: + return _objective( + profile_a, + profile_b, + distance_bohr, + position, + ) + + boundary_density = min(function(0.0), function(distance_bohr)) + if overlap_right <= overlap_left: + return _ReferenceSearch( + overlap_left, + overlap_right, + 0.0, + cutoff_a, + cutoff_b, + (), + (), + boundary_density, + "no_resolved_interior_minimum", + ) + + max_grid_spacing, raw_candidates = _scan_reference_objective( + function, + overlap_left, + overlap_right, + max_spacing_bohr, + ) + resolved = _coalesce_at_public_resolution( + raw_candidates, + overlap_left, + overlap_right, + ) + return _ReferenceSearch( + overlap_left, + overlap_right, + max_grid_spacing, + cutoff_a, + cutoff_b, + raw_candidates, + resolved, + boundary_density, + _reference_status(resolved, boundary_density), + ) + + +def _assert_matches_dense_reference( + atom_a: str, + atom_b: str, + distance_bohr: float, + expected_status: str, +) -> None: + reference = _independent_reference_search( + atom_a, + atom_b, + distance_bohr, + ) + result = ar.estimate_promolecular_density_minimum( + atom_a, + atom_b, + distance_bohr, + distance_unit="bohr", + ) + + assert result is not None + assert reference.max_grid_spacing_bohr <= _REFERENCE_GRID_SPACING_BOHR + assert reference.status == expected_status + assert result.status == expected_status + assert result.method == "promolecular_density_minimum" + assert result.cutoff_radius_a == pytest.approx( + reference.cutoff_radius_a_bohr, + rel=5.0e-13, + ) + assert result.cutoff_radius_b == pytest.approx( + reference.cutoff_radius_b_bohr, + rel=5.0e-13, + ) + + selected = reference.selected + assert selected is not None + assert result.position_from_a is not None + assert result.rho_sum is not None + assert reference.overlap_left_bohr < result.position_from_a + assert result.position_from_a < reference.overlap_right_bohr + assert abs(result.position_from_a - selected.position_bohr) <= ( + ar.IAS_MINIMUM_RESOLUTION_BOHR + ) + assert abs(result.rho_sum - selected.density) / selected.density <= 1.0e-4 + + alternative = reference.alternative + if expected_status == "ambiguous_competing_minima": + assert alternative is not None + assert result.alternative_position_from_a is not None + assert result.alternative_rho_sum is not None + assert abs( + result.alternative_position_from_a - alternative.position_bohr + ) <= ar.IAS_MINIMUM_RESOLUTION_BOHR + assert abs( + result.alternative_rho_sum - alternative.density + ) / alternative.density <= 1.0e-4 + reference_gap = ( + alternative.density - selected.density + ) / selected.density + assert result.relative_depth_gap == pytest.approx( + reference_gap, + rel=2.0e-6, + abs=1.0e-14, + ) + + +@pytest.mark.parametrize( + ("atom_a", "atom_b", "distance_bohr", "status"), + [ + pytest.param("C", "O", 1.5, "ok", id="ordinary-c-o"), + pytest.param("H", "U", 3.8, "ok", id="asymmetric-h-u"), + pytest.param("At", "Bk", 4.0, "ok", id="heavy-at-bk"), + pytest.param( + "Fr", + "Li", + 10.44712292574865, + "ambiguous_competing_minima", + id="competing-fr-li", + ), + pytest.param( + "H", + "Dy", + 1.5865200103109787, + "boundary_dominated", + id="boundary-dominated-h-dy", + ), + ], +) +def test_practical_minimum_matches_independent_dense_reference( + atom_a: str, + atom_b: str, + distance_bohr: float, + status: str, +) -> None: + _assert_matches_dense_reference( + atom_a, + atom_b, + distance_bohr, + status, + ) + + +@pytest.mark.parametrize( + ("atom_a", "atom_b", "distance_bohr"), + [ + pytest.param( + "Ni", + "Te", + 1.5897045597171517, + id="archived-ni-te", + ), + pytest.param( + "Al", + "Pb", + 1.3645306063699802, + id="archived-al-pb", + ), + pytest.param( + "Pr", + "Re", + 0.7592738817632778, + id="archived-pr-re", + ), + ], +) +def test_archived_stage4a_resolved_cases_match_dense_reference( + atom_a: str, + atom_b: str, + distance_bohr: float, +) -> None: + _assert_matches_dense_reference(atom_a, atom_b, distance_bohr, "ok") + + +@pytest.mark.parametrize( + ("atom_a", "atom_b", "distance_bohr"), + [ + pytest.param("H", "U", 1.58, id="archived-h-u"), + pytest.param( + "U", + "H", + 0.05625504168898983, + id="adjacent-ulp-midpoint-regression", + ), + ], +) +def test_difficult_short_pairs_have_no_independent_interior_valley( + atom_a: str, + atom_b: str, + distance_bohr: float, +) -> None: + reference = _independent_reference_search( + atom_a, + atom_b, + distance_bohr, + ) + result = ar.estimate_promolecular_density_minimum( + atom_a, + atom_b, + distance_bohr, + distance_unit="bohr", + ) + + assert not reference.raw_candidates + assert result is not None + assert result.status == "no_resolved_interior_minimum" + assert result.method == "none" + assert result.position_from_a is None + assert result.rho_sum is None + + +@pytest.mark.parametrize( + ("atom_a", "atom_b", "distance_bohr", "near_nucleus"), + [ + pytest.param( + "C", + "Lu", + 0.2217301367970158, + True, + id="archived-c-lu", + ), + pytest.param( + "He", + "Ru", + 0.6552909074349161, + False, + id="archived-he-ru", + ), + ], +) +def test_subresolution_dense_valleys_are_intentionally_unresolved( + atom_a: str, + atom_b: str, + distance_bohr: float, + near_nucleus: bool, +) -> None: + dense = _independent_reference_search( + atom_a, + atom_b, + distance_bohr, + ) + practical_grid = _independent_reference_search( + atom_a, + atom_b, + distance_bohr, + max_spacing_bohr=ar.IAS_MINIMUM_RESOLUTION_BOHR, + ) + result = ar.estimate_promolecular_density_minimum( + atom_a, + atom_b, + distance_bohr, + distance_unit="bohr", + ) + + assert dense.selected is not None + assert not practical_grid.raw_candidates + if near_nucleus: + assert dense.selected.position_bohr < ar.IAS_MINIMUM_RESOLUTION_BOHR + assert result is not None + assert result.status == "no_resolved_interior_minimum" + assert result.method == "none" + assert result.position_from_a is None + + +def test_homonuclear_shell_microstructure_cannot_move_public_midpoint() -> None: + distance_bohr = 5.0 + reference = _independent_reference_search("Li", "Li", distance_bohr) + result = ar.estimate_promolecular_density_minimum( + "Li", + "Li", + distance_bohr, + distance_unit="bohr", + ) + + assert len(reference.raw_candidates) >= 5 + assert reference.selected is not None + assert abs(reference.selected.position_bohr - distance_bohr / 2.0) > ( + ar.IAS_MINIMUM_RESOLUTION_BOHR + ) + assert result is not None + assert result.method == "homonuclear_midpoint" + assert result.position_from_a == distance_bohr / 2.0 + assert result.rho_sum is not None + assert reference.selected.density < result.rho_sum + + +def test_heavy_pair_reference_and_public_result_reverse_together() -> None: + distance_bohr = 4.0 + reference = _independent_reference_search("At", "Bk", distance_bohr) + forward = ar.estimate_promolecular_density_minimum( + "At", + "Bk", + distance_bohr, + distance_unit="bohr", + ) + reverse = ar.estimate_promolecular_density_minimum( + "Bk", + "At", + distance_bohr, + distance_unit="bohr", + ) + + assert reference.selected is not None + assert forward is not None and reverse is not None + assert forward.position_from_a is not None + assert reverse.position_from_a is not None + assert forward.rho_sum is not None and reverse.rho_sum is not None + assert abs( + forward.position_from_a - reference.selected.position_bohr + ) <= ar.IAS_MINIMUM_RESOLUTION_BOHR + assert reverse.position_from_a == pytest.approx( + distance_bohr - forward.position_from_a, + abs=2.0e-15, + ) + assert reverse.rho_sum == pytest.approx(forward.rho_sum, rel=2.0e-15) + assert reverse.status == forward.status + assert reverse.method == forward.method + + +def test_packaged_y_np_reference_rejects_false_competitive_alternative() -> None: + distance_bohr = 10.212596531891476 + reference = _independent_reference_search("Y", "Np", distance_bohr) + result = ar.estimate_promolecular_density_minimum( + "Y", + "Np", + distance_bohr, + distance_unit="bohr", + ) + + assert result is not None + assert reference.status == result.status == "ok" + assert reference.selected is not None + assert reference.alternative is None + assert result.position_from_a == pytest.approx( + reference.selected.position_bohr, + abs=ar.IAS_MINIMUM_RESOLUTION_BOHR, + ) + assert result.alternative_position_from_a is None + assert result.alternative_rho_sum is None + assert result.ambiguous is False + + +@pytest.mark.parametrize( + ("atom_b", "distance_bohr", "midpoint_bohr"), + [ + ("Mg", 10.427738551450130, 4.128012047240235), + ("K", 11.554061800270143, 4.128012047240236), + ], +) +def test_adjacent_float_midpoint_valley_matches_independent_reference( + atom_b: str, + distance_bohr: float, + midpoint_bohr: float, +) -> None: + reference = _independent_reference_search("H", atom_b, distance_bohr) + result = ar.estimate_promolecular_density_minimum( + "H", + atom_b, + distance_bohr, + distance_unit="bohr", + ) + + assert result is not None + assert reference.status == result.status == "ok" + assert reference.selected is not None + assert reference.selected.position_bohr == midpoint_bohr + assert result.position_from_a == midpoint_bohr + assert result.rho_sum == reference.selected.density + + +def test_reference_grouping_uses_position_connected_components() -> None: + candidates = tuple( + _ReferenceCandidate(position, density) + for position, density in ( + (0.100, 1.0), + (0.109, 1.0001), + (0.118, 1.00005), + ) + ) + + resolved = _coalesce_at_public_resolution(candidates, 0.0, 1.0) + + assert resolved == (_ReferenceCandidate(0.100, 1.0),) + + +def test_reference_grouping_rejects_endpoints_but_retains_interior() -> None: + left = 0.0 + right = 1.0 + first_interior = math.nextafter(left, right) + last_interior = math.nextafter(right, left) + candidates = ( + _ReferenceCandidate(left, 0.5), + _ReferenceCandidate(first_interior, 1.0), + _ReferenceCandidate(0.5, 2.0), + _ReferenceCandidate(last_interior, 1.5), + _ReferenceCandidate(right, 0.5), + ) + + resolved = _coalesce_at_public_resolution(candidates, left, right) + + assert all(left < candidate.position_bohr < right for candidate in resolved) + assert first_interior in { + candidate.position_bohr for candidate in resolved + } + assert last_interior in { + candidate.position_bohr for candidate in resolved + } + + +@pytest.mark.parametrize( + ("center", "detected_spacing", "missed_spacing"), + [ + (1.07 * 26.0 / 54.0, 0.02, 0.01), + (1.07 * 53.0 / 107.0, 0.01, 0.02), + ], +) +def test_reference_scanner_resolves_non_nested_grid_valleys_independently( + center: float, + detected_spacing: float, + missed_spacing: float, +) -> None: + half_width = 0.002 + + def function(position: float) -> float: + broad = 1.0 + (position - 0.2) ** 2 + offset = abs(position - center) + if offset >= half_width: + return broad + shape = (1.0 - (offset / half_width) ** 2) ** 2 + depth = 1.0 + (center - 0.2) ** 2 - 1.00005 + return broad - depth * shape + + _, detected = _scan_reference_objective( + function, + 0.0, + 1.07, + detected_spacing, + ) + _, missed = _scan_reference_objective( + function, + 0.0, + 1.07, + missed_spacing, + ) + _, dense = _scan_reference_objective(function, 0.0, 1.07, 0.001) + + assert any( + abs(candidate.position_bohr - center) < half_width + for candidate in detected + ) + assert not any( + abs(candidate.position_bohr - center) < half_width + for candidate in missed + ) + assert any( + abs(candidate.position_bohr - center) < half_width + for candidate in dense + ) + + +@pytest.mark.parametrize(("atom_a", "atom_b"), [("H", "O"), ("O", "H")]) +@pytest.mark.parametrize("steps_below", [1, 2]) +def test_reference_rejects_near_contact_cutoff_endpoints( + atom_a: str, + atom_b: str, + steps_below: int, +) -> None: + cutoff_a = _independent_cutoff_radius(_profile("H")) + cutoff_b = _independent_cutoff_radius(_profile("O")) + distance = cutoff_a + cutoff_b + for _ in range(steps_below): + distance = math.nextafter(distance, 0.0) + + reference = _independent_reference_search(atom_a, atom_b, distance) + + for candidate in ( + *reference.raw_candidates, + *reference.resolved_candidates, + ): + assert ( + reference.overlap_left_bohr + < candidate.position_bohr + < reference.overlap_right_bohr + ) + if not reference.raw_candidates: + assert reference.status == "no_resolved_interior_minimum" + assert not reference.resolved_candidates + + +@pytest.mark.parametrize(("atom_a", "atom_b"), [("H", "O"), ("O", "H")]) +def test_reference_rejects_extreme_unlike_nuclear_candidate( + atom_a: str, + atom_b: str, +) -> None: + reference = _independent_reference_search(atom_a, atom_b, 2.0e-323) + + assert reference.status == "no_resolved_interior_minimum" + assert not reference.raw_candidates + assert not reference.resolved_candidates diff --git a/tools/release_check.py b/tools/release_check.py index a357a18..e71d4e5 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -61,7 +61,15 @@ def _smoke_test_wheel() -> None: "assert ar.get_vdw_radius('C') == 1.77; " "assert 'atomic_radius' in ar.list_quantities(); " "assert 'rahm2016' in ar.list_dataset_ids(" - "'atomic_radius', usage_role='support')" + "'atomic_radius', usage_role='support'); " + "boundary = ar.estimate_proatomic_boundary(" + "'C', 'O', 1.5, distance_unit='bohr'); " + "assert boundary is not None; " + "assert boundary.method == 'equal_proatom_density'; " + "minimum = ar.estimate_promolecular_density_minimum(" + "'C', 'O', 1.5, distance_unit='bohr'); " + "assert minimum is not None; " + "assert minimum.requested_mode == 'minimum'" ), ) From 1777b2d6e4e2ed359ea2bcbf126ff7cecf8ab8a5 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Tue, 14 Jul 2026 14:51:05 +0300 Subject: [PATCH 07/23] docs: add proatomic density and pairwise feature notebook --- README.md | 65 ++- docs/api/proatoms.md | 18 +- docs/api/registry.md | 2 +- docs/dev/architecture.md | 11 +- docs/dev/data_curation.md | 9 +- docs/guide/install.md | 3 +- docs/guide/non_goals.md | 7 +- docs/guide/notebooks.md | 15 +- docs/guide/policies.md | 2 +- docs/guide/proatomic_density.md | 91 +++- docs/guide/quickstart.md | 3 +- docs/index.md | 65 ++- notebooks/05-proatomic-density-and-ias.ipynb | 414 +++++++++++++++++++ pyproject.toml | 3 + src/atomref/proatoms.py | 5 + tests/meta/test_notebooks.py | 1 + tests/meta/test_readme_sync.py | 30 +- tools/README.md | 7 +- tools/check_notebooks.py | 188 ++++++++- tools/check_registry.py | 2 +- tools/gen_readme.py | 6 +- 21 files changed, 844 insertions(+), 103 deletions(-) create mode 100644 notebooks/05-proatomic-density-and-ias.ipynb diff --git a/README.md b/README.md index aa357f4..3ba67d8 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ Documentation: https://delonecommons.github.io/atomref -`atomref` is a small pure-Python package for **curated atomic reference data** -and **provenance-aware lookup policies** used by geometry and -structure-analysis algorithms. +`atomref` is a small pure-Python package for **curated atomic reference data**, +**provenance-aware lookup policies**, and neutral-proatom reference workflows +used by geometry and structure-analysis algorithms. It is not meant to be yet another periodic-table encyclopedia. The package is for code that needs stable atomic reference values with explicit provenance, @@ -26,7 +26,11 @@ What you get in the current release line: - substitution and linear transfer from support datasets or policies into target datasets, - guarded nested policy-backed transfers with explicit transfer depth, conservative fit/prediction controls, and cycle detection, -- user-defined custom element-indexed scalar sets. +- user-defined custom element-indexed scalar sets, +- frozen neutral H–Lr spherical proatomic-density profiles with scalar, + unit-aware evaluation, +- a stable pairwise neutral-proatom boundary and an optional cutoff-bounded, + resolution-limited promolecular-minimum proxy. ## Core terms @@ -34,19 +38,21 @@ What you get in the current release line: - **quantity** — the operational property family being requested, such as `covalent_radius`, `van_der_waals_radius`, `atomic_radius`, or - `xh_bond_length`. + `xh_bond_length`, or `proatomic_density`. - **domain** — the key space used to index that quantity. In the current runtime, the supported domain is `element`, meaning lookups are keyed by an element symbol. -- **dataset** — one curated named table inside a quantity, such as - `cordero2008`, `alvarez2013`, or `csd_legacy_xh_cno`. -- **policy** — the ordered rule set that decides what value to return when the - preferred dataset is incomplete. - -The metadata layer already records `domain` explicitly because the package is -built for later extension, but the current runtime intentionally keeps the -implementation narrow and stable: **the current runtime resolves only -element-domain scalar values**. +- **dataset** — one curated named scalar table or radial-profile payload inside + a quantity, such as `cordero2008`, `alvarez2013`, or the neutral H–Lr + proatomic set. +- **policy** — the ordered rule set that decides what scalar value to return + when the preferred dataset is incomplete. + +The metadata layer records `domain` and storage kind explicitly. The current +runtime supports the `element` domain with packaged scalar tables and one +shared-grid radial-profile family. Lookup policies, substitution, and linear +transfer intentionally remain scalar-only; radial profiles are never completed +by correlation or neighboring-element substitution. ## Why this exists @@ -94,6 +100,11 @@ provisional X–H inference from a chosen covalent-radii policy. 1 >>> lookup.resolved_from (DatasetRef(quantity='atomic_radius', set_id='rahm2016'),) +>>> ar.get_proatomic_density("O", 0.75) +0.1141799693379811 +>>> boundary = ar.estimate_proatomic_boundary("C", "O", 1.5, distance_unit="bohr") +>>> boundary.method, boundary.status +('equal_proatom_density', 'ok') ``` `get_*` returns only the number. `lookup_*` returns a `LookupResult` that also @@ -122,18 +133,33 @@ You can also load a packaged set directly: >>> xh = ar.get_xh_set("csd_legacy_xh_cno") >>> xh.get("C") 1.089 +>>> info = ar.get_proatomic_density_set_info() +>>> radial = ar.get_builtin_set(info.ref) +>>> type(radial).__name__ +'ElementRadialSet' ``` +The proatomic profiles are isolated neutral spherical references, not molecular +electron densities. Pairwise `boundary` mode is the stable default. Optional +`minimum` mode searches only above the fixed `1e-4 electron/bohr^3` per-atom +tail cutoff and has a practical resolution of `0.01 bohr`; it may return an +explicit diagnostic status without a coordinate. See the +[proatomic-density guide](https://delonecommons.github.io/atomref/guide/proatomic_density/) +for the 20-bohr domain, units, provenance, mode selection, and physical +limitations. + ## Notebook walkthroughs -The repository ships example notebooks for the main workflows. In the -documentation they are also available as rendered Markdown pages, so users can -read them without opening Jupyter first. +The repository ships example notebooks for the main workflows. The first three +are also available as rendered Markdown pages. The `0.2.0` proatomic notebooks +are saved, executed source artifacts with outputs and plots. - [Notebook overview](https://delonecommons.github.io/atomref/guide/notebooks/) - [Quickstart notebook](https://delonecommons.github.io/atomref/notebooks/01-quickstart/) - [Policies and assessment notebook](https://delonecommons.github.io/atomref/notebooks/02-policies-and-assessment/) - [Custom sets and discovery notebook](https://delonecommons.github.io/atomref/notebooks/03-custom-sets-and-discovery/) +- [IAS method-selection study](https://delonecommons.github.io/atomref/dev/ias_method_selection/) +- [Executed proatomic density and IAS feature notebook](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) ## Relationship to Delone Commons @@ -151,8 +177,9 @@ packages and are not direct consumers of `atomref`. The repository also ships small maintenance tools. The most important ones are: - `python tools/check_registry.py` — validate curated registry metadata against - packaged CSV tables, -- `python tools/check_notebooks.py` — execute notebook code cells, + every packaged scalar and radial payload, +- `python tools/check_notebooks.py` — validate saved release-notebook state and + execute notebook code cells, - `python tools/export_notebooks.py` — turn notebooks into Markdown pages for the docs, - `python tools/gen_readme.py` — regenerate `README.md` from this page, diff --git a/docs/api/proatoms.md b/docs/api/proatoms.md index 581dece..0e69a7d 100644 --- a/docs/api/proatoms.md +++ b/docs/api/proatoms.md @@ -1,7 +1,19 @@ # atomref.proatoms -This module exposes immutable neutral proatomic-density profiles and scalar -positive-region log-log evaluation. See the [proatomic-density guide](../guide/proatomic_density.md) -for provenance, units, range, interpolation, and limitations. +This module exposes the accepted `0.2.0` density and two-mode pairwise API: + +- `ProatomicDensityProfile` and `ProatomicDensitySet` for immutable radial data; +- `get_proatomic_density_profile()` and `get_proatomic_density()` for scalar + positive-region log-log evaluation; +- `estimate_proatomic_boundary()` for the stable default divider; +- `estimate_promolecular_density_minimum()` for the optional cutoff-bounded, + resolution-limited minimum proxy; +- `estimate_ias_position()` for explicit mode dispatch; +- `IASPositionResult` for coordinates, statuses, cutoff/search diagnostics, + units, and provenance. + +See the [proatomic-density guide](../guide/proatomic_density.md) for source +identity, units, the 20-bohr range, fixed cutoff, mode selection, statuses, and +limitations. ::: atomref.proatoms diff --git a/docs/api/registry.md b/docs/api/registry.md index 1d20375..93a0b13 100644 --- a/docs/api/registry.md +++ b/docs/api/registry.md @@ -10,7 +10,7 @@ The most important registry ideas are: - **quantity** — the operational property family, - **domain** — the key space used to index that quantity, -- **dataset** — one curated named table inside the quantity. +- **dataset** — one curated named source payload inside the quantity. In the current runtime, the implemented lookup domain is `element`. The registry still stores `domain` explicitly because the metadata design is diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 6c8c3fd..cee495a 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -1,6 +1,8 @@ # Architecture -Publicly, `atomref` is still radii-first, with a small provisional X–H helper. +Publicly, `atomref` provides scalar radii and X–H reference workflows together +with neutral radial proatomic-density evaluation and two explicit pairwise +neutral-proatom modes. Internally, the package is built around four layers: @@ -9,8 +11,9 @@ Internally, the package is built around four layers: scalar/radial data loading, 3. **policy core** — generic value selection with overrides, transfers, fallbacks, blocked keys, and provenance, -4. **quantity wrappers** — convenience APIs such as `atomref.radii` and - `atomref.xh`. +4. **quantity features** — scalar convenience APIs such as `atomref.radii` and + `atomref.xh`, plus immutable radial profiles and pairwise analysis in + `atomref.proatoms`. ## Core terminology @@ -18,7 +21,7 @@ A few terms are deliberately separated in the design: - **quantity** — the operational property family being requested, - **domain** — the key space used to index that quantity, -- **dataset** — one curated source table inside the quantity, +- **dataset** — one curated source payload inside the quantity, - **policy** — the ordered rule set used to select a final value. This separation is what allows the package to say, for example, that diff --git a/docs/dev/data_curation.md b/docs/dev/data_curation.md index 689ae24..37ae4b1 100644 --- a/docs/dev/data_curation.md +++ b/docs/dev/data_curation.md @@ -1,7 +1,9 @@ # Data curation -Packaged tables are stored as CSV files indexed by atomic number. Dataset -metadata and provenance live in `src/atomref/data/registry.json`. +Packaged scalar tables are stored as CSV files indexed by atomic number. The +radial dataset is a shared-grid CSV payload inside a deterministic ZIP. Dataset +metadata, storage declarations, and provenance live in +`src/atomref/data/registry.json`. Placeholder values are modeled as dataset metadata, not as hard-coded Python constants. @@ -19,7 +21,8 @@ This matters for support-only datasets such as `atomic_radius:rahm2016`, which is packaged as atomic support data and then used by the default van der Waals policy through linear transfer. -To check that metadata and packaged tables stay synchronized, run: +To check that metadata and every packaged scalar or radial payload stay +synchronized, run: ```bash python tools/check_registry.py diff --git a/docs/guide/install.md b/docs/guide/install.md index e7e0697..9153eec 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -13,12 +13,13 @@ For local development, documentation work, and tests, install the editable package together with the main extras: ```bash -pip install -e ".[test,docs,dev]" +pip install -e ".[test,notebook,docs,dev]" ``` Those extras currently cover: - `test` — pytest and test-only compatibility helpers, +- `notebook` — Matplotlib for executing the saved plotting notebooks, - `docs` — MkDocs and API documentation tooling, - `dev` — flake8, build, and release metadata checks. diff --git a/docs/guide/non_goals.md b/docs/guide/non_goals.md index b38aa68..fe29101 100644 --- a/docs/guide/non_goals.md +++ b/docs/guide/non_goals.md @@ -17,7 +17,6 @@ The package is about **curated reference data and explicit lookup policies**. That includes provenance, transfer from broader support datasets, and stable API surfaces that higher-level scientific code can rely on. -Future versions may widen the range of supported *reference-data families* — for -example X–H distances or radial atomic reference functions — but the package -should still remain a small reference-data layer rather than a full chemistry -platform. +Future versions may widen the range of supported *reference-data families*, but +the package should still remain a small reference-data layer rather than a full +chemistry platform. diff --git a/docs/guide/notebooks.md b/docs/guide/notebooks.md index 43a7c0b..e7a93c5 100644 --- a/docs/guide/notebooks.md +++ b/docs/guide/notebooks.md @@ -1,10 +1,11 @@ # Notebook gallery -`atomref` ships example and supporting-information Jupyter notebooks. -The three user-workflow notebooks have generated Markdown copies in these docs. -The Stage 4 method-selection notebook instead has a compact documentation page -with its main plots and conclusions, while the complete executed `.ipynb` -remains in the repository. +`atomref` ships example and supporting-information Jupyter notebooks. The first +three user-workflow notebooks have generated Markdown copies in these docs. +The two `0.2.0` proatomic notebooks are saved, executed `.ipynb` artifacts: the +method-selection study also has a compact documentation page, while the feature +notebook is linked directly so its embedded plots and outputs remain visible on +GitHub. Direct notebook rendering is planned for a later documentation release. ## Available notebooks @@ -17,6 +18,9 @@ remains in the repository. - [IAS method-selection study](../dev/ias_method_selection.md) — supporting numerical evidence for the two-mode pairwise design and the decision not to expose every interpolation-scale local minimum. +- [Proatomic density and IAS feature notebook](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) + — executed public-API examples for profile discovery, unit-aware density + evaluation, both pairwise modes, diagnostic cases, and saved plots. The original notebook files are also in the repository: @@ -24,3 +28,4 @@ The original notebook files are also in the repository: - [`02-policies-and-assessment.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/02-policies-and-assessment.ipynb) - [`03-custom-sets-and-discovery.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/03-custom-sets-and-discovery.ipynb) - [`04-ias-method-selection-study.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/04-ias-method-selection-study.ipynb) +- [`05-proatomic-density-and-ias.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) diff --git a/docs/guide/policies.md b/docs/guide/policies.md index b9e3b7a..2586825 100644 --- a/docs/guide/policies.md +++ b/docs/guide/policies.md @@ -14,7 +14,7 @@ A few terms appear repeatedly in the API and docs: - **quantity** — the operational property family being requested. - **domain** — the lookup key space. In the current runtime that means `element`, so lookups are keyed by element symbol. -- **dataset** — a curated named table inside one quantity. +- **dataset** — a curated named source payload inside one quantity. - **policy** — the ordered rule set used to resolve missing values. The quantity and dataset live in the curated registry. The policy is the diff --git a/docs/guide/proatomic_density.md b/docs/guide/proatomic_density.md index d51a79a..fc7a014 100644 --- a/docs/guide/proatomic_density.md +++ b/docs/guide/proatomic_density.md @@ -31,10 +31,11 @@ The supported public interval is exactly 0 through 20 bohr, inclusive. The stored snapshot retains one source point above 20 bohr only to bracket the endpoint. Between positive stored knots, evaluation uses the dependency-free `loglog_positive_bracketed_v1` contract: linear interpolation in `log(r)` and -`log(rho)`. Exact knots return their stored values. At zero and below the first -finite grid point, the first stored density is returned; this is a finite-grid -convention, not an exact evaluation at the nucleus. Negative, non-finite, and -above-domain radii raise `ValueError`; there is no extrapolation or zero fill. +`log(rho)`. Exact knots return their stored values. Over `0 <= r <=` the first +positive stored radius, the first stored density is returned; this is a +finite-grid convention, not an exact evaluation at the nucleus. Negative, +non-finite, and above-domain radii raise `ValueError`; there is no extrapolation +or zero fill. Elements may be supplied as symbols or integer atomic numbers. Symbols follow the package's normal element handling, and `D` and `T` use H's electronic @@ -47,6 +48,12 @@ grids, stored values, and cached profile views are immutable. This API describes method-, basis-, state-, and sphericalization-defined isolated-atom references, not unique basis-independent atomic observables or molecular densities. +The metadata names the immutable source as `atomref-proatoms` 2.0.0, dataset +`pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`, and records the source profile, +metadata, and basis hashes. The imported profile data are CC BY 4.0; package +code has its own license. Use `get_proatomic_density_set_info()` to retain this +provenance in downstream work. + ## Pairwise estimates Three functions expose the Stage 4 pairwise model: @@ -61,19 +68,34 @@ same_boundary = ar.estimate_ias_position( ) ``` -Coordinates are measured from the first atom toward the second. `boundary` is -the dispatcher default: it returns the equal-neutral-proatom divider while the -two fixed tail contours overlap, and the midpoint of their gap after they -separate. `minimum` instead returns one practically resolved minimum of the -summed promolecular line density, searched only where both components reach -the fixed `1e-4 electron/bohr^3` cutoff. Its declared spatial resolution is -`0.01 bohr`, and it never silently switches to boundary mode. For unlike -atoms, any returned primary or alternative minimum lies strictly inside that -overlap interval. Raw candidates from every executed grid pass are combined -before one position-connected resolution grouping; distinct adjacent binary64 -grid points are retained. A refinement that lands on a cutoff endpoint or -nucleus is discarded; when no strict-interior valley remains, the typed result -reports `no_resolved_interior_minimum` without a coordinate. +Pair distances must satisfy `0 < R <= 20 bohr`; the default input unit is +angstrom. Coordinates are measured from the first atom toward the second and +are returned in the selected distance unit. Distance and density units are +independent, just as they are for profile evaluation. + +Reversing atom A and atom B maps every returned primary or alternative +coordinate `x` to `R - x`, swaps A/B component fields, and relabels +`dominant_atom` and its role. Method, status, total density, cutoff regime, and +orientation-independent search diagnostics remain equivalent under that +relabeling. + +`boundary` is the dispatcher default and the recommended stable geometry-facing +choice. Identical atoms return exactly `R/2`. While unlike-atom cutoff contours +overlap, it returns the equal-neutral-proatom-density divider. After the +contours separate, it returns the midpoint of their low-density gap. Complete +one-atom dominance is reported without inventing a coordinate. + +`minimum` is an optional, Bader-oriented neutral-promolecular proxy. It searches +for one practically resolved minimum of the summed line density only inside the +meaningful-overlap interval. That interval uses a fixed per-atom cutoff of +exactly `1e-4 electron/bohr^3`; the cutoff is a model tail policy, not a +universal interaction threshold. The declared minimum resolution is +`0.01 bohr`. The mode deliberately coalesces or rejects sub-resolution +features, may expose one competitive alternative, and never silently switches +to boundary mode. A candidate at a cutoff endpoint or nucleus is not a valid +strict-interior minimum. + +## Results and diagnostic statuses Both functions return an immutable `IASPositionResult` containing the method, status, units, component densities, cutoff geometry, search diagnostics, and @@ -81,7 +103,36 @@ dataset/interpolation/numerical-contract provenance. Valid pairs for which a mode is not scientifically applicable return a typed result with no coordinate; missing profiles return `None`. +Always inspect `method`, `status`, and `position_from_a`. The explicit statuses +are: + +| Status | Meaning | +|---|---| +| `ok` | The requested mode returned its ordinary result. | +| `low_density_gap` | The fixed cutoff contours are separated; boundary mode may return the gap midpoint, while minimum mode returns no coordinate. | +| `one_atom_dominates` | No interior equal-contribution boundary exists for the unlike pair. | +| `no_resolved_interior_minimum` | Minimum mode found no strict-interior valley at its practical resolution. | +| `boundary_dominated` | A selected interior minimum exists, but an internuclear-interval boundary is lower. | +| `ambiguous_competing_minima` | A competitive resolved alternative is reported. | +| `search_unstable` | Required search passes did not agree at the declared resolution. | + +`ambiguous`, `search_converged`, `search_passes`, `dominant_atom`, and the +alternative-position fields preserve the corresponding diagnostics. The result +also records the exact dataset ID, interpolation contract, cutoff density, and +pairwise numerical contract. + +## Choosing a mode and understanding the limit + +Use `boundary` for a stable pairwise divider in geometry, Voronoi/Laguerre +calibration, and similar reference-atom workflows. Use `minimum` only when a +cutoff-bounded promolecular line-density valley is the intended quantity and a +resolution-limited non-result is acceptable. + These are neutral-proatom estimates. Neither mode locates a molecular QTAIM -zero-flux surface or an exact molecular-density critical point. The numerical -choice and its limitations are documented in the -[IAS method-selection study](../dev/ias_method_selection.md). +zero-flux surface or an exact molecular-density critical point. They do not add +ionic, environment-dependent, vectorized, molecular-density, or grid-density +behavior. See the executed +[feature notebook on GitHub](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) +for the public workflows and the [IAS method-selection +study](../dev/ias_method_selection.md) for the numerical decision and archived +all-minima limitations. diff --git a/docs/guide/quickstart.md b/docs/guide/quickstart.md index fc9d629..8db9586 100644 --- a/docs/guide/quickstart.md +++ b/docs/guide/quickstart.md @@ -58,4 +58,5 @@ For longer, runnable examples see: - the [notebook overview](notebooks.md), - the [quickstart notebook page](../notebooks/01-quickstart.md), - the [policies notebook page](../notebooks/02-policies-and-assessment.md), -- the [custom sets notebook page](../notebooks/03-custom-sets-and-discovery.md). +- the [custom sets notebook page](../notebooks/03-custom-sets-and-discovery.md), +- the [executed proatomic density and IAS feature notebook](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb). diff --git a/docs/index.md b/docs/index.md index 0b9f2e4..2a9bf7a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,9 +6,9 @@ [![Python Versions](https://img.shields.io/pypi/pyversions/atomref.svg)](https://pypi.org/project/atomref/) [![License](https://img.shields.io/pypi/l/atomref.svg)](https://github.com/DeloneCommons/atomref/blob/main/LICENSE) -`atomref` is a small pure-Python package for **curated atomic reference data** -and **provenance-aware lookup policies** used by geometry and -structure-analysis algorithms. +`atomref` is a small pure-Python package for **curated atomic reference data**, +**provenance-aware lookup policies**, and neutral-proatom reference workflows +used by geometry and structure-analysis algorithms. It is not meant to be yet another periodic-table encyclopedia. The package is for code that needs stable atomic reference values with explicit provenance, @@ -24,7 +24,11 @@ What you get in the current release line: - substitution and linear transfer from support datasets or policies into target datasets, - guarded nested policy-backed transfers with explicit transfer depth, conservative fit/prediction controls, and cycle detection, -- user-defined custom element-indexed scalar sets. +- user-defined custom element-indexed scalar sets, +- frozen neutral H–Lr spherical proatomic-density profiles with scalar, + unit-aware evaluation, +- a stable pairwise neutral-proatom boundary and an optional cutoff-bounded, + resolution-limited promolecular-minimum proxy. ## Core terms @@ -32,19 +36,21 @@ What you get in the current release line: - **quantity** — the operational property family being requested, such as `covalent_radius`, `van_der_waals_radius`, `atomic_radius`, or - `xh_bond_length`. + `xh_bond_length`, or `proatomic_density`. - **domain** — the key space used to index that quantity. In the current runtime, the supported domain is `element`, meaning lookups are keyed by an element symbol. -- **dataset** — one curated named table inside a quantity, such as - `cordero2008`, `alvarez2013`, or `csd_legacy_xh_cno`. -- **policy** — the ordered rule set that decides what value to return when the - preferred dataset is incomplete. - -The metadata layer already records `domain` explicitly because the package is -built for later extension, but the current runtime intentionally keeps the -implementation narrow and stable: **the current runtime resolves only -element-domain scalar values**. +- **dataset** — one curated named scalar table or radial-profile payload inside + a quantity, such as `cordero2008`, `alvarez2013`, or the neutral H–Lr + proatomic set. +- **policy** — the ordered rule set that decides what scalar value to return + when the preferred dataset is incomplete. + +The metadata layer records `domain` and storage kind explicitly. The current +runtime supports the `element` domain with packaged scalar tables and one +shared-grid radial-profile family. Lookup policies, substitution, and linear +transfer intentionally remain scalar-only; radial profiles are never completed +by correlation or neighboring-element substitution. ## Why this exists @@ -92,6 +98,11 @@ provisional X–H inference from a chosen covalent-radii policy. 1 >>> lookup.resolved_from (DatasetRef(quantity='atomic_radius', set_id='rahm2016'),) +>>> ar.get_proatomic_density("O", 0.75) +0.1141799693379811 +>>> boundary = ar.estimate_proatomic_boundary("C", "O", 1.5, distance_unit="bohr") +>>> boundary.method, boundary.status +('equal_proatom_density', 'ok') ``` `get_*` returns only the number. `lookup_*` returns a `LookupResult` that also @@ -120,18 +131,33 @@ You can also load a packaged set directly: >>> xh = ar.get_xh_set("csd_legacy_xh_cno") >>> xh.get("C") 1.089 +>>> info = ar.get_proatomic_density_set_info() +>>> radial = ar.get_builtin_set(info.ref) +>>> type(radial).__name__ +'ElementRadialSet' ``` +The proatomic profiles are isolated neutral spherical references, not molecular +electron densities. Pairwise `boundary` mode is the stable default. Optional +`minimum` mode searches only above the fixed `1e-4 electron/bohr^3` per-atom +tail cutoff and has a practical resolution of `0.01 bohr`; it may return an +explicit diagnostic status without a coordinate. See the +[proatomic-density guide](https://delonecommons.github.io/atomref/guide/proatomic_density/) +for the 20-bohr domain, units, provenance, mode selection, and physical +limitations. + ## Notebook walkthroughs -The repository ships example notebooks for the main workflows. In the -documentation they are also available as rendered Markdown pages, so users can -read them without opening Jupyter first. +The repository ships example notebooks for the main workflows. The first three +are also available as rendered Markdown pages. The `0.2.0` proatomic notebooks +are saved, executed source artifacts with outputs and plots. - [Notebook overview](https://delonecommons.github.io/atomref/guide/notebooks/) - [Quickstart notebook](https://delonecommons.github.io/atomref/notebooks/01-quickstart/) - [Policies and assessment notebook](https://delonecommons.github.io/atomref/notebooks/02-policies-and-assessment/) - [Custom sets and discovery notebook](https://delonecommons.github.io/atomref/notebooks/03-custom-sets-and-discovery/) +- [IAS method-selection study](https://delonecommons.github.io/atomref/dev/ias_method_selection/) +- [Executed proatomic density and IAS feature notebook](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) ## Relationship to Delone Commons @@ -149,8 +175,9 @@ packages and are not direct consumers of `atomref`. The repository also ships small maintenance tools. The most important ones are: - `python tools/check_registry.py` — validate curated registry metadata against - packaged CSV tables, -- `python tools/check_notebooks.py` — execute notebook code cells, + every packaged scalar and radial payload, +- `python tools/check_notebooks.py` — validate saved release-notebook state and + execute notebook code cells, - `python tools/export_notebooks.py` — turn notebooks into Markdown pages for the docs, - `python tools/gen_readme.py` — regenerate `README.md` from this page, diff --git a/notebooks/05-proatomic-density-and-ias.ipynb b/notebooks/05-proatomic-density-and-ias.ipynb new file mode 100644 index 0000000..6446b91 --- /dev/null +++ b/notebooks/05-proatomic-density-and-ias.ipynb @@ -0,0 +1,414 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Neutral proatomic density and pairwise IAS-proxy workflows\n", + "\n", + "This notebook demonstrates the public `atomref 0.2.0` density and pairwise APIs. The profiles are frozen, isolated, neutral, spherical proatoms—not molecular electron densities. The pairwise results are useful reference-atom models, but neither mode is an exact molecular QTAIM zero-flux surface or critical point." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "The package has no runtime plotting dependency. This release notebook uses the optional `notebook` extra for Matplotlib and selects its non-interactive backend so the same cells run deterministically in CI." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "atomref 0.2.0; matplotlib 3.11.0\n" + ] + } + ], + "source": [ + "from __future__ import annotations\n", + "\n", + "import math\n", + "from pathlib import Path\n", + "import sys\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"src\" / \"atomref\").is_dir():\n", + " for candidate in (ROOT.parent, ROOT.parent.parent):\n", + " if (candidate / \"src\" / \"atomref\").is_dir():\n", + " ROOT = candidate\n", + " break\n", + "if not (ROOT / \"src\" / \"atomref\").is_dir():\n", + " raise RuntimeError(\"Run this notebook from the atomref repository checkout\")\n", + "sys.path.insert(0, str(ROOT / \"src\"))\n", + "\n", + "import atomref as ar\n", + "import matplotlib\n", + "matplotlib.use(\"Agg\")\n", + "import matplotlib.pyplot as plt\n", + "\n", + "print(f\"atomref {ar.__version__}; matplotlib {matplotlib.__version__}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dataset discovery and provenance\n", + "\n", + "The radial dataset uses the same registry and `get_builtin_set()` dispatcher as scalar tables. Notice the typed radial payload, complete H–Lr coverage, exact upstream release and dataset identity, 20-bohr public limit, and separate CC BY 4.0 data license." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quantity: 'proatomic_density'; domain='element'; units='electron/bohr^3'\n", + "Dataset: pbe0_sfx2c_dyallv4z_h-lr_neutral_v2\n", + "Payload: ElementRadialSet; profiles=103; grid rows=1127\n", + "Source: atomref-proatoms 2.0.0, pbe0_sfx2c_dyallv4z_h-lr_spherical_v2\n", + "Method: PBE0 self-consistent spherical fractional-occupation UKS with spin-free one-electron X2C and the dyall-v4z basis.\n", + "Public radius limit: 20.0 bohr; data license: CC BY 4.0\n", + "DOIs: 10.5281/zenodo.21291021, 10.5281/zenodo.21291022\n" + ] + } + ], + "source": [ + "quantity = ar.get_quantity_info(\"proatomic_density\")\n", + "info = ar.get_proatomic_density_set_info()\n", + "dataset = ar.get_builtin_set(info.ref)\n", + "storage = info.storage\n", + "\n", + "print(f\"Quantity: {quantity.quantity!r}; domain={quantity.domain!r}; units={quantity.units!r}\")\n", + "print(f\"Dataset: {info.ref.set_id}\")\n", + "print(f\"Payload: {type(dataset).__name__}; profiles={info.coverage.n_values}; grid rows={len(dataset.radii)}\")\n", + "print(f\"Source: {storage['source_project']} {storage['source_release']}, {storage['source_dataset_id']}\")\n", + "print(f\"Method: {info.method_summary}\")\n", + "print(f\"Public radius limit: {storage['public_max_radius_bohr']} bohr; data license: {storage['data_license']}\")\n", + "print(\"DOIs:\", \", \".join(reference.doi for reference in info.references if reference.doi))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Profile retrieval and scalar evaluation\n", + "\n", + "Profiles are immutable views on the shared grid. Radius input and density output units are independent: the two evaluations below use different radius units for the same physical point, while the final value requests electron/ų explicitly." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Profile: O; Z=8; contract=loglog_positive_bracketed_v1\n", + "rho_O(0.75 Å) = 0.114179969338 electron/bohr^3\n", + "same point = 0.114179969338 electron/bohr^3\n", + "converted output = 0.770524625676 electron/angstrom^3\n" + ] + } + ], + "source": [ + "oxygen = ar.get_proatomic_density_profile(\"O\")\n", + "assert oxygen is not None\n", + "radius_angstrom = 0.75\n", + "radius_bohr = radius_angstrom / ar.BOHR_TO_ANGSTROM\n", + "rho_native_from_angstrom = oxygen(radius_angstrom)\n", + "rho_native_from_bohr = oxygen(radius_bohr, radius_unit=\"bohr\")\n", + "rho_angstrom = oxygen(\n", + " radius_bohr, radius_unit=\"bohr\", density_unit=\"electron/angstrom^3\"\n", + ")\n", + "assert math.isclose(rho_native_from_angstrom, rho_native_from_bohr, rel_tol=1e-14)\n", + "\n", + "print(f\"Profile: {oxygen.symbol}; Z={oxygen.atomic_number}; contract={oxygen.interpolation_contract}\")\n", + "print(f\"rho_O(0.75 Å) = {rho_native_from_angstrom:.12g} electron/bohr^3\")\n", + "print(f\"same point = {rho_native_from_bohr:.12g} electron/bohr^3\")\n", + "print(f\"converted output = {rho_angstrom:.12g} electron/angstrom^3\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Radial-density profiles\n", + "\n", + "A logarithmic density axis exposes both the compact core and long neutral tail. These curves are isolated-atom references sampled through the public scalar evaluator; they are not densities of atoms embedded in a molecule." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Plotted C and O profiles from 0.01 through 10 bohr.\n" + ] + }, + { + "data": { + "image/png": "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U6dOVfWrQmZz5QQymdk1ko4OeclJaEeOHFEnaxlJl4KbCQgIUB9Pnjxp2rZmzZp8+8iJbcbg261bN3z77bcqnBrHdj3ZR05Ku55xIQpjra+UbbRv3940drk/qe01+vvvv1UQb9WqFUqLjEv6ERMRlQRLGoiIStGwYcPUog1Sxyqzu1LvKidxSW2qnKQlnQakvCEvCY9SCtGrVy+MHTsWFy5cUF0QCpvtNJYgSKmE3K98jczizpw5M98+Ulcs3SD69eunSgxkdllmSRs1alTgfcqJctKBQTpGSLlB3vAtM8oymy0zxtI9QepxhXSVkNpjaR0m7dikS4N0gZCT0QoqnSiJffv2qbIP6cNLRFQSnOElIiohCXtdunTJt01C6pdffqlOvJIQKh0IpMxBWpZJjW/Xrl1VP9n7778/39fJyWwvvvii6k8r9yGruEmbLmlPVtjCE7///rsKnsYT4NauXav2MX6NLNgwadIkNQ6ZIZUeu3K/xhPQriczqLJghjE4S+iV+5OZY2kPJo8jdcByHzJrnDdYf/DBB2qWV/rsSiiW60bG+7n+hDgJ7MZQXNg+QjpASLC/vgaYiKiouLQwERGZ7N27F3/88QemTJliEUdFTlaTE+3kjwiZWSYiKgkGXiIiIiLSNZY0EBEREZGuMfASERERka4x8BIRERGRrjHwEhEREZGuMfASERERka4x8BIRERGRrjHwEhEREZGuMfASERERka4x8BIRERGRrjHwEhEREZGuMfASERERka4x8BIRERGRrjHwEhEREZGuMfASERERka4x8BIRERGRrjHwEhERERH07P8B97Mxaw9WedoAAAAASUVORK5CYII=", + "text/plain": "

      " + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "radii_bohr = [10 ** (-2 + 3 * index / 300) for index in range(301)]\n", + "figure, axis = plt.subplots(figsize=(7.0, 4.2))\n", + "for symbol in (\"C\", \"O\"):\n", + " profile = ar.get_proatomic_density_profile(symbol)\n", + " assert profile is not None\n", + " values = [profile(radius, radius_unit=\"bohr\") for radius in radii_bohr]\n", + " axis.plot(radii_bohr, values, label=symbol)\n", + "axis.axhline(ar.PROATOMIC_TAIL_CUTOFF, color=\"black\", linestyle=\"--\", label=\"pairwise cutoff\")\n", + "axis.set(xlabel=\"Radius (bohr)\", ylabel=r\"Density (electron/bohr$^3$)\", title=\"Neutral spherical proatomic densities\")\n", + "axis.set_yscale(\"log\")\n", + "axis.set_ylim(1e-8, None)\n", + "axis.legend()\n", + "figure.tight_layout()\n", + "print(\"Plotted C and O profiles from 0.01 through 10 bohr.\")\n", + "figure" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Ordinary heteronuclear pair: boundary and minimum\n", + "\n", + "For C–O at 1.5 bohr the fixed cutoff contours overlap. The stable default `boundary` balances the two proatomic contributions; the optional `minimum` instead selects a resolved valley in their sum. The distinct coordinates are expected because the modes answer different questions." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "boundary: method=equal_proatom_density; status=ok; x_A=0.556215252014 bohr; rho_sum=0.828138349302\n", + " minimum: method=promolecular_density_minimum; status=ok; x_A=0.611760239317 bohr; rho_sum=0.801643412838\n", + "coordinate difference: 0.055544987303 bohr\n", + "cutoff=0.0001 electron/bohr^3; regime=overlap\n" + ] + } + ], + "source": [ + "atom_a, atom_b, distance = \"C\", \"O\", 1.5\n", + "boundary = ar.estimate_proatomic_boundary(atom_a, atom_b, distance, distance_unit=\"bohr\")\n", + "minimum = ar.estimate_promolecular_density_minimum(atom_a, atom_b, distance, distance_unit=\"bohr\")\n", + "dispatched = ar.estimate_ias_position(atom_a, atom_b, distance, mode=\"boundary\", distance_unit=\"bohr\")\n", + "assert boundary is not None and minimum is not None and dispatched == boundary\n", + "\n", + "for label, result in ((\"boundary\", boundary), (\"minimum\", minimum)):\n", + " print(f\"{label:>8}: method={result.method}; status={result.status}; x_A={result.position_from_a:.12f} bohr; rho_sum={result.rho_sum:.12g}\")\n", + "print(f\"coordinate difference: {abs(boundary.position_from_a - minimum.position_from_a):.12f} bohr\")\n", + "print(f\"cutoff={boundary.cutoff_density:g} {boundary.density_unit}; regime={boundary.cutoff_regime}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Component and summed line densities\n", + "\n", + "The dashed boundary line crosses where the C and O contributions are equal. The dotted minimum line sits at the valley of their sum, so it need not divide the component densities equally. Coordinates are measured from atom A (C) toward atom B (O). Swapping the pair maps each coordinate `x` to `R - x` and exchanges the C/O component fields." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The component crossing and summed-density minimum are marked separately.\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": "
      " + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "profile_a = ar.get_proatomic_density_profile(atom_a)\n", + "profile_b = ar.get_proatomic_density_profile(atom_b)\n", + "assert profile_a is not None and profile_b is not None\n", + "positions = [distance * index / 500 for index in range(501)]\n", + "rho_a_values = [profile_a(x, radius_unit=\"bohr\") for x in positions]\n", + "rho_b_values = [profile_b(distance - x, radius_unit=\"bohr\") for x in positions]\n", + "rho_sum_values = [left + right for left, right in zip(rho_a_values, rho_b_values)]\n", + "\n", + "figure, axis = plt.subplots(figsize=(7.0, 4.2))\n", + "axis.plot(positions, rho_a_values, label=r\"$\\rho_C(x)$\")\n", + "axis.plot(positions, rho_b_values, label=r\"$\\rho_O(R-x)$\")\n", + "axis.plot(positions, rho_sum_values, label=\"sum\", linewidth=2)\n", + "axis.axvline(boundary.position_from_a, color=\"black\", linestyle=\"--\", label=\"boundary\")\n", + "axis.axvline(minimum.position_from_a, color=\"black\", linestyle=\":\", label=\"minimum\")\n", + "axis.set(xlabel=\"Position from C toward O (bohr)\", ylabel=r\"Density (electron/bohr$^3$)\", title=\"C–O component and summed line densities\")\n", + "axis.set_yscale(\"log\")\n", + "axis.legend()\n", + "figure.tight_layout()\n", + "print(\"The component crossing and summed-density minimum are marked separately.\")\n", + "figure" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exact homonuclear midpoint\n", + "\n", + "For identical atoms, exchange symmetry fixes both modes at exactly `R/2`; raw interpolation-scale shell structure is not allowed to choose an off-centre separator." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "O–O boundary: method=homonuclear_midpoint; x_A=1.5 bohr\n", + "O–O minimum: method=homonuclear_midpoint; x_A=1.5 bohr\n" + ] + } + ], + "source": [ + "homonuclear_distance = 3.0\n", + "o_o_boundary = ar.estimate_proatomic_boundary(\"O\", \"O\", homonuclear_distance, distance_unit=\"bohr\")\n", + "o_o_minimum = ar.estimate_promolecular_density_minimum(\"O\", \"O\", homonuclear_distance, distance_unit=\"bohr\")\n", + "assert o_o_boundary is not None and o_o_minimum is not None\n", + "assert o_o_boundary.position_from_a == homonuclear_distance / 2\n", + "assert o_o_minimum.position_from_a == homonuclear_distance / 2\n", + "print(f\"O–O boundary: method={o_o_boundary.method}; x_A={o_o_boundary.position_from_a} bohr\")\n", + "print(f\"O–O minimum: method={o_o_minimum.method}; x_A={o_o_minimum.position_from_a} bohr\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Low-density gap\n", + "\n", + "At a long H–O separation the two `1e-4 electron/bohr^3` contours no longer overlap. Boundary mode returns the geometric midpoint of the contour gap. Minimum mode does not search unconstrained tails and explicitly returns a `low_density_gap` non-result." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "boundary: method=cutoff_gap_midpoint; status=low_density_gap; x_A=5.897488624776 bohr\n", + "minimum: method=none; status=low_density_gap; x_A=None\n", + "cutoff-contour separation: 3.538953155071 bohr\n" + ] + } + ], + "source": [ + "gap_boundary = ar.estimate_proatomic_boundary(\"H\", \"O\", 12.0, distance_unit=\"bohr\")\n", + "gap_minimum = ar.estimate_promolecular_density_minimum(\"H\", \"O\", 12.0, distance_unit=\"bohr\")\n", + "assert gap_boundary is not None and gap_minimum is not None\n", + "print(f\"boundary: method={gap_boundary.method}; status={gap_boundary.status}; x_A={gap_boundary.position_from_a:.12f} bohr\")\n", + "print(f\"minimum: method={gap_minimum.method}; status={gap_minimum.status}; x_A={gap_minimum.position_from_a}\")\n", + "print(f\"cutoff-contour separation: {gap_boundary.contour_separation:.12f} bohr\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## No resolved interior minimum\n", + "\n", + "A short H–U example shows another valid diagnostic result. The cutoff-bounded search finds no strict-interior valley resolved at the declared `0.01 bohr` scale, so minimum mode returns typed status and provenance without inventing a coordinate or falling back to boundary mode." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "method=none; status=no_resolved_interior_minimum; x_A=None\n", + "resolution=0.01 bohr; converged=True; passes=2\n", + "dataset=pbe0_sfx2c_dyallv4z_h-lr_neutral_v2\n", + "interpolation=loglog_positive_bracketed_v1\n", + "pairwise contract=neutral_proatom_pairwise_cutoff_1e-4_resolution_0.01_v1\n" + ] + } + ], + "source": [ + "unresolved = ar.estimate_promolecular_density_minimum(\"H\", \"U\", 1.58, distance_unit=\"bohr\")\n", + "assert unresolved is not None\n", + "print(f\"method={unresolved.method}; status={unresolved.status}; x_A={unresolved.position_from_a}\")\n", + "print(f\"resolution={unresolved.search_resolution} bohr; converged={unresolved.search_converged}; passes={unresolved.search_passes}\")\n", + "print(f\"dataset={unresolved.dataset_id}\")\n", + "print(f\"interpolation={unresolved.interpolation_contract}\")\n", + "print(f\"pairwise contract={unresolved.pairwise_contract}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mode selection, statuses, and limitations\n", + "\n", + "Use `boundary`—the dispatcher default—for a stable neutral-proatom divider in geometry, Voronoi/Laguerre calibration, or related reference-atom workflows. Use `minimum` only when a cutoff-bounded promolecular line-density valley is the intended proxy and a resolution-limited non-result is acceptable. Minimum mode never silently falls back to boundary mode.\n", + "\n", + "Pair distances must satisfy `0 < R <= 20 bohr`; distance and density units are independent, and coordinates are reported from atom A toward atom B. Reversing A/B maps primary and alternative coordinates `x` to `R - x`, swaps component fields, and relabels dominance; orientation-independent status and diagnostics remain equivalent. The fixed per-atom cutoff is exactly `1e-4 electron/bohr^3`. The practical minimum resolution is `0.01 bohr`; sub-resolution minima are deliberately coalesced or rejected. Inspect `method`, `status`, `position_from_a`, `dominant_atom`, `ambiguous`, and search diagnostics instead of assuming every valid request has a coordinate. Explicit statuses include `low_density_gap`, `one_atom_dominates`, `no_resolved_interior_minimum`, `boundary_dominated`, `ambiguous_competing_minima`, and `search_unstable`.\n", + "\n", + "The profiles cover neutral H–Lr only and are defined by the recorded PBE0/sf-X2C/dyall-v4z method, finite radial grid, sphericalization, and interpolation contract. There are no ions, vectorized or grid-density APIs, environment-dependent atoms, molecular densities, or exact QTAIM surfaces in this release." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index fea1cba..f1d096d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,9 @@ test = [ "pytest>=7", "tomli>=2; python_version < '3.11'", ] +notebook = [ + "matplotlib>=3.8", +] docs = [ "mkdocs>=1.6,<2", "mkdocs-material>=9.5", diff --git a/src/atomref/proatoms.py b/src/atomref/proatoms.py index bd7a4ca..9d88e8d 100644 --- a/src/atomref/proatoms.py +++ b/src/atomref/proatoms.py @@ -60,6 +60,11 @@ class IASPositionResult: ``density_unit``. A valid but scientifically non-applicable request uses ``method="none"`` and leaves its coordinate fields as :data:`None`. + Reversing the atoms maps primary and alternative coordinates ``x`` to + ``R - x``, swaps A/B component fields, and relabels dominance. Method, + status, summed density, cutoff regime, and orientation-independent search + diagnostics remain equivalent under that relabeling. + ``boundary_dominated`` takes status precedence over ``search_unstable``, which takes precedence over ``ambiguous_competing_minima``. The separate ``search_converged`` and ``ambiguous`` fields preserve all diagnostics. diff --git a/tests/meta/test_notebooks.py b/tests/meta/test_notebooks.py index 092b28c..350a46e 100644 --- a/tests/meta/test_notebooks.py +++ b/tests/meta/test_notebooks.py @@ -18,6 +18,7 @@ def test_notebook_files_exist() -> None: "02-policies-and-assessment.ipynb", "03-custom-sets-and-discovery.ipynb", "04-ias-method-selection-study.ipynb", + "05-proatomic-density-and-ias.ipynb", } actual = {path.name for path in NOTEBOOKS.glob("*.ipynb")} assert expected.issubset(actual) diff --git a/tests/meta/test_readme_sync.py b/tests/meta/test_readme_sync.py index fe56ac2..21e965e 100644 --- a/tests/meta/test_readme_sync.py +++ b/tests/meta/test_readme_sync.py @@ -17,4 +17,32 @@ def test_readme_is_in_sync(tmp_path: Path) -> None: cwd=REPO_ROOT, check=True, ) - assert generated.read_text(encoding='utf-8') == README.read_text(encoding='utf-8') + assert generated.read_bytes() == README.read_bytes() + assert b'\r' not in generated.read_bytes() + + +def test_readme_check_rejects_crlf_output(tmp_path: Path) -> None: + generated = tmp_path / 'README.generated.md' + subprocess.run( + [sys.executable, str(SCRIPT), '--output', str(generated)], + cwd=REPO_ROOT, + check=True, + ) + generated.write_bytes(generated.read_bytes().replace(b'\n', b'\r\n')) + + result = subprocess.run( + [ + sys.executable, + str(SCRIPT), + '--output', + str(generated), + '--check', + ], + cwd=REPO_ROOT, + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 1 + assert 'out of sync' in result.stderr diff --git a/tools/README.md b/tools/README.md index 912295e..7e3649a 100644 --- a/tools/README.md +++ b/tools/README.md @@ -11,9 +11,10 @@ release preparation. access. - `check_dist.py` — verify that wheel and source-distribution artifacts contain the key files expected by the project. -- `check_notebooks.py` — validate notebook JSON and execute notebook code cells. -- `check_registry.py` — validate curated registry metadata against packaged CSV - tables. +- `check_notebooks.py` — validate notebook JSON, release-notebook saved state, + required Markdown and outputs, then execute notebook code cells headlessly. +- `check_registry.py` — validate curated registry metadata against every + packaged scalar and radial payload. - `export_notebooks.py` — render the bundled notebooks into Markdown pages under `docs/notebooks/`. - `gen_readme.py` — regenerate `README.md` from `docs/index.md`. diff --git a/tools/check_notebooks.py b/tools/check_notebooks.py index 7844111..42bed06 100644 --- a/tools/check_notebooks.py +++ b/tools/check_notebooks.py @@ -3,11 +3,14 @@ from __future__ import annotations -from contextlib import redirect_stdout +from contextlib import redirect_stderr, redirect_stdout import io import json +import os from pathlib import Path +import shutil import sys +import tempfile REPO_ROOT = Path(__file__).resolve().parents[1] @@ -21,8 +24,39 @@ "02-policies-and-assessment.ipynb", "03-custom-sets-and-discovery.ipynb", "04-ias-method-selection-study.ipynb", + "05-proatomic-density-and-ias.ipynb", ) +RELEASE_NOTEBOOK_REQUIREMENTS = { + "04-ias-method-selection-study.ipynb": { + "markdown": ( + "# Choosing a practical neutral-proatom IAS proxy", + "## Numerical policy examined here", + "## Representative pairwise results", + "## Why homonuclear symmetry overrides raw shell minima", + "## Decision recorded by this study", + ), + "output_cells": (6, 8, 10, 12, 14, 16), + "plot_cells": (), + }, + "05-proatomic-density-and-ias.ipynb": { + "markdown": ( + "# Neutral proatomic density and pairwise IAS-proxy workflows", + "## Dataset discovery and provenance", + "## Profile retrieval and scalar evaluation", + "## Radial-density profiles", + "## Ordinary heteronuclear pair: boundary and minimum", + "## Component and summed line densities", + "## Exact homonuclear midpoint", + "## Low-density gap", + "## No resolved interior minimum", + "## Mode selection, statuses, and limitations", + ), + "output_cells": (3, 5, 7, 9, 11, 13, 15, 17, 19), + "plot_cells": (9, 13), + }, +} + class NotebookCheckError(RuntimeError): """Raised when a notebook is malformed or fails to execute.""" @@ -43,6 +77,17 @@ def load_notebook(path: Path) -> dict[str, object]: return data +def _cell_source(cell: dict[str, object], *, path: Path, index: int) -> str: + """Return normalized source text for one notebook cell.""" + + source = cell.get("source", []) + if isinstance(source, str): + return source + if isinstance(source, list) and all(isinstance(line, str) for line in source): + return "".join(source) + raise NotebookCheckError(f"{path.name}: cell {index} has invalid source") + + def iter_code_cells(data: dict[str, object], *, path: Path) -> tuple[str, ...]: """Return notebook code-cell sources in order.""" @@ -57,34 +102,124 @@ def iter_code_cells(data: dict[str, object], *, path: Path) -> tuple[str, ...]: cell_type = cell.get("cell_type") if cell_type != "code": continue - source = cell.get("source", []) - if isinstance(source, str): - text = source - elif isinstance(source, list) and all(isinstance(line, str) for line in source): - text = "".join(source) - else: - raise NotebookCheckError( - f"{path.name}: cell {index} has invalid code source" - ) - code.append(text) + code.append(_cell_source(cell, path=path, index=index + 1)) if not code: raise NotebookCheckError(f"{path.name}: contains no code cells") return tuple(code) +def validate_saved_release_notebook( + data: dict[str, object], + *, + path: Path, +) -> None: + """Validate the small saved-state contract for release notebooks.""" + + requirements = RELEASE_NOTEBOOK_REQUIREMENTS.get(path.name) + if requirements is None: + return + if data.get("nbformat") != 4: + raise NotebookCheckError(f"{path.name}: expected notebook format 4") + + cells = data.get("cells") + if not isinstance(cells, list): + raise NotebookCheckError(f"{path.name}: missing notebook cell list") + + markdown_parts: list[str] = [] + execution_counts: list[int] = [] + for index, cell in enumerate(cells, start=1): + if not isinstance(cell, dict): + raise NotebookCheckError(f"{path.name}: cell {index} is not an object") + source = _cell_source(cell, path=path, index=index) + if cell.get("cell_type") == "markdown": + markdown_parts.append(source) + continue + if cell.get("cell_type") != "code" or not source.strip(): + continue + + count = cell.get("execution_count") + if isinstance(count, bool) or not isinstance(count, int) or count <= 0: + raise NotebookCheckError( + f"{path.name}: nonempty code cell {index} lacks an execution count" + ) + execution_counts.append(count) + + if index == 1 or not isinstance(cells[index - 2], dict): + raise NotebookCheckError( + f"{path.name}: code cell {index} lacks preceding Markdown" + ) + previous = cells[index - 2] + if previous.get("cell_type") != "markdown" or not _cell_source( + previous, + path=path, + index=index - 1, + ).strip(): + raise NotebookCheckError( + f"{path.name}: code cell {index} lacks preceding Markdown" + ) + + outputs = cell.get("outputs") + if not isinstance(outputs, list): + raise NotebookCheckError(f"{path.name}: cell {index} has invalid outputs") + if any( + isinstance(output, dict) and output.get("output_type") == "error" + for output in outputs + ): + raise NotebookCheckError(f"{path.name}: cell {index} saved an error") + + expected_counts = list(range(1, len(execution_counts) + 1)) + if execution_counts != expected_counts: + raise NotebookCheckError( + f"{path.name}: execution counts are not sequential from 1" + ) + + markdown = "\n".join(markdown_parts) + missing_markdown = [ + heading for heading in requirements["markdown"] if heading not in markdown + ] + if missing_markdown: + raise NotebookCheckError( + f"{path.name}: missing required Markdown: {', '.join(missing_markdown)}" + ) + + for cell_number in requirements["output_cells"]: + cell = cells[cell_number - 1] + outputs = cell.get("outputs") if isinstance(cell, dict) else None + if not isinstance(outputs, list) or not outputs: + raise NotebookCheckError( + f"{path.name}: expected saved output in cell {cell_number}" + ) + + for cell_number in requirements["plot_cells"]: + cell = cells[cell_number - 1] + outputs = cell.get("outputs") if isinstance(cell, dict) else None + has_png = isinstance(outputs, list) and any( + isinstance(output, dict) + and output.get("output_type") == "display_data" + and isinstance(output.get("data"), dict) + and "image/png" in output["data"] + for output in outputs + ) + if not has_png: + raise NotebookCheckError( + f"{path.name}: expected an embedded PNG in cell {cell_number}" + ) + + def execute_notebook(path: Path) -> None: """Execute all code cells from one notebook in a shared namespace.""" if not path.exists(): raise NotebookCheckError(f"missing notebook: {path}") data = load_notebook(path) + validate_saved_release_notebook(data, path=path) namespace = {"__name__": "__main__"} for index, source in enumerate(iter_code_cells(data, path=path), start=1): if not source.strip(): continue try: code = compile(source, f"{path.name}::cell{index}", "exec") - with redirect_stdout(io.StringIO()): + with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()): exec(code, namespace, namespace) except Exception as exc: # noqa: BLE001 raise NotebookCheckError( @@ -96,8 +231,33 @@ def main() -> int: """Validate and execute every required notebook.""" notebooks = iter_notebooks() - for notebook in notebooks: - execute_notebook(notebook) + previous_cwd = Path.cwd() + previous_backend = os.environ.get("MPLBACKEND") + previous_config = os.environ.get("MPLCONFIGDIR") + with tempfile.TemporaryDirectory(prefix="atomref-notebooks-") as tmp: + isolated_root = Path(tmp) + shutil.copytree(SRC, isolated_root / "src") + matplotlib_config = isolated_root / "matplotlib" + matplotlib_config.mkdir() + try: + os.environ["MPLBACKEND"] = "Agg" + os.environ["MPLCONFIGDIR"] = str(matplotlib_config) + os.chdir(isolated_root) + for notebook in notebooks: + execute_notebook(notebook) + pyplot = sys.modules.get("matplotlib.pyplot") + if pyplot is not None: + pyplot.close("all") + finally: + os.chdir(previous_cwd) + if previous_backend is None: + os.environ.pop("MPLBACKEND", None) + else: + os.environ["MPLBACKEND"] = previous_backend + if previous_config is None: + os.environ.pop("MPLCONFIGDIR", None) + else: + os.environ["MPLCONFIGDIR"] = previous_config print(f"Validated {len(notebooks)} notebook(s).") return 0 diff --git a/tools/check_registry.py b/tools/check_registry.py index 4072e42..63d9a01 100644 --- a/tools/check_registry.py +++ b/tools/check_registry.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Validate packaged registry metadata against bundled data tables.""" +"""Validate packaged registry metadata against bundled data payloads.""" from __future__ import annotations diff --git a/tools/gen_readme.py b/tools/gen_readme.py index 8ce9259..b4de3ed 100644 --- a/tools/gen_readme.py +++ b/tools/gen_readme.py @@ -79,15 +79,15 @@ def main() -> int: ) args = parser.parse_args() - rendered = render_readme() + rendered = render_readme().encode("utf-8") if args.check: - current = args.output.read_text(encoding="utf-8") + current = args.output.read_bytes() if current != rendered: print(f"{args.output} is out of sync with docs/index.md", file=sys.stderr) return 1 return 0 - args.output.write_text(rendered, encoding="utf-8") + args.output.write_bytes(rendered) return 0 From 1fba665a41d5a7b2ee97d27701347113b277cd4f Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Tue, 14 Jul 2026 14:51:28 +0300 Subject: [PATCH 08/23] chore: prepare atomref 0.2.0 --- .github/workflows/ci.yml | 23 +++++++++++++-- CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++ pyproject.toml | 15 ++++++++-- src/atomref/__about__.py | 2 +- tests/meta/test_imports.py | 1 + tests/meta/test_release_tools.py | 19 +++++++++++++ tests/test_smoke.py | 5 ++-- tools/check_dist.py | 39 ++++++++++++++++++++++++++ tools/release_check.py | 20 ++++++++++--- 9 files changed, 160 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f00ac0..9d88481 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - name: Install lint dependencies run: | python -m pip install --upgrade pip - python -m pip install .[dev] + python -m pip install .[dev,notebook] - name: Lint run: flake8 src tests tools - name: Validate packaged registry @@ -58,7 +58,7 @@ jobs: - name: Install run: | python -m pip install --upgrade pip - python -m pip install .[test] + python -m pip install .[test,notebook] - name: Test run: pytest @@ -89,4 +89,23 @@ jobs: assert ar.get_vdw_radius('C') == 1.77 assert 'atomic_radius' in ar.list_quantities() assert 'rahm2016' in ar.list_dataset_ids('atomic_radius', usage_role='support') + ref = ar.DatasetRef( + 'proatomic_density', + 'pbe0_sfx2c_dyallv4z_h-lr_neutral_v2', + ) + dataset = ar.get_builtin_set(ref) + assert dataset.get('O') is not None + rho = ar.get_proatomic_density( + 'O', + 0.75, + radius_unit='angstrom', + density_unit='electron/bohr^3', + ) + assert rho is not None and rho > 0 + boundary = ar.estimate_proatomic_boundary('C', 'O', 1.43) + assert boundary is not None and boundary.position_from_a is not None + minimum = ar.estimate_promolecular_density_minimum('C', 'O', 1.43) + assert minimum is not None + selected = ar.estimate_ias_position('C', 'O', 1.43, mode='boundary') + assert selected == boundary PY diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d2c3a..bc6cc39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,53 @@ # Changelog +## 0.2.0 - 2026-07-14 + +### Added + +- A packaged, immutable neutral H–Lr spherical proatomic-density dataset derived + reproducibly from `atomref-proatoms` 2.0.0 dataset + `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`, with exact source, basis, license, + hash, and DOI metadata. +- Cached profile retrieval and dependency-free scalar density evaluation with + independent radius and density units, positive-region log–log interpolation, + and a strict 0–20 bohr public radius domain. +- `estimate_proatomic_boundary()` for the stable neutral-proatom divider and + `estimate_promolecular_density_minimum()` for the optional cutoff-bounded, + resolution-limited promolecular line-density minimum proxy. +- `estimate_ias_position()` with explicit `boundary` and `minimum` modes; + `boundary` is the default and minimum mode never silently falls back to it. +- Immutable `IASPositionResult` values with coordinates, explicit diagnostic + statuses, component densities, cutoff/search information, units, and + numerical/data provenance. +- Executed method-selection and feature notebooks with saved outputs and plots. + +### Changed + +- `get_builtin_set()` now dispatches both scalar CSV and shared-grid radial ZIP + datasets through the same registry machinery. Existing scalar policies, + radii values, X–H behavior, and `0.1.x` APIs remain unchanged. +- The package now identifies itself as version `0.2.0` and includes proatomic + density, electron density, interatomic-surface, and IAS discovery keywords. + +### Documentation + +- Added the neutral proatomic-density and pairwise guide, complete + `atomref.proatoms` API reference, exact cutoff/range/unit/status guidance, and + links to the saved release notebooks. +- Updated the home page and architecture description for radial datasets and + the accepted two-mode pairwise API without performing the broader planned + documentation redesign. + +### Packaging + +- Included the deterministic proatomic-density ZIP in wheels and source + distributions and added independent content validation. +- Added an optional `notebook` extra for Matplotlib; runtime dependencies remain + empty. +- Extended CI, notebook checks, distribution-content checks, release checks, + and clean-wheel smoke tests for density evaluation, both pairwise modes, and + dispatcher equivalence. + ## 0.1.4 - 2026-03-15 ### Added diff --git a/pyproject.toml b/pyproject.toml index f1d096d..32d0bbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,14 +5,24 @@ build-backend = "hatchling.build" [project] name = "atomref" dynamic = ["version"] -description = "Curated atomic reference data and transfer policies for geometry and structure-analysis algorithms." +description = "Curated atomic reference data, proatomic densities, and transfer policies for structure algorithms." readme = "README.md" requires-python = ">=3.10" license = { file = "LICENSE" } authors = [ { name = "Ivan Yu. Chernyshov", email = "ivan.chernyshoff@gmail.com" } ] -keywords = ["chemistry", "materials", "crystallography", "reference data", "atomic radii"] +keywords = [ + "chemistry", + "materials", + "crystallography", + "reference data", + "atomic radii", + "proatomic density", + "electron density", + "interatomic surfaces", + "IAS", +] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", @@ -77,6 +87,7 @@ include = [ "/docs", "/tools", "/notebooks", + "/.flake8", "/mkdocs.yml", "/README.md", "/CHANGELOG.md", diff --git a/src/atomref/__about__.py b/src/atomref/__about__.py index bbab024..d3ec452 100644 --- a/src/atomref/__about__.py +++ b/src/atomref/__about__.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.2.0" diff --git a/tests/meta/test_imports.py b/tests/meta/test_imports.py index 66210e7..e3bca9a 100644 --- a/tests/meta/test_imports.py +++ b/tests/meta/test_imports.py @@ -9,6 +9,7 @@ 'atomref.registry', 'atomref.transfer', 'atomref.policy', + 'atomref.proatoms', 'atomref.radii', 'atomref.xh', ] diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index 1179ca8..425418a 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -43,6 +43,25 @@ def test_dist_check_accepts_pinned_proatomic_snapshot() -> None: ) +def test_dist_check_requires_release_tools_and_legacy_data() -> None: + assert "atomref/data/xh_bond_length.csv" in check_dist.REQUIRED_WHEEL_MEMBERS + assert "src/atomref/data/xh_bond_length.csv" in ( + check_dist.REQUIRED_SDIST_SUFFIXES + ) + assert ".flake8" in check_dist.REQUIRED_SDIST_SUFFIXES + assert "tools/check_registry.py" in check_dist.REQUIRED_SDIST_SUFFIXES + assert "tools/check_dist.py" in check_dist.REQUIRED_SDIST_SUFFIXES + + +def test_sdist_root_readme_cannot_be_satisfied_by_tools_readme() -> None: + with pytest.raises(check_dist.DistCheckError, match="root-level 'README.md'"): + check_dist._sdist_root_member( + {"atomref-0.2.0/tools/README.md"}, + "README.md", + label="test sdist", + ) + + def test_dist_check_rejects_changed_snapshot_archive() -> None: payload = SNAPSHOT_PATH.read_bytes() + b"altered" diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 6a96b08..17ae6b6 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -3,9 +3,8 @@ import atomref as ar -def test_version_is_present() -> None: - assert isinstance(ar.__version__, str) - assert ar.__version__ +def test_release_version() -> None: + assert ar.__version__ == '0.2.0' def test_basic_smoke_import_and_lookup() -> None: diff --git a/tools/check_dist.py b/tools/check_dist.py index 2bd7d31..85c9c68 100644 --- a/tools/check_dist.py +++ b/tools/check_dist.py @@ -15,6 +15,7 @@ "atomref/data/covalent.csv", "atomref/data/proatomic_density_neutral.zip", "atomref/data/van_der_waals.csv", + "atomref/data/xh_bond_length.csv", "atomref/data/registry.json", "atomref/py.typed", "dist-info/licenses/NOTICE.md", @@ -25,6 +26,7 @@ "src/atomref/data/covalent.csv", "src/atomref/data/proatomic_density_neutral.zip", "src/atomref/data/van_der_waals.csv", + "src/atomref/data/xh_bond_length.csv", "src/atomref/data/registry.json", "src/atomref/py.typed", "README.md", @@ -33,14 +35,23 @@ "LICENSE", "NOTICE.md", "pyproject.toml", + ".flake8", "notebooks/01-quickstart.ipynb", "notebooks/02-policies-and-assessment.ipynb", "notebooks/03-custom-sets-and-discovery.ipynb", + "notebooks/04-ias-method-selection-study.ipynb", + "notebooks/05-proatomic-density-and-ias.ipynb", "docs/notebooks/01-quickstart.md", "docs/notebooks/02-policies-and-assessment.md", "docs/notebooks/03-custom-sets-and-discovery.md", + "docs/guide/notebooks.md", + "docs/guide/proatomic_density.md", + "docs/dev/ias_method_selection.md", + "docs/api/proatoms.md", "tools/build_proatomic_density_snapshot.py", "tools/check_notebooks.py", + "tools/check_registry.py", + "tools/check_dist.py", "tools/export_notebooks.py", "tools/gen_readme.py", "tools/release_check.py", @@ -61,6 +72,7 @@ EXPECTED_PROATOMIC_CSV_SHA256 = ( "8478da862233c8874e36d65bb5eb762cdb9cbcb0e0278733c0f425ae00c2dcfe" ) +REPO_ROOT = Path(__file__).resolve().parents[1] class DistCheckError(RuntimeError): @@ -107,6 +119,21 @@ def _member_matching_suffix( return matches[0] +def _sdist_root_member(actual: set[str], filename: str, *, label: str) -> str: + """Return one file directly below the sdist's generated root directory.""" + + matches = sorted( + name + for name in actual + if name.count("/") == 1 and name.rsplit("/", 1)[-1] == filename + ) + if len(matches) != 1: + raise DistCheckError( + f"{label} must contain exactly one root-level {filename!r}" + ) + return matches[0] + + def _assert_attribution(text: str, *, member: str, label: str) -> None: """Raise when a packaged metadata file omits required data attribution.""" @@ -196,6 +223,18 @@ def check_sdist(path: Path) -> None: matched = _members_matching_suffixes(names, REQUIRED_SDIST_SUFFIXES) _assert_members_present(matched, REQUIRED_SDIST_SUFFIXES, label=path.name) + readme_member = _sdist_root_member(names, "README.md", label=path.name) + readme_file = tf.extractfile(readme_member) + if readme_file is None: + raise DistCheckError( + f"{path.name} member {readme_member!r} is not a regular file" + ) + if readme_file.read() != (REPO_ROOT / "README.md").read_bytes(): + raise DistCheckError( + f"{path.name} member {readme_member!r} does not exactly match " + "the source README.md" + ) + snapshot_member = _member_matching_suffix( names, "src/atomref/data/proatomic_density_neutral.zip", diff --git a/tools/release_check.py b/tools/release_check.py index e71d4e5..29f436d 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -62,14 +62,26 @@ def _smoke_test_wheel() -> None: "assert 'atomic_radius' in ar.list_quantities(); " "assert 'rahm2016' in ar.list_dataset_ids(" "'atomic_radius', usage_role='support'); " + "ref = ar.DatasetRef(" + "'proatomic_density', " + "'pbe0_sfx2c_dyallv4z_h-lr_neutral_v2'); " + "dataset = ar.get_builtin_set(ref); " + "assert dataset.get('O') is not None; " + "rho = ar.get_proatomic_density(" + "'O', 0.75, radius_unit='angstrom', " + "density_unit='electron/bohr^3'); " + "assert rho is not None and rho > 0; " "boundary = ar.estimate_proatomic_boundary(" - "'C', 'O', 1.5, distance_unit='bohr'); " + "'C', 'O', 1.43); " "assert boundary is not None; " - "assert boundary.method == 'equal_proatom_density'; " + "assert boundary.position_from_a is not None; " "minimum = ar.estimate_promolecular_density_minimum(" - "'C', 'O', 1.5, distance_unit='bohr'); " + "'C', 'O', 1.43); " "assert minimum is not None; " - "assert minimum.requested_mode == 'minimum'" + "assert minimum.requested_mode == 'minimum'; " + "selected = ar.estimate_ias_position(" + "'C', 'O', 1.43, mode='boundary'); " + "assert selected == boundary" ), ) From 3490e348b6a3824bfaa0e60c3cda88f82595106c Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Tue, 14 Jul 2026 22:49:37 +0300 Subject: [PATCH 09/23] docs: update dev plan for 0.2.1 version --- DEV_PLAN.md | 363 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 274 insertions(+), 89 deletions(-) diff --git a/DEV_PLAN.md b/DEV_PLAN.md index e36183c..5edee75 100644 --- a/DEV_PLAN.md +++ b/DEV_PLAN.md @@ -2446,77 +2446,163 @@ chore: prepare atomref 0.2.0 Do not start these stages until `0.2.0` is accepted. -### Stage 6 — Notebook infrastructure and content overhaul +### Stage 6 — Complete API docs, adoption-oriented README, notebook documentation, packaging, and `0.2.1` release #### Goal -Make notebooks true executable documentation with saved, verifiable rich -outputs while reducing duplicate files and tools. +Make the package immediately understandable and attractive to both new users and +developers while preserving accurate scientific claims and the lightweight +runtime. + +This stage is a documentation, packaging, and release-hardening stage. It MUST +NOT change density values, cutoff radii, pairwise mode semantics, selected +coordinates, or other accepted scientific behavior except to fix a confirmed +defect with an explicit changelog entry and regression test. + +#### Documentation and adoption principle + +The README and documentation home page are the main adoption surface for a +developer deciding whether to implement the required atomic-reference logic +independently or use `atomref`. + +They must be technically accurate, but accuracy alone is not sufficient. A new +visitor should be able to determine quickly: + +- what practical problems `atomref` solves; +- which capabilities are available now; +- why the package is preferable to embedding ad hoc constants, datasets, and + fallback logic in another project; +- that the core runtime remains small and dependency-free; +- how to install it; +- how to perform one useful operation immediately; +- where to find detailed provenance, scientific assumptions, limitations, and + API documentation. + +Use an adoption-oriented or marketability-aware presentation without hype, +unsupported comparison, or concealment of limitations. Lead with user problems, +working examples, and concrete benefits. Move internal terminology, complete +dataset inventories, nested-policy mechanics, and detailed caveats below the +first-use path or into dedicated documentation. + +#### Notebook policy and direct rendering + +Notebooks are narrative examples, how-to material, and occasionally +supporting-information-style analyses. They are not the canonical +implementation or a release-defining exact-reproducibility layer. + +Replace the current generated-Markdown notebook path with direct rendering of +the actual `.ipynb` files through `mkdocs-jupyter`. + +The direct-rendering design MUST: + +- render committed notebook Markdown, code, saved text output, and saved plots + inside the MkDocs site; +- render ordinary Jupyter inline and display mathematics, including `$...$` and + `$$...$$`, under the current Material theme; +- use the committed outputs during documentation builds rather than executing + notebooks as part of `mkdocs build`; +- keep only one copy of each notebook and one documentation representation; +- add no staging, cache, generated-output, or parallel notebook directory to the + repository; +- remove generated `docs/notebooks/*.md` copies and the custom Markdown exporter; +- preserve direct links to the source notebook where useful; +- avoid an output-freshness, normalized-output, or byte-for-byte reproducibility + contract. + +Because MkDocs pages normally belong under the configured documentation source, +the preferred file layout is to move the actual notebooks from the root +`notebooks/` directory into the existing `docs/notebooks/` directory, replacing +the generated Markdown files there. Do not retain duplicate root-level notebook +copies, symlinks, or a copy step. If a simpler supported configuration renders +the existing root-level notebooks directly without duplication or staging, that +configuration MAY be used instead. + +Configure `mkdocs-jupyter` with notebook execution disabled for ordinary and +strict documentation builds. The built site must therefore reflect the outputs +committed in the notebook files and must not silently rewrite them. + +Notebook code should still receive one separate smoke-execution check using +standard Jupyter tooling such as `nbclient` or `nbconvert`, not the current +custom JSON-plus-`exec()` approximation. Execute temporary copies or write the +results only to a temporary output directory, fail on notebook exceptions, and +discard the produced notebooks after the check. Do not compare generated +outputs with committed outputs and do not modify the committed notebooks. + +The smoke check should run once on the primary CI Python version, not through +every Python version in the ordinary test matrix. A small wrapper in `tools/` +MAY remain when it makes the command portable and keeps `release_check.py` +simple, but it should delegate execution to standard Jupyter tooling rather +than implement notebook semantics itself. + +All direct notebook dependencies MUST be declared in the existing `notebook` +optional-dependency group, including `mkdocs-jupyter`, the execution tool used by +the smoke check, `matplotlib`, and any kernel package explicitly required by the +repository. The same dependency entries MUST be duplicated in `all`. They MUST +NOT become core runtime dependencies. The documentation workflow should install +both `docs` and `notebook` extras. + +If a focused local prototype demonstrates that `mkdocs-jupyter` cannot render +inline and display mathematics correctly with the current Material theme, stop +that implementation and use standard `nbconvert` output as the fallback. Do not +maintain both rendering systems. The fallback must still remove the bespoke +exporter, use one standard conversion path, and avoid exact output-freshness +requirements. + +Notebook prose or stored outputs should be changed only when needed to fix a +broken, stale, or misleading user-facing example discovered during the +transition or documentation audit. #### Required changes -1. Add notebook/documentation optional dependencies: - - `nbformat`; - - `nbclient` or `nbconvert`; - - `ipykernel`; - - `matplotlib`; - - `mkdocs-jupyter`. -2. Rewrite one existing notebook tool to support execution/update/check modes. -3. Execute and save all notebooks. -4. Add explanatory Markdown before every logical code section. -5. Render actual `.ipynb` files directly in MkDocs. -6. Remove generated `docs/notebooks/*.md`. -7. Remove `tools/export_notebooks.py`. -8. Remove export-sync tests and CI steps. -9. Add normalized execution-freshness checks. -10. Ensure plots render on the documentation site. - -#### Preferred command model - -```bash -python tools/check_notebooks.py --write -python tools/check_notebooks.py --check -``` - -The exact name may remain `check_notebooks.py` even though write mode is -supported, to avoid another file. - -#### Completion criteria - -- every notebook is executed and saved; -- re-execution reproduces committed normalized outputs; -- plots are visible in MkDocs; -- no generated Markdown notebook copies remain; -- notebook prose is suitable for learning, not only smoke testing; -- the number of notebook tools/files is reduced. - -#### Suggested commit - -```text -docs: make notebooks executable documentation -``` - ---- - -### Stage 7 — Complete API docs, README, and `0.2.1` release - -#### Goal - -Make the documentation immediately useful to both new users and developers. - -#### Required changes - -1. Configure `mkdocstrings` for full typed signatures and structured sections. +1. Configure `mkdocstrings` for complete typed signatures and structured + documentation sections. 2. Rewrite all intended public docstrings in one consistent style. -3. Verify every public parameter, return value, raised error, unit, and - important attribute appears in rendered API docs. -4. Review API page member selection. -5. Rewrite `docs/index.md` for product positioning and regenerate `README.md`. -6. Improve quickstart flow. -7. Remove `docs/dev/dev_plan.md` and its navigation item. -8. Ensure architecture and data-curation docs match the final code. -9. Update changelog and version to `0.2.1`. -10. Simplify CI commands after removing notebook Markdown export. +3. Verify every public parameter, return value, raised error, unit, valid range, + and important attribute appears correctly in rendered API documentation. +4. Review API-page member selection so every intended public function, class, + method, and dataclass is documented without exposing irrelevant internals. +5. Rewrite `docs/index.md` as a clear product introduction and regenerate + `README.md` from it. +6. Improve the quickstart path so installation and a useful first result appear + before detailed architecture or policy terminology. +7. Replace generated notebook Markdown with direct `.ipynb` rendering through + `mkdocs-jupyter`, following the notebook policy above. +8. Move the actual notebooks into the existing MkDocs source tree when required + for direct rendering, while retaining only one copy of each notebook. +9. Remove `tools/export_notebooks.py`, generated notebook Markdown files, + export-sync tests, and the corresponding CI/release-check commands. +10. Replace the custom `exec()`-based notebook check with one temporary + Jupyter-based smoke-execution path, or reduce the existing wrapper to a thin + delegation layer. +11. Verify that inline mathematics, display mathematics, saved text output, and + saved PNG plots render correctly in the strict MkDocs build. +12. Keep notebook execution disabled in `mkdocs-jupyter`; documentation builds + render committed outputs and do not rewrite source notebooks. +13. Add an `all` optional-dependency group containing the union of all + user-facing optional feature dependencies. Do not include test, lint, + packaging, upload, or release-only tooling in `all`. +14. Keep every notebook-related dependency in `notebook` and duplicate the same + dependency entries in `all`. Keep ordinary documentation dependencies in + `docs`, and install `.[docs,notebook]` for documentation builds. +15. Document at least the lightweight base installation, notebook-capable + installation, and complete user-facing installation: + + ```bash + pip install atomref + pip install "atomref[notebook]" + pip install "atomref[all]" + ``` + +16. Remove `docs/dev/dev_plan.md` and its navigation item so the root + `DEV_PLAN.md` remains the only development plan. +17. Ensure architecture, data-curation, notebook-overview, and scientific guide + pages match the final code, public names, dataset identity, units, cutoff, + mode semantics, and limitations. +18. Review package description, keywords, project links, and other public + metadata for consistency with the complete `0.2.x` feature set. +19. Update the changelog and version to `0.2.1`. +20. Validate the base installation, `atomref[notebook]`, and `atomref[all]` from + built artifacts in clean environments. #### API documentation acceptance @@ -2534,20 +2620,60 @@ For every public function/class added or retained: #### README acceptance -A new visitor should discover within the initial screen: - -- the package purpose; -- radii, profile, and IAS capabilities; -- the dependency-free runtime; -- a compact working example; -- provenance as a central value; -- links to installation, notebooks, datasets, and API. +A new visitor should discover within the initial screen or immediately following +it: + +- a concise statement of what `atomref` does; +- the concrete developer problems it solves; +- radii, X–H, proatomic-density, boundary, and IAS-proxy capabilities; +- the dependency-free core runtime; +- installation and a compact working example; +- explicit provenance as a central value; +- a concise explanation of why using `atomref` is preferable to maintaining + project-local constants and fallback rules; +- links to the quickstart, datasets/provenance, notebooks, scientific guidance, + and API reference. + +The opening MUST NOT require a newcomer to understand the internal registry, +policy, transfer, or dataset taxonomy before seeing the package's value and a +working example. Limitations must remain honest and visible, but should be +placed near the affected feature rather than overwhelming the first-use path. + +#### Notebook documentation acceptance + +- each notebook has exactly one maintained `.ipynb` source file; +- the actual notebooks appear directly in MkDocs navigation; +- inline and display mathematics render correctly; +- saved text outputs and PNG plots render correctly; +- documentation builds do not execute or modify notebooks; +- a separate temporary Jupyter smoke check fails on execution errors; +- the smoke check does not compare outputs or enforce freshness; +- no generated notebook Markdown, custom Markdown exporter, duplicate notebook + tree, or committed conversion cache remains; +- notebook links, source links, packaging checks, and the notebook overview use + the final notebook paths. + +#### Packaging acceptance + +- required runtime dependencies remain empty; +- existing individual user-facing extras remain usable; +- every direct notebook dependency is present in `notebook` and duplicated in + `all`; +- `all` is the union of user-facing optional feature dependencies; +- `all` excludes contributor-only test, lint, build, upload, and release tools; +- base, `notebook`, and `all` installations succeed from built artifacts in clean + environments; +- installed-package examples use only documented dependencies; +- wheel and sdist retain all required datasets, attribution, documentation, and + notebook artifacts. #### Final checks +Run the updated project checks, including: + ```bash python tools/check_registry.py -python tools/check_notebooks.py --check +# Run the final temporary Jupyter notebook smoke-execution command or thin wrapper. python tools/gen_readme.py --check flake8 src tests tools python -m pytest -q @@ -2555,11 +2681,42 @@ mkdocs build --strict python tools/release_check.py ``` +The obsolete custom Markdown-export command MUST NOT remain. Inspect the strict +site build and verify at least one inline formula, one display formula, one saved +text output, and one saved PNG plot from the notebooks. + +Also verify the base package, `atomref[notebook]`, and `atomref[all]` by +installing the built artifacts in clean environments and running the documented +minimal examples outside the repository checkout. + +#### Completion criteria + +- complete typed API documentation renders correctly; +- the README/home page communicates value and first use immediately without + overstating the package; +- a developer considering a local reimplementation can understand the concrete + advantages of adopting `atomref`; +- the actual `.ipynb` notebooks render directly in MkDocs with formulas and + saved rich output; +- notebook smoke execution uses standard Jupyter tooling without introducing an + output-freshness contract; +- generated notebook Markdown, the bespoke exporter, and duplicate notebook + representations are removed; +- base, `notebook`, and `all` optional-dependency installations are coherent, + documented, and validated from built artifacts; +- duplicate development-plan documentation is removed; +- architecture, scientific guides, package metadata, and changelog match the + accepted implementation; +- strict documentation and full release checks pass; +- version `0.2.1` is ready for external review and release. + #### Suggested commits ```text +docs: render notebooks directly docs: complete typed API reference docs: reposition atomref documentation +chore: add complete user extras chore: prepare atomref 0.2.1 ``` @@ -2665,27 +2822,55 @@ chore: prepare atomref 0.2.1 - [ ] Installed wheel loads the radial set, evaluates density, and performs both pairwise modes. -### 14.6 `0.2.1` notebook documentation - -- [ ] All shipped notebooks are executed and saved. -- [ ] Re-execution freshness is checked through a Jupyter kernel. -- [ ] Notebook plots and rich outputs render directly in MkDocs. -- [ ] Logical code sections have explanatory Markdown. -- [ ] Generated notebook Markdown copies are removed. -- [ ] The separate notebook exporter is removed. -- [ ] One maintained notebook tool handles update and check modes. -- [ ] CI and docs workflows use the simplified process. - -### 14.7 `0.2.1` API and README documentation +### 14.6 `0.2.1` documentation, notebooks, packaging, and release - [ ] Full typed signatures render. -- [ ] All public parameters, defaults, returns, raises, and attributes render. +- [ ] All public parameters, defaults, returns, raises, units, ranges, and + attributes render correctly. - [ ] Public docstrings use one structured style. +- [ ] API-page member selection is complete and excludes irrelevant internals. - [ ] API cross-references work. -- [ ] README/home page explains value immediately. -- [ ] Working radii, density, boundary-mode, and minimum-mode examples appear near the top. -- [ ] Detailed policy terminology is moved below first-use content. +- [ ] README/home page explains the package value immediately. +- [ ] Installation and a compact working example appear before detailed internal + terminology. +- [ ] Working radii, X–H, density, boundary-mode, and minimum-mode capabilities + are discoverable from the opening documentation path. +- [ ] The README explains honestly why a developer should use `atomref` rather + than maintain project-local constants, datasets, and fallback rules. +- [ ] Detailed policy and registry terminology is moved below first-use content. +- [ ] Provenance and limitations remain explicit without obscuring the initial + adoption path. +- [ ] `mkdocs-jupyter` renders the actual `.ipynb` files directly in the MkDocs + site. +- [ ] Each notebook has exactly one maintained source file and no parallel + Markdown representation. +- [ ] Inline `$...$` and display `$$...$$` mathematics render correctly with the + current Material theme. +- [ ] Saved text output and saved PNG plots render correctly. +- [ ] Ordinary and strict documentation builds do not execute or rewrite + notebooks. +- [ ] Notebook code is smoke-executed once through standard Jupyter tooling in a + temporary location and execution errors fail the check. +- [ ] Notebook smoke execution does not compare outputs or enforce freshness, + normalization, or byte-for-byte reproducibility. +- [ ] Generated notebook Markdown files, `tools/export_notebooks.py`, export-sync + tests, and obsolete CI/release commands are removed. +- [ ] No duplicate notebook tree, symlink, staging directory, or committed + notebook-conversion cache remains. +- [ ] Notebook navigation, links, source links, packaging checks, and sdist paths + use the final notebook locations. +- [ ] Every direct notebook dependency is declared in `notebook` and duplicated + in `all`. +- [ ] Documentation CI installs both `docs` and `notebook` extras. +- [ ] An `all` extra contains every user-facing optional feature dependency. +- [ ] `all` excludes test, lint, packaging, upload, and release-only dependencies. +- [ ] Base, `atomref[notebook]`, and `atomref[all]` installations succeed from + built artifacts in clean environments. +- [ ] Documented minimal examples run outside the repository checkout. - [ ] Duplicate development-plan docs are removed. +- [ ] Architecture, data-curation, scientific guides, package metadata, and + changelog match the accepted implementation. +- [ ] Version is `0.2.1`. - [ ] Strict docs and full release checks pass. ## 15. Optional architecture review after `0.2.0` From 5e94fef53d772084fba106022563fe00efdc7cbd Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 00:22:26 +0300 Subject: [PATCH 10/23] docs: render notebooks directly --- .github/workflows/ci.yml | 32 +- .github/workflows/docs.yml | 10 +- docs/dev/ias_method_selection.md | 7 +- docs/guide/notebooks.md | 84 +- docs/guide/proatomic_density.md | 16 +- docs/notebooks/01-quickstart.ipynb | 264 ++++ docs/notebooks/01-quickstart.md | 72 -- .../02-policies-and-assessment.ipynb | 233 ++++ docs/notebooks/02-policies-and-assessment.md | 73 -- .../03-custom-sets-and-discovery.ipynb | 187 +++ .../notebooks/03-custom-sets-and-discovery.md | 56 - .../04-ias-method-selection-study.ipynb | 1101 +++++++++++++++++ .../05-proatomic-density-and-ias.ipynb | 8 +- mkdocs.yml | 14 +- notebooks/01-quickstart.ipynb | 89 -- notebooks/02-policies-and-assessment.ipynb | 97 -- notebooks/03-custom-sets-and-discovery.ipynb | 79 -- notebooks/04-ias-method-selection-study.ipynb | 1079 ---------------- pyproject.toml | 7 +- tests/meta/test_notebooks.py | 169 ++- tests/meta/test_text_generation_tools.py | 34 - tools/README.md | 11 +- tools/check_notebooks.py | 332 ++--- tools/export_notebooks.py | 146 --- tools/release_check.py | 1 - 25 files changed, 2140 insertions(+), 2061 deletions(-) create mode 100644 docs/notebooks/01-quickstart.ipynb delete mode 100644 docs/notebooks/01-quickstart.md create mode 100644 docs/notebooks/02-policies-and-assessment.ipynb delete mode 100644 docs/notebooks/02-policies-and-assessment.md create mode 100644 docs/notebooks/03-custom-sets-and-discovery.ipynb delete mode 100644 docs/notebooks/03-custom-sets-and-discovery.md create mode 100644 docs/notebooks/04-ias-method-selection-study.ipynb rename {notebooks => docs/notebooks}/05-proatomic-density-and-ias.ipynb (99%) delete mode 100644 notebooks/01-quickstart.ipynb delete mode 100644 notebooks/02-policies-and-assessment.ipynb delete mode 100644 notebooks/03-custom-sets-and-discovery.ipynb delete mode 100644 notebooks/04-ias-method-selection-study.ipynb delete mode 100644 tests/meta/test_text_generation_tools.py delete mode 100644 tools/export_notebooks.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d88481..f868cf8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,18 +15,28 @@ jobs: - name: Install lint dependencies run: | python -m pip install --upgrade pip - python -m pip install .[dev,notebook] + python -m pip install ".[dev]" - name: Lint run: flake8 src tests tools - name: Validate packaged registry run: python tools/check_registry.py - - name: Validate notebooks - run: python tools/check_notebooks.py - - name: Check notebook exports - run: python tools/export_notebooks.py --check - name: Check README sync run: python tools/gen_readme.py --check + notebook-smoke: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install notebook dependencies + run: | + python -m pip install --upgrade pip + python -m pip install ".[notebook]" + - name: Smoke-execute notebooks with Jupyter + run: python tools/check_notebooks.py + docs-check: runs-on: ubuntu-latest steps: @@ -37,13 +47,13 @@ jobs: - name: Install docs extras run: | python -m pip install --upgrade pip - python -m pip install .[docs] - - name: Export notebooks and README - run: | - python tools/export_notebooks.py --check - python tools/gen_readme.py --check + python -m pip install ".[docs,notebook]" + - name: Check README sync + run: python tools/gen_readme.py --check - name: Build docs run: mkdocs build --strict + - name: Verify docs did not rewrite notebooks + run: git diff --exit-code -- docs/notebooks test: runs-on: ubuntu-latest @@ -58,7 +68,7 @@ jobs: - name: Install run: | python -m pip install --upgrade pip - python -m pip install .[test,notebook] + python -m pip install ".[test]" - name: Test run: pytest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 36c704c..586b767 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,13 +24,13 @@ jobs: - name: Install docs extras run: | python -m pip install --upgrade pip - python -m pip install .[docs] - - name: Check generated files - run: | - python tools/export_notebooks.py --check - python tools/gen_readme.py --check + python -m pip install ".[docs,notebook]" + - name: Check README sync + run: python tools/gen_readme.py --check - name: Build docs run: mkdocs build --strict + - name: Verify docs did not rewrite notebooks + run: git diff --exit-code -- docs/notebooks - name: Upload GitHub Pages artifact uses: actions/upload-pages-artifact@v4 with: diff --git a/docs/dev/ias_method_selection.md b/docs/dev/ias_method_selection.md index 620d5bb..35f9a8d 100644 --- a/docs/dev/ias_method_selection.md +++ b/docs/dev/ias_method_selection.md @@ -117,6 +117,7 @@ separate: [doi:10.1021/jp1093417](https://doi.org/10.1021/jp1093417). The complete calculations, representative profiles, adversarial cases, and -local timing record are in the executed -[`04-ias-method-selection-study.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/04-ias-method-selection-study.ipynb) -notebook. +local timing record are in the directly rendered +[`04-ias-method-selection-study.ipynb`](../notebooks/04-ias-method-selection-study.ipynb) +notebook. Its committed outputs are shown without re-execution during the +documentation build. diff --git a/docs/guide/notebooks.md b/docs/guide/notebooks.md index e7a93c5..9546191 100644 --- a/docs/guide/notebooks.md +++ b/docs/guide/notebooks.md @@ -1,31 +1,57 @@ # Notebook gallery -`atomref` ships example and supporting-information Jupyter notebooks. The first -three user-workflow notebooks have generated Markdown copies in these docs. -The two `0.2.0` proatomic notebooks are saved, executed `.ipynb` artifacts: the -method-selection study also has a compact documentation page, while the feature -notebook is linked directly so its embedded plots and outputs remain visible on -GitHub. Direct notebook rendering is planned for a later documentation release. - -## Available notebooks - -- [Quickstart notebook](../notebooks/01-quickstart.md) — basic imports, - `get_*` vs `lookup_*`, quantity discovery, and packaged-set access. -- [Policies and assessment notebook](../notebooks/02-policies-and-assessment.md) - — overrides, transfer-backed policies, and policy summaries. -- [Custom sets and discovery notebook](../notebooks/03-custom-sets-and-discovery.md) - — user-defined sets, catalog inspection, and metadata exploration. -- [IAS method-selection study](../dev/ias_method_selection.md) — supporting - numerical evidence for the two-mode pairwise design and the decision not to - expose every interpolation-scale local minimum. -- [Proatomic density and IAS feature notebook](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) - — executed public-API examples for profile discovery, unit-aware density - evaluation, both pairwise modes, diagnostic cases, and saved plots. - -The original notebook files are also in the repository: - -- [`01-quickstart.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/01-quickstart.ipynb) -- [`02-policies-and-assessment.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/02-policies-and-assessment.ipynb) -- [`03-custom-sets-and-discovery.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/03-custom-sets-and-discovery.ipynb) -- [`04-ias-method-selection-study.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/04-ias-method-selection-study.ipynb) -- [`05-proatomic-density-and-ias.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) +`atomref` ships five explanatory Jupyter notebooks. These are the actual +committed `.ipynb` sources rendered directly by MkDocs—there is no generated +Markdown copy or parallel notebook tree. + +Documentation builds show the committed Markdown, code, mathematics, saved +text, and saved PNG plots. They do not execute or rewrite the notebooks. A +separate release check runs temporary copies through a standard Jupyter kernel, +fails on execution exceptions, and discards the temporary outputs without +comparing them with the committed files. + +## User workflows + +- [Quickstart](../notebooks/01-quickstart.ipynb) introduces direct scalar + values, provenance-carrying lookup, and dataset discovery. +- [Policies and assessment](../notebooks/02-policies-and-assessment.ipynb) + explains ordered restoration, fitted transfers, provenance, and policy + summaries. +- [Custom sets and discovery](../notebooks/03-custom-sets-and-discovery.ipynb) + builds a user-provided scalar set and inspects the packaged catalog. +- [Proatomic density and IAS workflows](../notebooks/05-proatomic-density-and-ias.ipynb) + covers profile provenance, unit-aware evaluation, both pairwise modes, + diagnostic cases, formulas, and saved plots. + +## Supporting numerical study + +- [IAS method-selection study](../notebooks/04-ias-method-selection-study.ipynb) + records the numerical evidence behind the stable boundary and practical + minimum contracts. It is supporting information, not an alternative runtime + implementation. + +The durable scientific summary is also available in +[Pairwise boundary and IAS-proxy method selection](../dev/ias_method_selection.md). + +## Notebook sources + +The canonical file for every rendered page is available here for viewing or +download: + +- [`01-quickstart.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/docs/notebooks/01-quickstart.ipynb) +- [`02-policies-and-assessment.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/docs/notebooks/02-policies-and-assessment.ipynb) +- [`03-custom-sets-and-discovery.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/docs/notebooks/03-custom-sets-and-discovery.ipynb) +- [`04-ias-method-selection-study.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/docs/notebooks/04-ias-method-selection-study.ipynb) +- [`05-proatomic-density-and-ias.ipynb`](https://github.com/DeloneCommons/atomref/blob/main/docs/notebooks/05-proatomic-density-and-ias.ipynb) + +## Run notebooks locally + +Install all direct notebook dependencies with: + +```bash +pip install "atomref[notebook]" +``` + +The notebooks are designed for a standard Python Jupyter kernel. Saved outputs +are examples, not a byte-for-byte reproducibility promise: timing, rendering +metadata, and PNG bytes may vary when a notebook is deliberately rerun. diff --git a/docs/guide/proatomic_density.md b/docs/guide/proatomic_density.md index fc7a014..c584459 100644 --- a/docs/guide/proatomic_density.md +++ b/docs/guide/proatomic_density.md @@ -31,8 +31,8 @@ The supported public interval is exactly 0 through 20 bohr, inclusive. The stored snapshot retains one source point above 20 bohr only to bracket the endpoint. Between positive stored knots, evaluation uses the dependency-free `loglog_positive_bracketed_v1` contract: linear interpolation in `log(r)` and -`log(rho)`. Exact knots return their stored values. Over `0 <= r <=` the first -positive stored radius, the first stored density is returned; this is a +`log(rho)`. Exact knots return their stored values. From `r = 0` through the +first positive stored radius, the first stored density is returned; this is a finite-grid convention, not an exact evaluation at the nucleus. Negative, non-finite, and above-domain radii raise `ValueError`; there is no extrapolation or zero fill. @@ -131,8 +131,10 @@ resolution-limited non-result is acceptable. These are neutral-proatom estimates. Neither mode locates a molecular QTAIM zero-flux surface or an exact molecular-density critical point. They do not add ionic, environment-dependent, vectorized, molecular-density, or grid-density -behavior. See the executed -[feature notebook on GitHub](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) -for the public workflows and the [IAS method-selection -study](../dev/ias_method_selection.md) for the numerical decision and archived -all-minima limitations. +behavior. See the directly rendered +[proatomic density and IAS notebook](../notebooks/05-proatomic-density-and-ias.ipynb) +for the public workflows, the +[IAS method-selection notebook](../notebooks/04-ias-method-selection-study.ipynb) +for the executed supporting analysis, and the +[durable method summary](../dev/ias_method_selection.md) for the numerical +decision and archived all-minima limitations. diff --git a/docs/notebooks/01-quickstart.ipynb b/docs/notebooks/01-quickstart.ipynb new file mode 100644 index 0000000..3758d99 --- /dev/null +++ b/docs/notebooks/01-quickstart.ipynb @@ -0,0 +1,264 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "71066777", + "metadata": {}, + "source": [ + "# atomref scalar-data quickstart\n", + "\n", + "**Goal.** Retrieve common atomic reference values, inspect the provenance of\n", + "a transferred value, and discover the packaged scalar datasets.\n", + "\n", + "**Prerequisites.** Install `atomref`; no optional dependencies or prior\n", + "knowledge of its registry and policy internals are required.\n", + "\n", + "The density and pairwise neutral-proatom workflows have their own focused\n", + "notebook later in this gallery." + ] + }, + { + "cell_type": "markdown", + "id": "a61df675", + "metadata": {}, + "source": [ + "## Setup and element discovery\n", + "\n", + "Import the public package namespace, normalize one element symbol, and list\n", + "the available reference-data quantities." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1e9acad5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:14.251072Z", + "iopub.status.busy": "2026-07-14T20:11:14.250764Z", + "iopub.status.idle": "2026-07-14T20:11:14.404186Z", + "shell.execute_reply": "2026-07-14T20:11:14.403144Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Element(z=17, symbol='Cl', name='Chlorine')\n", + "('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density')\n" + ] + } + ], + "source": [ + "import atomref as ar\n", + "\n", + "print(ar.get_element('Cl'))\n", + "print(ar.list_quantities())\n" + ] + }, + { + "cell_type": "markdown", + "id": "b3f97e09", + "metadata": {}, + "source": [ + "## Direct scalar lookups\n", + "\n", + "The convenience `get_*` functions return numbers in angstrom. They are the\n", + "short path when the selected value is all the caller needs." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fccc4ee6", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:14.406117Z", + "iopub.status.busy": "2026-07-14T20:11:14.405944Z", + "iopub.status.idle": "2026-07-14T20:11:14.420709Z", + "shell.execute_reply": "2026-07-14T20:11:14.419620Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.76\n", + "1.5\n" + ] + } + ], + "source": [ + "r_c = ar.get_covalent_radius('C')\n", + "r_vdw = ar.get_vdw_radius('O')\n", + "print(r_c)\n", + "print(r_vdw)\n", + "assert r_c == 0.76\n", + "assert r_vdw == 1.50\n" + ] + }, + { + "cell_type": "markdown", + "id": "0e75e1ac", + "metadata": {}, + "source": [ + "## A lookup with provenance\n", + "\n", + "The preferred van der Waals table has no direct value for Pm. `lookup_*`\n", + "returns the value together with the transfer method and source dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8696b55d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:14.422614Z", + "iopub.status.busy": "2026-07-14T20:11:14.422449Z", + "iopub.status.idle": "2026-07-14T20:11:14.426883Z", + "shell.execute_reply": "2026-07-14T20:11:14.425877Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.897226539515\n", + "transfer_linear\n", + "(DatasetRef(quantity='atomic_radius', set_id='rahm2016'),)\n" + ] + } + ], + "source": [ + "lookup = ar.lookup_vdw_radius('Pm')\n", + "print(f\"{lookup.value:.12f}\")\n", + "print(lookup.source)\n", + "print(lookup.resolved_from)\n", + "assert lookup.source == 'transfer_linear'\n" + ] + }, + { + "cell_type": "markdown", + "id": "7d19b2d7", + "metadata": {}, + "source": [ + "## Catalog metadata\n", + "\n", + "Quantity and dataset metadata make units, intended role, and source choice\n", + "discoverable instead of leaving them as project-local conventions." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e6f673b1", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:14.429516Z", + "iopub.status.busy": "2026-07-14T20:11:14.429265Z", + "iopub.status.idle": "2026-07-14T20:11:14.433868Z", + "shell.execute_reply": "2026-07-14T20:11:14.432970Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "atomic_radius element angstrom\n", + "bondi1964 Bondi van der Waals radii target\n", + "rowland_taylor1996 Rowland & Taylor nonbonded contact radii target\n", + "alvarez2013 Alvarez van der Waals radii target\n", + "chernyshov2020 Chernyshov LoS van der Waals radii target\n" + ] + } + ], + "source": [ + "quantity = ar.get_quantity_info('atomic_radius')\n", + "print(quantity.quantity, quantity.domain, quantity.units)\n", + "\n", + "for info in ar.list_dataset_infos('van_der_waals_radius', usage_role='target'):\n", + " print(info.ref.set_id, info.name, info.usage_role)\n" + ] + }, + { + "cell_type": "markdown", + "id": "791e6178", + "metadata": {}, + "source": [ + "## Direct packaged-set access\n", + "\n", + "Load a target radii table through its convenience helper and a support table\n", + "through the generic dataset reference used by the registry." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f34b8f08", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:14.436281Z", + "iopub.status.busy": "2026-07-14T20:11:14.436089Z", + "iopub.status.idle": "2026-07-14T20:11:14.439659Z", + "shell.execute_reply": "2026-07-14T20:11:14.438664Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.5\n", + "2.83\n" + ] + } + ], + "source": [ + "vdw = ar.get_radii_set('van_der_waals', 'alvarez2013')\n", + "print(vdw.get('O'))\n", + "\n", + "support = ar.get_builtin_set(ar.DatasetRef('atomic_radius', 'rahm2016'))\n", + "print(support.get('Pm'))\n" + ] + }, + { + "cell_type": "markdown", + "id": "680f4dda", + "metadata": {}, + "source": [ + "## What this demonstrated and limitations\n", + "\n", + "The scalar API supports short numeric lookups, provenance-carrying results,\n", + "catalog discovery, and direct packaged-set access. A transferred value is a\n", + "declared policy result, not a hidden measurement. Dataset suitability and\n", + "fallback policy remain choices for the consuming scientific workflow." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/notebooks/01-quickstart.md b/docs/notebooks/01-quickstart.md deleted file mode 100644 index 12529e0..0000000 --- a/docs/notebooks/01-quickstart.md +++ /dev/null @@ -1,72 +0,0 @@ - - -[Open the original notebook on GitHub](https://github.com/DeloneCommons/atomref/blob/main/notebooks/01-quickstart.ipynb) -# atomref quickstart - -This notebook covers the main public API: element helpers, direct -`get_*` calls, provenance-carrying `lookup_*` calls, and packaged dataset -discovery. -```python -import atomref as ar - -print(ar.get_element('Cl')) -print(ar.list_quantities()) -``` -**Output** -```text -Element(z=17, symbol='Cl', name='Chlorine') -('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') -``` -```python -r_c = ar.get_covalent_radius('C') -r_vdw = ar.get_vdw_radius('O') -print(r_c) -print(r_vdw) -assert r_c == 0.76 -assert r_vdw == 1.50 -``` -**Output** -```text -0.76 -1.5 -``` -```python -lookup = ar.lookup_vdw_radius('Pm') -print(f"{lookup.value:.12f}") -print(lookup.source) -print(lookup.resolved_from) -assert lookup.source == 'transfer_linear' -``` -**Output** -```text -2.897226539515 -transfer_linear -(DatasetRef(quantity='atomic_radius', set_id='rahm2016'),) -``` -```python -quantity = ar.get_quantity_info('atomic_radius') -print(quantity.quantity, quantity.domain, quantity.units) - -for info in ar.list_dataset_infos('van_der_waals_radius', usage_role='target'): - print(info.ref.set_id, info.name, info.usage_role) -``` -**Output** -```text -atomic_radius element angstrom -bondi1964 Bondi van der Waals radii target -rowland_taylor1996 Rowland & Taylor nonbonded contact radii target -alvarez2013 Alvarez van der Waals radii target -chernyshov2020 Chernyshov LoS van der Waals radii target -``` -```python -vdw = ar.get_radii_set('van_der_waals', 'alvarez2013') -print(vdw.get('O')) - -support = ar.get_builtin_set(ar.DatasetRef('atomic_radius', 'rahm2016')) -print(support.get('Pm')) -``` -**Output** -```text -1.5 -2.83 -``` diff --git a/docs/notebooks/02-policies-and-assessment.ipynb b/docs/notebooks/02-policies-and-assessment.ipynb new file mode 100644 index 0000000..6d49757 --- /dev/null +++ b/docs/notebooks/02-policies-and-assessment.ipynb @@ -0,0 +1,233 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3313eadf", + "metadata": {}, + "source": [ + "# Policies and assessment\n", + "\n", + "**Goal.** Define explicit substitution and linear-transfer policies, inspect\n", + "their provenance, and summarize their behavior over several elements.\n", + "\n", + "**Prerequisites.** Familiarity with direct `get_*` and provenance-carrying\n", + "`lookup_*` calls from the scalar-data quickstart is helpful. All values in\n", + "this notebook are scalar radii in angstrom." + ] + }, + { + "cell_type": "markdown", + "id": "3a60ea11", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "Import the public namespace used for policy, dataset, transfer, and\n", + "assessment objects." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bbc64e7f", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:15.758860Z", + "iopub.status.busy": "2026-07-14T20:11:15.758646Z", + "iopub.status.idle": "2026-07-14T20:11:15.895500Z", + "shell.execute_reply": "2026-07-14T20:11:15.894387Z" + } + }, + "outputs": [], + "source": [ + "import atomref as ar\n" + ] + }, + { + "cell_type": "markdown", + "id": "308a0e21", + "metadata": {}, + "source": [ + "## Ordered substitution\n", + "\n", + "This policy prefers Cordero covalent radii and explicitly substitutes the\n", + "legacy CSD covalent table when the preferred table lacks an element. The\n", + "result reports that substitution and the dataset that supplied it." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6e5f9f9b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:15.898380Z", + "iopub.status.busy": "2026-07-14T20:11:15.898198Z", + "iopub.status.idle": "2026-07-14T20:11:15.935476Z", + "shell.execute_reply": "2026-07-14T20:11:15.933556Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "transfer_substitution\n", + "1.540000000000\n", + "(DatasetRef(quantity='covalent_radius', set_id='csd_legacy_cov'),)\n" + ] + } + ], + "source": [ + "covalent_policy = ar.RadiiPolicy(\n", + " kind='covalent',\n", + " base_set='cordero2008',\n", + " transfers=(\n", + " ar.SubstitutionTransfer(\n", + " source=ar.DatasetRef('covalent_radius', 'csd_legacy_cov')\n", + " ),\n", + " ),\n", + ")\n", + "lookup = ar.lookup_covalent_radius('Bk', policy=covalent_policy)\n", + "print(lookup.source)\n", + "print(f\"{lookup.value:.12f}\")\n", + "print(lookup.resolved_from)\n" + ] + }, + { + "cell_type": "markdown", + "id": "40aa7f39", + "metadata": {}, + "source": [ + "## Linear transfer from support data\n", + "\n", + "For a missing van der Waals value, a fitted relation uses the packaged Rahm\n", + "atomic-radius support set. The fit record exposes its coefficients and\n", + "training-point count rather than hiding the inference." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "daeaa220", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:15.938115Z", + "iopub.status.busy": "2026-07-14T20:11:15.937883Z", + "iopub.status.idle": "2026-07-14T20:11:15.949934Z", + "shell.execute_reply": "2026-07-14T20:11:15.948451Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.897226539515\n", + "transfer_linear\n", + "slope=1.135336645553 intercept=-0.315776167399 n=90\n" + ] + } + ], + "source": [ + "vdw_policy = ar.RadiiPolicy(\n", + " kind='van_der_waals',\n", + " base_set='alvarez2013',\n", + " transfers=(\n", + " ar.LinearTransfer(\n", + " predictors=(ar.DatasetRef('atomic_radius', 'rahm2016'),)\n", + " ),\n", + " ),\n", + ")\n", + "lookup = ar.lookup_vdw_radius('Pm', policy=vdw_policy)\n", + "print(f\"{lookup.value:.12f}\")\n", + "print(lookup.source)\n", + "print(\n", + " f\"slope={lookup.fit.coefficients[0]:.12f} intercept={lookup.fit.intercept:.12f} n={lookup.fit.n_points}\"\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "4b36c351", + "metadata": {}, + "source": [ + "## Assess behavior over a selection\n", + "\n", + "Assessment counts direct, transferred, and missing outcomes and can retain a\n", + "per-element result for auditing the policy before it enters an algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "336054f5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:15.952974Z", + "iopub.status.busy": "2026-07-14T20:11:15.952686Z", + "iopub.status.idle": "2026-07-14T20:11:15.958265Z", + "shell.execute_reply": "2026-07-14T20:11:15.956920Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3 1 0\n", + "C base 1.770000000000\n", + "Xe base 2.060000000000\n", + "Pm transfer_linear 2.897226539515\n", + "Bk base 3.400000000000\n" + ] + } + ], + "source": [ + "assessment = ar.assess_radii_policy(\n", + " ['C', 'Xe', 'Pm', 'Bk'],\n", + " policy=vdw_policy,\n", + " detail=True,\n", + ")\n", + "print(assessment.n_base, assessment.n_transfer_linear, assessment.n_missing)\n", + "for row in assessment.per_element:\n", + " value = 'None' if row.lookup.value is None else f\"{row.lookup.value:.12f}\"\n", + " print(row.symbol, row.lookup.source, value)\n" + ] + }, + { + "cell_type": "markdown", + "id": "b2534084", + "metadata": {}, + "source": [ + "## What this demonstrated and limitations\n", + "\n", + "Policies make fallback order and inferred values explicit, while assessment\n", + "shows how that choice behaves over a requested element set. Transfer quality\n", + "still depends on scientifically appropriate target and support datasets.\n", + "These scalar policies do not apply to proatomic radial profiles." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/notebooks/02-policies-and-assessment.md b/docs/notebooks/02-policies-and-assessment.md deleted file mode 100644 index 4f6baf6..0000000 --- a/docs/notebooks/02-policies-and-assessment.md +++ /dev/null @@ -1,73 +0,0 @@ - - -[Open the original notebook on GitHub](https://github.com/DeloneCommons/atomref/blob/main/notebooks/02-policies-and-assessment.ipynb) -# Policies and assessment - -This notebook shows how `atomref` resolves missing values through ordered -policy steps and how to inspect policy-level behavior. -```python -import atomref as ar -``` -```python -covalent_policy = ar.RadiiPolicy( - kind='covalent', - base_set='cordero2008', - transfers=( - ar.SubstitutionTransfer( - source=ar.DatasetRef('covalent_radius', 'csd_legacy_cov') - ), - ), -) -lookup = ar.lookup_covalent_radius('Bk', policy=covalent_policy) -print(lookup.source) -print(f"{lookup.value:.12f}") -print(lookup.resolved_from) -``` -**Output** -```text -transfer_substitution -1.540000000000 -(DatasetRef(quantity='covalent_radius', set_id='csd_legacy_cov'),) -``` -```python -vdw_policy = ar.RadiiPolicy( - kind='van_der_waals', - base_set='alvarez2013', - transfers=( - ar.LinearTransfer( - predictors=(ar.DatasetRef('atomic_radius', 'rahm2016'),) - ), - ), -) -lookup = ar.lookup_vdw_radius('Pm', policy=vdw_policy) -print(f"{lookup.value:.12f}") -print(lookup.source) -print( - f"slope={lookup.fit.coefficients[0]:.12f} intercept={lookup.fit.intercept:.12f} n={lookup.fit.n_points}" -) -``` -**Output** -```text -2.897226539515 -transfer_linear -slope=1.135336645553 intercept=-0.315776167399 n=90 -``` -```python -assessment = ar.assess_radii_policy( - ['C', 'Xe', 'Pm', 'Bk'], - policy=vdw_policy, - detail=True, -) -print(assessment.n_base, assessment.n_transfer_linear, assessment.n_missing) -for row in assessment.per_element: - value = 'None' if row.lookup.value is None else f"{row.lookup.value:.12f}" - print(row.symbol, row.lookup.source, value) -``` -**Output** -```text -3 1 0 -C base 1.770000000000 -Xe base 2.060000000000 -Pm transfer_linear 2.897226539515 -Bk base 3.400000000000 -``` diff --git a/docs/notebooks/03-custom-sets-and-discovery.ipynb b/docs/notebooks/03-custom-sets-and-discovery.ipynb new file mode 100644 index 0000000..3166023 --- /dev/null +++ b/docs/notebooks/03-custom-sets-and-discovery.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "84b6b5b7", + "metadata": {}, + "source": [ + "# Custom sets and dataset discovery\n", + "\n", + "**Goal.** Build a small immutable user-provided scalar set, use it as a\n", + "policy base, and inspect metadata for packaged alternatives.\n", + "\n", + "**Prerequisites.** Familiarity with element symbols and basic scalar\n", + "lookups is sufficient. The example values are illustrative covalent radii\n", + "in angstrom, not a new curated dataset shipped by `atomref`." + ] + }, + { + "cell_type": "markdown", + "id": "2306eafd", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "Import the public namespace used to construct sets, policies, and dataset\n", + "references." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b9842810", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:17.456848Z", + "iopub.status.busy": "2026-07-14T20:11:17.456589Z", + "iopub.status.idle": "2026-07-14T20:11:17.615608Z", + "shell.execute_reply": "2026-07-14T20:11:17.614461Z" + } + }, + "outputs": [], + "source": [ + "import atomref as ar\n" + ] + }, + { + "cell_type": "markdown", + "id": "fc8072d3", + "metadata": {}, + "source": [ + "## Define a custom base set\n", + "\n", + "The custom set supplies C and O directly. An explicit substitution step\n", + "uses the packaged Cordero table for N, so each lookup retains whether it\n", + "came from the custom base or the fallback source." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "70ae0eff", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:17.617820Z", + "iopub.status.busy": "2026-07-14T20:11:17.617624Z", + "iopub.status.idle": "2026-07-14T20:11:17.653056Z", + "shell.execute_reply": "2026-07-14T20:11:17.651997Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "C LookupResult(value=0.77, source='base', target=DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'), resolved_from=(DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'),), is_placeholder=False, fit=None, notes=(), transfer_depth=0)\n", + "O LookupResult(value=0.67, source='base', target=DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'), resolved_from=(DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'),), is_placeholder=False, fit=None, notes=(), transfer_depth=0)\n", + "N LookupResult(value=0.71, source='transfer_substitution', target=DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'), resolved_from=(DatasetRef(quantity='covalent_radius', set_id='cordero2008'),), is_placeholder=False, fit=None, notes=('missing in base set; substituted from transfer source',), transfer_depth=1)\n" + ] + } + ], + "source": [ + "custom_cov = ar.ElementScalarSet.from_mapping(\n", + " ref=ar.DatasetRef(\"covalent_radius\", \"demo_user_cov\"),\n", + " values={\"C\": 0.77, \"O\": 0.67},\n", + " name=\"Demo user covalent set\",\n", + " units=\"angstrom\",\n", + " description=\"Example custom set for notebook usage.\",\n", + " notes=(\"Notebook example\",),\n", + ")\n", + "\n", + "policy = ar.RadiiPolicy(\n", + " kind=\"covalent\",\n", + " base_set=custom_cov,\n", + " transfers=(\n", + " ar.SubstitutionTransfer(\n", + " source=ar.DatasetRef(\"covalent_radius\", \"cordero2008\")\n", + " ),\n", + " ),\n", + ")\n", + "\n", + "for symbol in (\"C\", \"O\", \"N\"):\n", + " print(symbol, ar.lookup_covalent_radius(symbol, policy=policy))\n" + ] + }, + { + "cell_type": "markdown", + "id": "346902c8", + "metadata": {}, + "source": [ + "## Discover packaged dataset metadata\n", + "\n", + "List target van der Waals tables and inspect the scientific classification\n", + "and usage role of the Rahm atomic-radius support dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3b37702a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:11:17.655243Z", + "iopub.status.busy": "2026-07-14T20:11:17.655068Z", + "iopub.status.idle": "2026-07-14T20:11:17.659378Z", + "shell.execute_reply": "2026-07-14T20:11:17.658466Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bondi1964 vdw_compiled compiled_experimental mixed_or_legacy\n", + "rowland_taylor1996 vdw_structural structural condensed_phase\n", + "alvarez2013 vdw_structural structural condensed_phase\n", + "chernyshov2020 vdw_structural_typed_reduced structural condensed_phase\n", + "Rahm isodensity atomic radii (ρ=0.001 e/bohr³)\n", + "atomic_isodensity isolated_atom support\n" + ] + } + ], + "source": [ + "for info in ar.list_radii_set_infos(\"van_der_waals\", usage_role=\"target\"):\n", + " print(info.ref.set_id, info.semantic_class, info.origin_class, info.phase_context)\n", + "\n", + "rahm = ar.get_dataset_info(ar.DatasetRef(\"atomic_radius\", \"rahm2016\"))\n", + "print(rahm.name)\n", + "print(rahm.semantic_class, rahm.phase_context, rahm.usage_role)\n" + ] + }, + { + "cell_type": "markdown", + "id": "be1ea745", + "metadata": {}, + "source": [ + "## What this demonstrated and limitations\n", + "\n", + "A custom `ElementScalarSet` can participate in the same scalar policy\n", + "machinery as packaged tables without modifying the global registry. Users\n", + "remain responsible for the provenance, units, coverage, and scientific\n", + "fitness of their own values. Custom radial-profile datasets are outside the\n", + "current public scope." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/notebooks/03-custom-sets-and-discovery.md b/docs/notebooks/03-custom-sets-and-discovery.md deleted file mode 100644 index 47138bf..0000000 --- a/docs/notebooks/03-custom-sets-and-discovery.md +++ /dev/null @@ -1,56 +0,0 @@ - - -[Open the original notebook on GitHub](https://github.com/DeloneCommons/atomref/blob/main/notebooks/03-custom-sets-and-discovery.ipynb) -# Custom sets and dataset discovery - -This notebook shows how to define a small user-provided set, plug it into a -policy, and inspect the packaged dataset catalog. -```python -import atomref as ar -``` -```python -custom_cov = ar.ElementScalarSet.from_mapping( - ref=ar.DatasetRef("covalent_radius", "demo_user_cov"), - values={"C": 0.77, "O": 0.67}, - name="Demo user covalent set", - units="angstrom", - description="Example custom set for notebook usage.", - notes=("Notebook example",), -) - -policy = ar.RadiiPolicy( - kind="covalent", - base_set=custom_cov, - transfers=( - ar.SubstitutionTransfer( - source=ar.DatasetRef("covalent_radius", "cordero2008") - ), - ), -) - -for symbol in ("C", "O", "N"): - print(symbol, ar.lookup_covalent_radius(symbol, policy=policy)) -``` -**Output** -```text -C LookupResult(value=0.77, source='base', target=DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'), resolved_from=(DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'),), is_placeholder=False, fit=None, notes=(), transfer_depth=0) -O LookupResult(value=0.67, source='base', target=DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'), resolved_from=(DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'),), is_placeholder=False, fit=None, notes=(), transfer_depth=0) -N LookupResult(value=0.71, source='transfer_substitution', target=DatasetRef(quantity='covalent_radius', set_id='demo_user_cov'), resolved_from=(DatasetRef(quantity='covalent_radius', set_id='cordero2008'),), is_placeholder=False, fit=None, notes=('missing in base set; substituted from transfer source',), transfer_depth=1) -``` -```python -for info in ar.list_radii_set_infos("van_der_waals", usage_role="target"): - print(info.ref.set_id, info.semantic_class, info.origin_class, info.phase_context) - -rahm = ar.get_dataset_info(ar.DatasetRef("atomic_radius", "rahm2016")) -print(rahm.name) -print(rahm.semantic_class, rahm.phase_context, rahm.usage_role) -``` -**Output** -```text -bondi1964 vdw_compiled compiled_experimental mixed_or_legacy -rowland_taylor1996 vdw_structural structural condensed_phase -alvarez2013 vdw_structural structural condensed_phase -chernyshov2020 vdw_structural_typed_reduced structural condensed_phase -Rahm isodensity atomic radii (ρ=0.001 e/bohr³) -atomic_isodensity isolated_atom support -``` diff --git a/docs/notebooks/04-ias-method-selection-study.ipynb b/docs/notebooks/04-ias-method-selection-study.ipynb new file mode 100644 index 0000000..bed2275 --- /dev/null +++ b/docs/notebooks/04-ias-method-selection-study.ipynb @@ -0,0 +1,1101 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cdde20c5", + "metadata": {}, + "source": [ + "\n", + "# Choosing a practical neutral-proatom IAS proxy\n", + "\n", + "This notebook is supporting information for the Stage 4 numerical decision in\n", + "`atomref`. It compares two pairwise quantities derived from the packaged neutral\n", + "spherical proatomic densities:\n", + "\n", + "1. a **stable proatomic boundary**, based on equal proatomic contributions or a\n", + " midpoint between fixed low-density contours;\n", + "2. an optional **promolecular line-density minimum**, searched only where both\n", + " proatomic contributions remain above the cutoff and only to a declared\n", + " practical spatial resolution.\n", + "\n", + "The notebook does **not** implement the public API and does not claim to locate\n", + "an exact QTAIM zero-flux surface. Its helper functions are research prototypes\n", + "used to test the revised contract.\n", + "\n", + "**Prerequisites.** This supporting study assumes familiarity with radial\n", + "electron densities, one-dimensional minimization, and the distinction between\n", + "neutral-proatom models and molecular-density QTAIM analysis." + ] + }, + { + "cell_type": "markdown", + "id": "59023f54", + "metadata": {}, + "source": [ + "\n", + "## Numerical policy examined here\n", + "\n", + "The per-atom neutral-tail cutoff is\n", + "\n", + "$$\n", + "\\rho_c = 10^{-4}\\ \\mathrm{electron/bohr^3}.\n", + "$$\n", + "\n", + "The default boundary mode uses homonuclear symmetry, equal proatomic\n", + "contributions in the overlap region, and the midpoint between cutoff contours\n", + "when a low-density gap opens. The optional minimum mode searches only the\n", + "interval where both contributions are at least $\\rho_c$, and coalesces\n", + "features below `0.01 bohr` (about `0.0053 Å`).\n" + ] + }, + { + "cell_type": "markdown", + "id": "fcdd3855", + "metadata": {}, + "source": [ + "## Scientific context and references\n", + "\n", + "The two modes answer different questions.\n", + "\n", + "- QTAIM defines atomic basins through zero-flux surfaces of the **molecular**\n", + " electron density; a critical point on an internuclear path is therefore a\n", + " molecular-density object, not something that neutral spherical proatoms can\n", + " reproduce exactly. See R. F. W. Bader, *Chemical Reviews* **91** (1991),\n", + " 893–928, [doi:10.1021/cr00005a013](https://doi.org/10.1021/cr00005a013).\n", + "- Hirshfeld's stockholder construction assigns molecular density in proportion\n", + " to free-atom reference densities. In the present two-proatom line model,\n", + " equal neutral-proatom densities therefore give equal pairwise stockholder\n", + " contributions. See F. L. Hirshfeld, *Theoretica Chimica Acta* **44** (1977),\n", + " 129–138, [doi:10.1007/BF00549096](https://doi.org/10.1007/BF00549096).\n", + "- Radial-density constructions can be useful atom/bond models, but they remain\n", + " model definitions that need comparison with molecular-density results. See\n", + " P. L. Warburton, R. A. Poirier, and D. Nippard, *J. Phys. Chem. A* **115**\n", + " (2011), 852–867, [doi:10.1021/jp1093417](https://doi.org/10.1021/jp1093417).\n", + "\n", + "These references motivate the interpretation. The numerical experiments below\n", + "justify only the implementation policy for the supplied neutral-proatom data;\n", + "they do not validate either mode as an exact QTAIM surface.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "51dd32b7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:22:29.498724Z", + "iopub.status.busy": "2026-07-14T20:22:29.498536Z", + "iopub.status.idle": "2026-07-14T20:22:30.124758Z", + "shell.execute_reply": "2026-07-14T20:22:30.123737Z" + } + }, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "\n", + "from bisect import bisect_right\n", + "from dataclasses import dataclass\n", + "from functools import lru_cache\n", + "import math\n", + "import random\n", + "import time\n", + "\n", + "from pathlib import Path\n", + "import sys\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"src\" / \"atomref\").is_dir():\n", + " for candidate in (ROOT.parent, ROOT.parent.parent):\n", + " if (candidate / \"src\" / \"atomref\").is_dir():\n", + " ROOT = candidate\n", + " break\n", + "if not (ROOT / \"src\" / \"atomref\").is_dir():\n", + " raise RuntimeError(\"Run this notebook from the atomref repository checkout\")\n", + "sys.path.insert(0, str(ROOT / \"src\"))\n", + "\n", + "import atomref as ar\n", + "\n", + "try:\n", + " import matplotlib.pyplot as plt\n", + "except ImportError: # The package itself has no plotting dependency.\n", + " plt = None\n", + "\n", + "ASSET_DIR = ROOT / \"docs\" / \"assets\" / \"ias-method-study\"\n", + "ASSET_DIR.mkdir(parents=True, exist_ok=True)\n", + "\n", + "TAIL_CUTOFF = 1.0e-4\n", + "PRACTICAL_RESOLUTION = 0.01\n", + "INITIAL_STEP = 0.02\n", + "CONFIRM_STEP = 0.01\n", + "FALLBACK_STEP = 0.005\n", + "COMPETITIVE_RELATIVE_GAP = 1.0e-4\n", + "\n", + "\n", + "@dataclass(frozen=True)\n", + "class StudyEstimate:\n", + " method: str\n", + " status: str\n", + " position: float | None\n", + " rho_a: float | None\n", + " rho_b: float | None\n", + " rho_sum: float | None\n", + " contour_separation: float\n", + " search_resolution: float | None = None\n", + " search_converged: bool | None = None\n", + " alternative_position: float | None = None\n", + " relative_depth_gap: float | None = None\n", + " probes: int = 0\n", + "\n", + "\n", + "@lru_cache(maxsize=None)\n", + "def profile(symbol: str):\n", + " result = ar.get_proatomic_density_profile(symbol)\n", + " if result is None:\n", + " raise ValueError(f\"missing profile: {symbol}\")\n", + " return result\n", + "\n", + "\n", + "@lru_cache(maxsize=None)\n", + "def cutoff_radius(symbol: str) -> float:\n", + " p = profile(symbol)\n", + " right = next(i for i, value in enumerate(p.densities) if value <= TAIL_CUTOFF)\n", + " if p.densities[right] == TAIL_CUTOFF:\n", + " return p.radii[right]\n", + " left = right - 1\n", + " lr0 = math.log(p.radii[left])\n", + " lr1 = math.log(p.radii[right])\n", + " ld0 = math.log(p.densities[left])\n", + " ld1 = math.log(p.densities[right])\n", + " fraction = (math.log(TAIL_CUTOFF) - ld0) / (ld1 - ld0)\n", + " return math.exp(lr0 + fraction * (lr1 - lr0))\n", + "\n", + "\n", + "def rho(symbol: str, radius: float) -> float:\n", + " return profile(symbol)._evaluate_bohr(radius)\n", + "\n", + "\n", + "def objective(atom_a: str, atom_b: str, x: float, distance: float) -> float:\n", + " return rho(atom_a, x) + rho(atom_b, distance - x)\n", + "\n", + "\n", + "def continuous_log_density(symbol: str, radius: float) -> float:\n", + " p = profile(symbol)\n", + " if radius <= p.radii[0]:\n", + " return math.log(p.densities[0])\n", + " index = min(bisect_right(p.radii, radius) - 1, len(p.radii) - 2)\n", + " lr0 = math.log(p.radii[index])\n", + " lr1 = math.log(p.radii[index + 1])\n", + " ld0 = math.log(p.densities[index])\n", + " ld1 = math.log(p.densities[index + 1])\n", + " fraction = (math.log(radius) - lr0) / (lr1 - lr0)\n", + " return ld0 + fraction * (ld1 - ld0)\n", + "\n", + "\n", + "def equal_contribution_position(atom_a: str, atom_b: str, distance: float) -> float | None:\n", + " if atom_a == atom_b:\n", + " return distance / 2.0\n", + "\n", + " def difference(x: float) -> float:\n", + " return continuous_log_density(atom_a, x) - continuous_log_density(\n", + " atom_b, distance - x\n", + " )\n", + "\n", + " left_value = difference(0.0)\n", + " right_value = difference(distance)\n", + " if left_value == 0.0:\n", + " return 0.0\n", + " if right_value == 0.0:\n", + " return distance\n", + " if left_value * right_value > 0.0:\n", + " return None\n", + "\n", + " left = 0.0\n", + " right = distance\n", + " for _ in range(100):\n", + " midpoint = (left + right) / 2.0\n", + " value = difference(midpoint)\n", + " if right - left <= 1.0e-10:\n", + " break\n", + " if value > 0.0:\n", + " left = midpoint\n", + " else:\n", + " right = midpoint\n", + " return (left + right) / 2.0\n", + "\n", + "\n", + "def component_values(atom_a: str, atom_b: str, distance: float, x: float):\n", + " value_a = rho(atom_a, x)\n", + " value_b = rho(atom_b, distance - x)\n", + " return value_a, value_b, value_a + value_b\n", + "\n", + "\n", + "def boundary_estimate(atom_a: str, atom_b: str, distance: float) -> StudyEstimate:\n", + " radius_a = cutoff_radius(atom_a)\n", + " radius_b = cutoff_radius(atom_b)\n", + " separation = distance - radius_a - radius_b\n", + "\n", + " if atom_a == atom_b:\n", + " x = distance / 2.0\n", + " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", + " status = \"low_density_gap\" if separation > 0.0 else \"ok\"\n", + " return StudyEstimate(\n", + " \"homonuclear_midpoint\", status, x, value_a, value_b, total, separation\n", + " )\n", + "\n", + " if separation > 0.0:\n", + " x = (radius_a + distance - radius_b) / 2.0\n", + " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", + " return StudyEstimate(\n", + " \"cutoff_gap_midpoint\",\n", + " \"low_density_gap\",\n", + " x,\n", + " value_a,\n", + " value_b,\n", + " total,\n", + " separation,\n", + " )\n", + "\n", + " x = equal_contribution_position(atom_a, atom_b, distance)\n", + " if x is None or not (0.0 < x < distance):\n", + " return StudyEstimate(\n", + " \"none\", \"one_atom_dominates\", None, None, None, None, separation\n", + " )\n", + " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", + " return StudyEstimate(\n", + " \"equal_proatom_density\", \"ok\", x, value_a, value_b, total, separation\n", + " )\n", + "\n", + "\n", + "def overlap_interval(atom_a: str, atom_b: str, distance: float) -> tuple[float, float]:\n", + " return (\n", + " max(0.0, distance - cutoff_radius(atom_b)),\n", + " min(distance, cutoff_radius(atom_a)),\n", + " )\n", + "\n", + "\n", + "def golden_minimize(function, left: float, right: float):\n", + " ratio = (math.sqrt(5.0) - 1.0) / 2.0\n", + " x1 = right - ratio * (right - left)\n", + " x2 = left + ratio * (right - left)\n", + " f1 = function(x1)\n", + " f2 = function(x2)\n", + " for _ in range(70):\n", + " if f1 <= f2:\n", + " right, x2, f2 = x2, x1, f1\n", + " x1 = right - ratio * (right - left)\n", + " f1 = function(x1)\n", + " else:\n", + " left, x1, f1 = x1, x2, f2\n", + " x2 = left + ratio * (right - left)\n", + " f2 = function(x2)\n", + " return (x1, f1) if f1 <= f2 else (x2, f2)\n", + "\n", + "\n", + "def grid_valleys(atom_a: str, atom_b: str, distance: float, step: float):\n", + " left, right = overlap_interval(atom_a, atom_b, distance)\n", + " if right <= left:\n", + " return [], 0\n", + "\n", + " count = max(3, math.ceil((right - left) / step) + 1)\n", + " coordinates = [left + (right - left) * i / (count - 1) for i in range(count)]\n", + " equal = equal_contribution_position(atom_a, atom_b, distance)\n", + " if equal is not None and left < equal < right:\n", + " coordinates.append(equal)\n", + " coordinates = sorted(set(coordinates))\n", + "\n", + " values = [objective(atom_a, atom_b, x, distance) for x in coordinates]\n", + " function = lambda x: objective(atom_a, atom_b, x, distance)\n", + " candidates = []\n", + " for index in range(1, len(coordinates) - 1):\n", + " if values[index] <= values[index - 1] and values[index] <= values[index + 1]:\n", + " candidates.append(\n", + " golden_minimize(\n", + " function, coordinates[index - 1], coordinates[index + 1]\n", + " )\n", + " )\n", + " return candidates, len(coordinates)\n", + "\n", + "\n", + "def coalesce(candidates, resolution: float = PRACTICAL_RESOLUTION):\n", + " if not candidates:\n", + " return []\n", + " ordered = sorted(candidates, key=lambda item: item[0])\n", + " groups: list[list[tuple[float, float]]] = []\n", + " for candidate in ordered:\n", + " if not groups or candidate[0] - groups[-1][-1][0] > resolution:\n", + " groups.append([candidate])\n", + " else:\n", + " groups[-1].append(candidate)\n", + " return [min(group, key=lambda item: item[1]) for group in groups]\n", + "\n", + "\n", + "def selections_compatible(first, second) -> bool:\n", + " if first is None or second is None:\n", + " return first is second\n", + " position_ok = abs(first[0] - second[0]) <= PRACTICAL_RESOLUTION\n", + " density_ok = abs(first[1] - second[1]) <= (\n", + " COMPETITIVE_RELATIVE_GAP * min(first[1], second[1])\n", + " )\n", + " return position_ok and density_ok\n", + "\n", + "\n", + "def practical_minimum_estimate(atom_a: str, atom_b: str, distance: float) -> StudyEstimate:\n", + " separation = distance - cutoff_radius(atom_a) - cutoff_radius(atom_b)\n", + " if atom_a == atom_b:\n", + " x = distance / 2.0\n", + " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", + " status = \"low_density_gap\" if separation > 0.0 else \"ok\"\n", + " return StudyEstimate(\n", + " \"homonuclear_midpoint\",\n", + " status,\n", + " x,\n", + " value_a,\n", + " value_b,\n", + " total,\n", + " separation,\n", + " search_resolution=PRACTICAL_RESOLUTION,\n", + " search_converged=True,\n", + " )\n", + "\n", + " left, right = overlap_interval(atom_a, atom_b, distance)\n", + " if right <= left:\n", + " return StudyEstimate(\n", + " \"none\", \"low_density_gap\", None, None, None, None, separation\n", + " )\n", + "\n", + " all_candidates = []\n", + " pass_best = []\n", + " probes = 0\n", + " for step in (INITIAL_STEP, CONFIRM_STEP):\n", + " candidates, count = grid_valleys(atom_a, atom_b, distance, step)\n", + " probes += count\n", + " all_candidates.extend(candidates)\n", + " pass_best.append(min(candidates, key=lambda item: item[1]) if candidates else None)\n", + "\n", + " need_fallback = not selections_compatible(pass_best[0], pass_best[1])\n", + " if need_fallback:\n", + " candidates, count = grid_valleys(atom_a, atom_b, distance, FALLBACK_STEP)\n", + " probes += count\n", + " all_candidates.extend(candidates)\n", + " pass_best.append(min(candidates, key=lambda item: item[1]) if candidates else None)\n", + "\n", + " resolved = coalesce(all_candidates)\n", + " if not resolved:\n", + " return StudyEstimate(\n", + " \"none\",\n", + " \"no_resolved_interior_minimum\",\n", + " None,\n", + " None,\n", + " None,\n", + " None,\n", + " separation,\n", + " search_resolution=FALLBACK_STEP if need_fallback else CONFIRM_STEP,\n", + " search_converged=not need_fallback,\n", + " probes=probes,\n", + " )\n", + "\n", + " ordered = sorted(resolved, key=lambda item: item[1])\n", + " selected = ordered[0]\n", + " alternative = ordered[1] if len(ordered) > 1 else None\n", + " converged = (\n", + " selections_compatible(pass_best[-2], pass_best[-1])\n", + " if need_fallback\n", + " else True\n", + " )\n", + "\n", + " status = \"ok\"\n", + " relative_gap = None\n", + " if alternative is not None:\n", + " relative_gap = (alternative[1] - selected[1]) / selected[1]\n", + " if relative_gap <= COMPETITIVE_RELATIVE_GAP:\n", + " status = \"ambiguous_competing_minima\"\n", + " if not converged:\n", + " status = \"search_unstable\"\n", + "\n", + " boundary_value = min(\n", + " objective(atom_a, atom_b, 0.0, distance),\n", + " objective(atom_a, atom_b, distance, distance),\n", + " )\n", + " if boundary_value < selected[1] * (1.0 - 2.0e-14):\n", + " status = \"boundary_dominated\"\n", + "\n", + " value_a, value_b, total = component_values(\n", + " atom_a, atom_b, distance, selected[0]\n", + " )\n", + " return StudyEstimate(\n", + " \"promolecular_density_minimum\",\n", + " status,\n", + " selected[0],\n", + " value_a,\n", + " value_b,\n", + " total,\n", + " separation,\n", + " search_resolution=FALLBACK_STEP if need_fallback else CONFIRM_STEP,\n", + " search_converged=converged,\n", + " alternative_position=None if alternative is None else alternative[0],\n", + " relative_depth_gap=relative_gap,\n", + " probes=probes,\n", + " )\n", + "\n", + "\n", + "def dense_reference_minimum(atom_a: str, atom_b: str, distance: float, step=0.001):\n", + " if atom_a == atom_b:\n", + " x = distance / 2.0\n", + " return x, objective(atom_a, atom_b, x, distance)\n", + " candidates, _ = grid_valleys(atom_a, atom_b, distance, step)\n", + " if not candidates:\n", + " return None\n", + " return min(candidates, key=lambda item: item[1])\n", + "\n", + "\n", + "def deterministic_cases(count: int, seed: int = 20260713):\n", + " symbols = [element.symbol for element in ar.iter_elements() if element.z <= 103]\n", + " generator = random.Random(seed)\n", + " cases = []\n", + " for index in range(count):\n", + " atom_a = generator.choice(symbols)\n", + " atom_b = generator.choice(symbols)\n", + " if index % 2:\n", + " distance = generator.uniform(0.2, 12.0)\n", + " else:\n", + " distance = 10.0 ** generator.uniform(math.log10(0.2), math.log10(12.0))\n", + " cases.append((atom_a, atom_b, distance))\n", + " return cases\n", + "\n", + "\n", + "def run_comparison(count=300):\n", + " cases = deterministic_cases(count)\n", + " practical = []\n", + " reference = []\n", + " start = time.perf_counter()\n", + " for case in cases:\n", + " practical.append(practical_minimum_estimate(*case))\n", + " practical_seconds = time.perf_counter() - start\n", + "\n", + " start = time.perf_counter()\n", + " for case in cases:\n", + " reference.append(dense_reference_minimum(*case))\n", + " reference_seconds = time.perf_counter() - start\n", + "\n", + " status_mismatch = 0\n", + " errors = []\n", + " for case, estimate, expected in zip(cases, practical, reference):\n", + " found = estimate.position is not None\n", + " expected_found = expected is not None\n", + " if found != expected_found:\n", + " # A sub-resolution dense-reference minimum can intentionally be absent.\n", + " status_mismatch += 1\n", + " continue\n", + " if found:\n", + " dx = abs(estimate.position - expected[0])\n", + " relative_density = abs(estimate.rho_sum - expected[1]) / expected[1]\n", + " errors.append((dx, relative_density, case, estimate, expected))\n", + "\n", + " return {\n", + " \"cases\": cases,\n", + " \"practical\": practical,\n", + " \"reference\": reference,\n", + " \"practical_seconds\": practical_seconds,\n", + " \"reference_seconds\": reference_seconds,\n", + " \"status_mismatch\": status_mismatch,\n", + " \"errors\": errors,\n", + " }\n" + ] + }, + { + "cell_type": "markdown", + "id": "bedc9faf", + "metadata": {}, + "source": [ + "\n", + "## Cutoff radii across H–Lr\n", + "\n", + "Every packaged profile is monotone over its stored positive segments, so the\n", + "cutoff radius is unique and can be obtained by directly inverting one log–log\n", + "segment. The plot shows that the chosen contour is deliberately diffuse: the\n", + "radii span roughly 1.8–4.2 Å.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e69152d0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:22:30.127567Z", + "iopub.status.busy": "2026-07-14T20:22:30.127316Z", + "iopub.status.idle": "2026-07-14T20:22:30.657102Z", + "shell.execute_reply": "2026-07-14T20:22:30.655974Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Smallest cutoff radius: He = 3.373985 bohr (1.785436 Å)\n", + "Largest cutoff radius: Ba = 7.966363 bohr (4.215618 Å)\n", + "Representative radii:\n", + " H: 4.128012 bohr = 2.184450 Å\n", + " He: 3.373985 bohr = 1.785436 Å\n", + " Li: 6.469571 bohr = 3.423549 Å\n", + " C: 4.895923 bohr = 2.590811 Å\n", + " O: 4.333035 bohr = 2.292943 Å\n", + " Fe: 6.132435 bohr = 3.245145 Å\n", + " U: 7.398370 bohr = 3.915049 Å\n", + " Lr: 7.736203 bohr = 4.093822 Å\n" + ] + }, + { + "data": { + "image/png": "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JUta/L4///wzEhMT8f777+Ps2bP4+uuv0a5dO/z6668mx504cQLdunXDvffei3379mHXrl0YP348PvroIwD6c5VWqzV7rjpx4gR/rho1ahS2bNmC77//HpWVldBoNHjggQfQu3dvbNy4EUePHsXMmTPRu3dvZGRk8N//7Nmz/Bq5cePGYfPmzdi5cycGDx6Ml19+ucnPdebMGVy5cqVJYFdTU4Px48fjp59+QmpqKiZPnowxY8agqqqKP0apVGLGjBno378/tm7disOHD2PatGno37+/2czftWvXMG7cOGzbtg3btm3DgAED8Pbbb5scs3z5cnz22WdYtGgRHn74YRw4cABr167F4cOHMW7cOCxduhRfffUVJkyYgMDAQIwcObLF1460gCWkEbVazQJgH3roIZZlWXbEiBFsbGwsW11dzbIsy+7YsYMFwP7000/8Y5544gnWx8eHPXToEH9bVVUV27t3b3bUqFH8bS+99BJr7m3HPeeOHTv423r37s2OHz++ybF//PEHC4Dt378/m5eXx7Isy9bW1rIVFRVmf56ioiI2Li6OfeaZZ/jbTpw4wQJgf/vttxZ/H//5z39YhULBzpkzh1Wr1SzLsqxOp2Pvv/9+VqFQsJcvX2ZZtuH3Fhsby27ZsoV/fH5+Pnv69GlWJpOx999/P6vValmWZVmtVsvOnDmTlcvlbHp6erNjeO+991hfX1/2+vXr/G0TJ05kU1JSmhx76NAhViKRsI899hh/m1qtZu+66y7Wz8+PzcjI4G8HwMbFxbG7du3ib1u2bBnLMAw7e/Zs9q+//uJvX758OQuAPX78eLNj/eCDD1gA7B133MG/Z/Lz8y0ef/bsWVYul7PLly/nb/vyyy9ZAOzq1av523Jzc9l+/fqxANg//viDv33kyJHs0KFD+f9v3bqVBcDu2bPH5Ptwr89rr73GsizLVlZWshKJhF24cKHJcbm5uS2+HuacOXOGlclk7Ndff83ftnLlShYAe+7cOaufZ8uWLSwA9n//+5/J7VOmTGEBmP1dvvXWWywAq74uXbrU4hj27dvHAmAXL17c5L7NmzezANhvv/22yX1vv/02C4A9e/as1T9vY7t372ZfffXVFo+7fPky6+/vz86YMYP/u2RZlv3uu+9YhmHYffv28bclJSWx9913H////fv3sxKJhJ0/fz6r0+n42998802T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FBTg8OHD8PPz43ev6NevH6ZPn45nn30W6enp8PPzQ2pqKp544gk8/PDDJs85ZcoUPPLII3j88cfRrl07hIaGNjmmsffeew9Tp07FuHHjMGzYMBw/fhxBQUG4+eabcfDgQbt/dj8/P6SkpGDcuHEYMmQIjh8/js2bN+Pjjz82O23W2MCBA/HRRx/hpZdewtmzZ3HjjTfi8OHD2LFjBxYvXsz3frrzzjsxePBgzJgxAw899BB0Oh22b9+OF198sUlANnnyZHz77bd8mxmZTIbnn38eY8aMwYIFC/D2228jLS0NvXv3xr///ou9e/di+fLlTdbFuIpMJsO7776LF198ETU1NejatSv27NmDcePGmSz+BoBnn30We/bswcSJE3H//fcjIiICu3btwty5c1vcHF6hUOCdd97B448/DrVajZSUFOzduxfPPfccvvvuO/646upq3HrrrWjfvj169eqFwMBA/P3337h48WKzW9n5+PhgwYIFeO2111BVVYXOnTtj165dmDhxYpP+aUOHDkVsbCyeffZZfpcFS33sdDodTp06hTFjxuDDDz/ElStXEB0djU2bNqGgoABbtmyxuHuFUGvslixZAo1Gwwc2e/bsgUajAaCfouSm1N5++20cPXoUo0aNwkMPPYT6+np88803uP/++/HQQw85PA5r9O7dG6tWrcJDDz2Effv2YcyYMfD19cXFixdx4MABLF261OLOOBMnTsTSpUvxwgsvYMOGDRgyZAhYlsW5c+dw8uRJrFq1CsnJyYiJicH69esxdepU9OnTBxMnToRcLsfff/+NMWPG8DMUU6ZMwTPPPIMnn3wSycnJfB+75ixYsABpaWkYN24cZs2ahbCwMKxbtw5VVVXYvHmzVcsOjG3btg1qtRq33Xab2fsfffRRzJ49G/3794dUKsX//vc/JCYmYvHixfwxCxcuxJkzZzBixAjMmjULQUFB+PXXX6HVarF69eoWe1gScaDAjjQhkUgwb948k+ajnGeeeQalpaXQarUmxQRBQUHYtGkTjhw5gn/++QdlZWVISUnBQw891KSC8pdffsG6detw4sQJxMXF4eWXX0ZJSQnmzZuHpKQk/ri5c+ciMTERBw4cQGFhIZ9169SpE+bNm2e2cnHSpEk4ffo0Nm3ahOrqajz66KOYMGECfvnlF5NFv9HR0Zg3bx66dOli9e+F68v3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", + "text/plain": [ + "
      " + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "cutoff_rows = []\n", + "for element in ar.iter_elements():\n", + " if element.z <= 103:\n", + " radius_bohr = cutoff_radius(element.symbol)\n", + " cutoff_rows.append(\n", + " (element.z, element.symbol, radius_bohr, radius_bohr * ar.proatoms.BOHR_TO_ANGSTROM)\n", + " )\n", + "\n", + "smallest = min(cutoff_rows, key=lambda row: row[2])\n", + "largest = max(cutoff_rows, key=lambda row: row[2])\n", + "print(\n", + " f\"Smallest cutoff radius: {smallest[1]} = {smallest[2]:.6f} bohr \"\n", + " f\"({smallest[3]:.6f} Å)\"\n", + ")\n", + "print(\n", + " f\"Largest cutoff radius: {largest[1]} = {largest[2]:.6f} bohr \"\n", + " f\"({largest[3]:.6f} Å)\"\n", + ")\n", + "print(\"Representative radii:\")\n", + "for symbol in (\"H\", \"He\", \"Li\", \"C\", \"O\", \"Fe\", \"U\", \"Lr\"):\n", + " row = next(item for item in cutoff_rows if item[1] == symbol)\n", + " print(f\" {symbol:>2}: {row[2]:.6f} bohr = {row[3]:.6f} Å\")\n", + "\n", + "figure = None\n", + "if plt is not None:\n", + " figure, axis = plt.subplots()\n", + " axis.plot([row[0] for row in cutoff_rows], [row[3] for row in cutoff_rows])\n", + " axis.set_xlabel(\"Atomic number\")\n", + " axis.set_ylabel(\"Cutoff radius (Å)\")\n", + " axis.set_title(r\"Neutral-proatom radius at $\\rho=10^{-4}$ electron/bohr$^3$\")\n", + " figure.tight_layout()\n", + " figure.savefig(ASSET_DIR / \"cutoff-radii.png\", dpi=160)\n", + " plt.close(figure)\n", + "else:\n", + " print(\"Matplotlib is unavailable; the cutoff-radius plot was skipped.\")\n", + "\n", + "figure\n" + ] + }, + { + "cell_type": "markdown", + "id": "31b82eb4", + "metadata": {}, + "source": [ + "\n", + "## Representative pairwise results\n", + "\n", + "The same profiles can support two useful but different answers. The boundary\n", + "mode is the recommended geometry-facing default. The minimum mode is retained\n", + "as a Bader-oriented neutral-promolecular proxy and may explicitly decline to\n", + "return a resolved minimum.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b318ce07", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:22:30.660171Z", + "iopub.status.busy": "2026-07-14T20:22:30.659988Z", + "iopub.status.idle": "2026-07-14T20:22:30.668872Z", + "shell.execute_reply": "2026-07-14T20:22:30.667791Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pair R/bohr boundary method boundary x minimum status minimum x\n", + "Li-Li 5.0000 homonuclear_midpoint 2.500000000 ok 2.500000000\n", + "H-U 3.8000 equal_proatom_density 1.184253035 ok 1.191871522\n", + "C-O 1.5000 equal_proatom_density 0.556215252 ok 0.611760239\n", + "H-O 12.0000 cutoff_gap_midpoint 5.897488625 low_density_gap None\n", + "H-U 1.5800 equal_proatom_density 0.017084468 no_resolved_interior_minimum None\n" + ] + } + ], + "source": [ + "\n", + "examples = [\n", + " (\"Li\", \"Li\", 5.0),\n", + " (\"H\", \"U\", 3.8),\n", + " (\"C\", \"O\", 1.5),\n", + " (\"H\", \"O\", 12.0),\n", + " (\"H\", \"U\", 1.58),\n", + "]\n", + "\n", + "header = (\n", + " \"pair\", \"R/bohr\", \"boundary method\", \"boundary x\", \"minimum status\", \"minimum x\"\n", + ")\n", + "print(f\"{header[0]:<8} {header[1]:>9} {header[2]:<25} {header[3]:>12} {header[4]:<31} {header[5]:>12}\")\n", + "for atom_a, atom_b, distance in examples:\n", + " boundary = boundary_estimate(atom_a, atom_b, distance)\n", + " minimum = practical_minimum_estimate(atom_a, atom_b, distance)\n", + " bx = \"None\" if boundary.position is None else f\"{boundary.position:.9f}\"\n", + " mx = \"None\" if minimum.position is None else f\"{minimum.position:.9f}\"\n", + " print(\n", + " f\"{atom_a+'-'+atom_b:<8} {distance:9.4f} {boundary.method:<25} {bx:>12} \"\n", + " f\"{minimum.status:<31} {mx:>12}\"\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "id": "69d51b79", + "metadata": {}, + "source": [ + "\n", + "## Why homonuclear symmetry overrides raw shell minima\n", + "\n", + "For Li–Li at 5 bohr, the exact all-minima investigation found symmetric\n", + "off-centre minima slightly below the midpoint. Returning either off-centre\n", + "point would be a poor pairwise separator for identical atoms. Both revised\n", + "modes therefore return $R/2$ and preserve the raw profile behaviour only as a\n", + "diagnostic.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f8592476", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:22:30.671433Z", + "iopub.status.busy": "2026-07-14T20:22:30.671216Z", + "iopub.status.idle": "2026-07-14T20:22:31.017143Z", + "shell.execute_reply": "2026-07-14T20:22:31.016087Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Raw fine-grid valley coordinates near the internuclear region:\n", + " x=2.118430478685 bohr, rho_sum=0.00654500903481724\n", + " x=2.472517881659 bohr, rho_sum=0.00658890668816022\n", + " x=2.500000037765 bohr, rho_sum=0.00658920833374835\n", + " x=2.527482056015 bohr, rho_sum=0.00658890668816022\n", + " x=2.881569505107 bohr, rho_sum=0.00654500903481724\n", + "Symmetry-selected coordinate: 2.500000000000 bohr\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
      " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "atom_a, atom_b, distance = \"Li\", \"Li\", 5.0\n", + "xs = [1.75 + (3.25 - 1.75) * i / 1600 for i in range(1601)]\n", + "ys = [objective(atom_a, atom_b, x, distance) for x in xs]\n", + "raw_valleys, _ = grid_valleys(atom_a, atom_b, distance, 0.001)\n", + "\n", + "print(\"Raw fine-grid valley coordinates near the internuclear region:\")\n", + "for x, value in raw_valleys:\n", + " if 1.75 <= x <= 3.25:\n", + " print(f\" x={x:.12f} bohr, rho_sum={value:.15g}\")\n", + "print(f\"Symmetry-selected coordinate: {distance/2:.12f} bohr\")\n", + "\n", + "figure = None\n", + "if plt is not None:\n", + " figure, axis = plt.subplots()\n", + " axis.plot(xs, ys, label=r\"$\\rho_{Li}(x)+\\rho_{Li}(R-x)$\")\n", + " shown = [(x, value) for x, value in raw_valleys if 1.75 <= x <= 3.25]\n", + " axis.scatter([item[0] for item in shown], [item[1] for item in shown], label=\"raw local valleys\")\n", + " axis.axvline(distance / 2.0, linestyle=\"--\", label=\"symmetry midpoint\")\n", + " axis.set_xlabel(\"Position from atom A (bohr)\")\n", + " axis.set_ylabel(r\"Promolecular line density (electron/bohr$^3$)\")\n", + " axis.set_title(\"Li–Li at R = 5 bohr\")\n", + " axis.legend()\n", + " figure.tight_layout()\n", + " figure.savefig(ASSET_DIR / \"li-li-symmetry.png\", dpi=160)\n", + " plt.close(figure)\n", + "else:\n", + " print(\"Matplotlib is unavailable; the Li–Li plot was skipped.\")\n", + "\n", + "figure\n" + ] + }, + { + "cell_type": "markdown", + "id": "aa43c79c", + "metadata": {}, + "source": [ + "\n", + "## Boundary and minimum are different scientific definitions\n", + "\n", + "For C–O at 1.5 bohr, the equal-contribution point and the selected promolecular\n", + "minimum differ visibly. This is not a solver failure: the first balances the two\n", + "reference-atom contributions, while the second minimizes their sum.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9f5d836a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:22:31.019467Z", + "iopub.status.busy": "2026-07-14T20:22:31.019254Z", + "iopub.status.idle": "2026-07-14T20:22:31.327019Z", + "shell.execute_reply": "2026-07-14T20:22:31.326098Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Boundary coordinate: 0.556215252014 bohr\n", + "Minimum coordinate: 0.611760239435 bohr\n", + "Coordinate difference: 0.055544987420 bohr\n", + "Boundary component densities: 0.414069174671, 0.414069174631\n", + "Minimum component densities: 0.325849689041, 0.475793723795\n" + ] + }, + { + "data": { + "image/png": "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nj17GDdunPEX99SpU7GxseHhhx8mODiYuLg4Fi9enGtLTG6mTZuGq6srw4YN48SJEyQkJPDLL7/w7rvvMnToULp06VKs+O/14IMPsmvXLpYvX05ycjLHjx9n+PDh9O7d26Rcw4YN0Wq1/PPPPyWeB2727NkAzJw5M99y5XWPXfY0PtndrABz5sxhzpw5nDlzhpSUFMLCwpg2bRpLlizh6aefpnnz5oWqOzExkfnz53Pnzh1CQkIYOXIkKpWKGTNmGMuU1WcvBEhiJyohBwcHNm3axOeff8769euNN+J369aNLVu28Pnnnxe5Zc3Z2Zlt27bx7rvv8v333+Pv74+rqytPPvkkbdu25dSpU3Tu3LmUzuj/PvnkE1xdXalbt26O+ePmzZvHjz/+yLp162jatCkeHh6MGjWKmJgYpk2bVmDdgYGBrFmzhgsXLuDn50fHjh1xcXHhrbfeKlIcK1as4PXXX2fWrFlUr16d3r17U7duXQ4cOICPj485LkO+5s+fzzfffMPKlStp0KABgYGBLF26lJdeeqlY9bm6urJ//34GDx7MK6+8gpeXF40aNWLGjBl0797d2P3u5+fHzp070Wg0tGrVisDAQMLCwnjjjTeAnK1k9/Lx8eHAgQPUq1eP3r174+HhwaxZs3j99ddZsWJFsWLPzZQpU5gyZQrTpk2jevXqPP/888yZM4fGjRublKtZsyaff/45q1atwtHRsdjz2EHW/62xY8fyxx9/5Jh6pzR16dLFOFcfwM8//2zsyr17XsHcTJgwAbVazdixY6levTrNmjVj586dfPvtt0VaueaNN97gzp07NG7cmBYtWqDT6dixYwf169c3limrz14IAJVS2CGBQgghcli/fj2DBg1i27Zt9OzZs7zDEUJUcdJiJ4QQJbBq1SrjqhpCCFHeJLETQohCev755/n333+Ji4sjMjKS9957j2XLlvH666/j6OhY3uEJIYR0xQohRGEdPHiQ2bNnc+TIEeLj42nQoAHPPvssL7zwQnmHJoQQgCR2QgghhBAWQ7pihRBCCCEshCR2QgghhBAWQlveAZQng8HAtWvXcHJyKvGST0IIIYQQhaUoComJidSqVQu12nztbFU6sbt27VqZTKgqhBBCCJGbiIgIateubbb6qnRi5+TkBGRdVGdn53KORoiKy2BQuBaXCkAtVzvUastp4VYUBZ0h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", + "text/plain": [ + "
      " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "atom_a, atom_b, distance = \"C\", \"O\", 1.5\n", + "boundary = boundary_estimate(atom_a, atom_b, distance)\n", + "minimum = practical_minimum_estimate(atom_a, atom_b, distance)\n", + "xs = [distance * i / 1000 for i in range(1001)]\n", + "rho_a_values = [rho(atom_a, x) for x in xs]\n", + "rho_b_values = [rho(atom_b, distance - x) for x in xs]\n", + "total_values = [a + b for a, b in zip(rho_a_values, rho_b_values)]\n", + "\n", + "print(f\"Boundary coordinate: {boundary.position:.12f} bohr\")\n", + "print(f\"Minimum coordinate: {minimum.position:.12f} bohr\")\n", + "print(f\"Coordinate difference: {abs(boundary.position-minimum.position):.12f} bohr\")\n", + "print(f\"Boundary component densities: {boundary.rho_a:.12g}, {boundary.rho_b:.12g}\")\n", + "print(f\"Minimum component densities: {minimum.rho_a:.12g}, {minimum.rho_b:.12g}\")\n", + "\n", + "figure = None\n", + "if plt is not None:\n", + " figure, axis = plt.subplots()\n", + " axis.plot(xs, rho_a_values, label=r\"$\\rho_C(x)$\")\n", + " axis.plot(xs, rho_b_values, label=r\"$\\rho_O(R-x)$\")\n", + " axis.plot(xs, total_values, label=\"sum\")\n", + " axis.axvline(boundary.position, linestyle=\"--\", label=\"proatomic boundary\")\n", + " axis.axvline(minimum.position, linestyle=\":\", label=\"promolecular minimum\")\n", + " axis.set_xlim(0.35, 0.90)\n", + " axis.set_ylim(0.15, 2.8)\n", + " axis.set_xlabel(\"Position from C (bohr)\")\n", + " axis.set_ylabel(r\"Density (electron/bohr$^3$)\")\n", + " axis.set_title(\"C–O interatomic region at R = 1.5 bohr\")\n", + " axis.legend(loc=\"upper right\")\n", + " figure.tight_layout()\n", + " figure.savefig(ASSET_DIR / \"c-o-method-comparison.png\", dpi=160)\n", + " plt.close(figure)\n", + "else:\n", + " print(\"Matplotlib is unavailable; the C–O plot was skipped.\")\n", + "\n", + "figure\n" + ] + }, + { + "cell_type": "markdown", + "id": "442a4cb8", + "metadata": {}, + "source": [ + "\n", + "## Practical minimum search against a slower reference\n", + "\n", + "The production proposal is not an all-minima theorem. It asks whether a valley\n", + "is resolved at 0.01 bohr and checks it at two grid scales, with one finer\n", + "fallback. The comparison below uses 300 deterministic H–Lr cases, plus seven\n", + "adversarial cases discovered during Stage 4A, against a slower 0.001-bohr\n", + "fine-grid search over the same cutoff-bounded interval.\n", + "\n", + "A dense-reference minimum may be intentionally rejected when it is narrower\n", + "than the public resolution or lies immediately beside a nucleus at an extreme\n", + "short distance.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0767eaed", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:22:31.329125Z", + "iopub.status.busy": "2026-07-14T20:22:31.328954Z", + "iopub.status.idle": "2026-07-14T20:22:33.542661Z", + "shell.execute_reply": "2026-07-14T20:22:33.541467Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cases: 307\n", + "Both methods returned a minimum: 298\n", + "Both returned no resolved minimum: 7\n", + "Practical-resolution nonmatches: 2\n", + "Joint results within 0.01 bohr: 298/298\n", + "Largest position difference: 0.0012907 bohr for C-O at R=1.5 bohr\n", + "Largest relative density difference: 5.96583e-06\n", + "Practical search time: 0.244 s\n", + "Slower reference time: 1.076 s\n", + "\n", + "Intentionally unresolved dense-reference minima:\n", + " C-Lu R=0.221730136797: status=no_resolved_interior_minimum, reference_x=0.00284120268288 bohr\n", + " He-Ru R=0.655290907435: status=no_resolved_interior_minimum, reference_x=0.0112558333887 bohr\n" + ] + } + ], + "source": [ + "\n", + "adversarial_cases = [\n", + " (\"Ni\", \"Te\", 1.5897045597171517),\n", + " (\"Al\", \"Pb\", 1.3645306063699802),\n", + " (\"Pr\", \"Re\", 0.7592738817632778),\n", + " (\"C\", \"Lu\", 0.2217301367970158),\n", + " (\"He\", \"Ru\", 0.6552909074349161),\n", + " (\"H\", \"U\", 1.58),\n", + " (\"C\", \"O\", 1.5),\n", + "]\n", + "cases = deterministic_cases(300) + adversarial_cases\n", + "\n", + "start = time.perf_counter()\n", + "practical_results = [practical_minimum_estimate(*case) for case in cases]\n", + "practical_seconds = time.perf_counter() - start\n", + "\n", + "start = time.perf_counter()\n", + "reference_results = [dense_reference_minimum(*case) for case in cases]\n", + "reference_seconds = time.perf_counter() - start\n", + "\n", + "joint_errors = []\n", + "intentional_nonmatches = []\n", + "both_absent = 0\n", + "for case, practical, reference in zip(cases, practical_results, reference_results):\n", + " if practical.position is None and reference is None:\n", + " both_absent += 1\n", + " elif (practical.position is None) != (reference is None):\n", + " intentional_nonmatches.append((case, practical, reference))\n", + " else:\n", + " joint_errors.append(\n", + " (\n", + " abs(practical.position - reference[0]),\n", + " abs(practical.rho_sum - reference[1]) / reference[1],\n", + " case,\n", + " )\n", + " )\n", + "\n", + "max_position = max(joint_errors, key=lambda item: item[0])\n", + "max_density = max(joint_errors, key=lambda item: item[1])\n", + "within_resolution = sum(error[0] <= PRACTICAL_RESOLUTION for error in joint_errors)\n", + "\n", + "print(f\"Cases: {len(cases)}\")\n", + "print(f\"Both methods returned a minimum: {len(joint_errors)}\")\n", + "print(f\"Both returned no resolved minimum: {both_absent}\")\n", + "print(f\"Practical-resolution nonmatches: {len(intentional_nonmatches)}\")\n", + "print(f\"Joint results within 0.01 bohr: {within_resolution}/{len(joint_errors)}\")\n", + "print(\n", + " f\"Largest position difference: {max_position[0]:.6g} bohr \"\n", + " f\"for {max_position[2][0]}-{max_position[2][1]} at R={max_position[2][2]:.6g} bohr\"\n", + ")\n", + "print(f\"Largest relative density difference: {max_density[1]:.6g}\")\n", + "print(f\"Practical search time: {practical_seconds:.3f} s\")\n", + "print(f\"Slower reference time: {reference_seconds:.3f} s\")\n", + "\n", + "if intentional_nonmatches:\n", + " print(\"\\nIntentionally unresolved dense-reference minima:\")\n", + " for case, practical, reference in intentional_nonmatches:\n", + " print(\n", + " f\" {case[0]}-{case[1]} R={case[2]:.12g}: \"\n", + " f\"status={practical.status}, reference_x={reference[0]:.12g} bohr\"\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "id": "b9d1be2c", + "metadata": {}, + "source": [ + "\n", + "## Cached timing comparison\n", + "\n", + "Timing is machine-dependent, so these figures are evidence rather than portable\n", + "thresholds. The important point is the scale difference: the boundary mode\n", + "needs one cached contour calculation plus a monotone bisection, while the\n", + "optional minimum mode remains below about a millisecond per ordinary pair in\n", + "this pure-Python prototype.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6a476f18", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T20:22:33.544904Z", + "iopub.status.busy": "2026-07-14T20:22:33.544722Z", + "iopub.status.idle": "2026-07-14T20:22:40.750962Z", + "shell.execute_reply": "2026-07-14T20:22:40.749824Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "boundary: median 0.0541 ms/pair; runs=[0.05746927700010929, 0.055642846000182544, 0.05410611999991488, 0.05177689599986479, 0.050906101000009585]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " minimum: median 0.7887 ms/pair; runs=[0.8100914009999087, 0.7820286119999764, 0.7892370699998992, 0.7887017269999888, 0.7813305360000413]\n" + ] + } + ], + "source": [ + "\n", + "import statistics\n", + "\n", + "timing_cases = deterministic_cases(1000)\n", + "for case in timing_cases[:30]:\n", + " boundary_estimate(*case)\n", + " practical_minimum_estimate(*case)\n", + "\n", + "for label, function in (\n", + " (\"boundary\", boundary_estimate),\n", + " (\"minimum\", practical_minimum_estimate),\n", + "):\n", + " samples = []\n", + " for _ in range(5):\n", + " start = time.perf_counter()\n", + " [function(*case) for case in timing_cases]\n", + " samples.append((time.perf_counter() - start) * 1000.0 / len(timing_cases))\n", + " print(f\"{label:>8}: median {statistics.median(samples):.4f} ms/pair; runs={samples}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "48e2df3d", + "metadata": {}, + "source": [ + "\n", + "## What the exhaustive Stage 4A work established\n", + "\n", + "The archived Stage 4A design, validation report, and two independent reviews\n", + "remain useful supporting evidence. They showed that an exact “return every\n", + "mathematical local minimum” contract really does require disproportionate\n", + "numerical machinery:\n", + "\n", + "| Case | Exact-topology observation | Product consequence |\n", + "|---|---|---|\n", + "| H–F near a knot | A minimum occurred 27 binary64 coordinates below a knot and needed high-precision derivative signs. | Important only when every microminimum is binding. |\n", + "| C–O exact knot | The stored knot and its immediate neighbor could differ by one density ULP. | Exact-knot topology is below the new 0.01-bohr resolution. |\n", + "| Li–Li, 5 bohr | Five local minima were found; the two off-centre minima were slightly deepest. | A raw minimum can violate the desired homonuclear separator symmetry. |\n", + "| H–U, 1.58 bohr | Fourteen isolated minima were found while a boundary value was lower. | Counting minima does not produce a useful IAS estimate. |\n", + "| At–Bk event collision | Two exact-distinct profile events rounded to one float and caused orientation-dependent candidate counts in the prototype. | Exact event identity is expensive and unnecessary for the revised contract. |\n", + "| Ne–Ne open segment | Ordinary log/exp value error exceeded the previous global-tie budget. | Numerical tie lists require a separate high-precision value model. |\n", + "\n", + "These are real numerical facts, not mistakes to hide. The revised API simply\n", + "places them outside the production semantics: boundary mode is unaffected, and\n", + "minimum mode deliberately coalesces or rejects sub-resolution structure.\n" + ] + }, + { + "cell_type": "markdown", + "id": "9abe91dc", + "metadata": {}, + "source": [ + "\n", + "## Decision recorded by this study\n", + "\n", + "The recommended Stage 4 API has three entry points:\n", + "\n", + "```python\n", + "estimate_proatomic_boundary(...)\n", + "estimate_promolecular_density_minimum(...)\n", + "estimate_ias_position(..., mode=\"boundary\" | \"minimum\")\n", + "```\n", + "\n", + "`boundary` is the default. It is the best fit for stable geometry and\n", + "Laguerre/Voronoi calibration. `minimum` remains available for Bader-oriented\n", + "research, but it searches only the significant-overlap region, returns one\n", + "resolved candidate rather than every local minimum, and reports ambiguity or a\n", + "non-result explicitly.\n", + "\n", + "For identical atoms, both modes return exactly `R/2`. No universal 0.5 Å ban is\n", + "needed: extreme short-distance cases naturally become `one_atom_dominates`,\n", + "`no_resolved_interior_minimum`, or another explicit diagnostic.\n", + "\n", + "The remaining scientific limitation is deliberate: neither neutral-proatom\n", + "mode has yet been calibrated against a curated set of molecular-density QTAIM\n", + "interatomic-surface coordinates.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/05-proatomic-density-and-ias.ipynb b/docs/notebooks/05-proatomic-density-and-ias.ipynb similarity index 99% rename from notebooks/05-proatomic-density-and-ias.ipynb rename to docs/notebooks/05-proatomic-density-and-ias.ipynb index 6446b91..737b28f 100644 --- a/notebooks/05-proatomic-density-and-ias.ipynb +++ b/docs/notebooks/05-proatomic-density-and-ias.ipynb @@ -6,7 +6,7 @@ "source": [ "# Neutral proatomic density and pairwise IAS-proxy workflows\n", "\n", - "This notebook demonstrates the public `atomref 0.2.0` density and pairwise APIs. The profiles are frozen, isolated, neutral, spherical proatoms—not molecular electron densities. The pairwise results are useful reference-atom models, but neither mode is an exact molecular QTAIM zero-flux surface or critical point." + "This notebook demonstrates the public `atomref 0.2.x` density and pairwise APIs. The profiles are frozen, isolated, neutral, spherical proatoms—not molecular electron densities. The pairwise results are useful reference-atom models, but neither mode is an exact molecular QTAIM zero-flux surface or critical point.\n\n**Prerequisites.** Install `atomref[notebook]` for Matplotlib support. Familiarity with scalar function calls and ordinary radial-density plots is sufficient; the scientific assumptions and mode distinction are introduced before use." ] }, { @@ -27,7 +27,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "atomref 0.2.0; matplotlib 3.11.0\n" + "atomref 0.2.1; matplotlib 3.11.0\n" ] } ], @@ -388,7 +388,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Mode selection, statuses, and limitations\n", + "## What this demonstrated: mode selection, statuses, and limitations\n", "\n", "Use `boundary`—the dispatcher default—for a stable neutral-proatom divider in geometry, Voronoi/Laguerre calibration, or related reference-atom workflows. Use `minimum` only when a cutoff-bounded promolecular line-density valley is the intended proxy and a resolution-limited non-result is acceptable. Minimum mode never silently falls back to boundary mode.\n", "\n", @@ -410,5 +410,5 @@ } }, "nbformat": 4, - "nbformat_minor": 5 + "nbformat_minor": 4 } diff --git a/mkdocs.yml b/mkdocs.yml index 2cbdde1..4d0c26d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,6 +8,12 @@ theme: plugins: - search + - mkdocs-jupyter: + execute: false + allow_errors: false + include_source: true + cache: false + custom_mathjax_url: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-AMS_CHTML-full,Safe - mkdocstrings: handlers: python: @@ -33,9 +39,11 @@ nav: - X–H bond length: datasets/xh_bond_length.md - Notebooks: - Overview: guide/notebooks.md - - Quickstart notebook: notebooks/01-quickstart.md - - Policies and assessment notebook: notebooks/02-policies-and-assessment.md - - Custom sets and discovery notebook: notebooks/03-custom-sets-and-discovery.md + - Quickstart notebook: notebooks/01-quickstart.ipynb + - Policies and assessment notebook: notebooks/02-policies-and-assessment.ipynb + - Custom sets and discovery notebook: notebooks/03-custom-sets-and-discovery.ipynb + - IAS method-selection study: notebooks/04-ias-method-selection-study.ipynb + - Proatomic density and IAS: notebooks/05-proatomic-density-and-ias.ipynb - Development: - Architecture: dev/architecture.md - Data curation: dev/data_curation.md diff --git a/notebooks/01-quickstart.ipynb b/notebooks/01-quickstart.ipynb deleted file mode 100644 index 47b58d1..0000000 --- a/notebooks/01-quickstart.ipynb +++ /dev/null @@ -1,89 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# atomref quickstart\n\nThis notebook covers the main public API: element helpers, direct\n`get_*` calls, provenance-carrying `lookup_*` calls, and packaged dataset\ndiscovery.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import atomref as ar\n", - "\n", - "print(ar.get_element('Cl'))\n", - "print(ar.list_quantities())\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "r_c = ar.get_covalent_radius('C')\n", - "r_vdw = ar.get_vdw_radius('O')\n", - "print(r_c)\n", - "print(r_vdw)\n", - "assert r_c == 0.76\n", - "assert r_vdw == 1.50\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lookup = ar.lookup_vdw_radius('Pm')\n", - "print(f\"{lookup.value:.12f}\")\n", - "print(lookup.source)\n", - "print(lookup.resolved_from)\n", - "assert lookup.source == 'transfer_linear'\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "quantity = ar.get_quantity_info('atomic_radius')\n", - "print(quantity.quantity, quantity.domain, quantity.units)\n", - "\n", - "for info in ar.list_dataset_infos('van_der_waals_radius', usage_role='target'):\n", - " print(info.ref.set_id, info.name, info.usage_role)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "vdw = ar.get_radii_set('van_der_waals', 'alvarez2013')\n", - "print(vdw.get('O'))\n", - "\n", - "support = ar.get_builtin_set(ar.DatasetRef('atomic_radius', 'rahm2016'))\n", - "print(support.get('Pm'))\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/02-policies-and-assessment.ipynb b/notebooks/02-policies-and-assessment.ipynb deleted file mode 100644 index dfe2678..0000000 --- a/notebooks/02-policies-and-assessment.ipynb +++ /dev/null @@ -1,97 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Policies and assessment\n", - "\n", - "This notebook shows how `atomref` resolves missing values through ordered\n", - "policy steps and how to inspect policy-level behavior.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import atomref as ar\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "covalent_policy = ar.RadiiPolicy(\n", - " kind='covalent',\n", - " base_set='cordero2008',\n", - " transfers=(\n", - " ar.SubstitutionTransfer(\n", - " source=ar.DatasetRef('covalent_radius', 'csd_legacy_cov')\n", - " ),\n", - " ),\n", - ")\n", - "lookup = ar.lookup_covalent_radius('Bk', policy=covalent_policy)\n", - "print(lookup.source)\n", - "print(f\"{lookup.value:.12f}\")\n", - "print(lookup.resolved_from)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "vdw_policy = ar.RadiiPolicy(\n", - " kind='van_der_waals',\n", - " base_set='alvarez2013',\n", - " transfers=(\n", - " ar.LinearTransfer(\n", - " predictors=(ar.DatasetRef('atomic_radius', 'rahm2016'),)\n", - " ),\n", - " ),\n", - ")\n", - "lookup = ar.lookup_vdw_radius('Pm', policy=vdw_policy)\n", - "print(f\"{lookup.value:.12f}\")\n", - "print(lookup.source)\n", - "print(\n", - " f\"slope={lookup.fit.coefficients[0]:.12f} intercept={lookup.fit.intercept:.12f} n={lookup.fit.n_points}\"\n", - ")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "assessment = ar.assess_radii_policy(\n", - " ['C', 'Xe', 'Pm', 'Bk'],\n", - " policy=vdw_policy,\n", - " detail=True,\n", - ")\n", - "print(assessment.n_base, assessment.n_transfer_linear, assessment.n_missing)\n", - "for row in assessment.per_element:\n", - " value = 'None' if row.lookup.value is None else f\"{row.lookup.value:.12f}\"\n", - " print(row.symbol, row.lookup.source, value)\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/03-custom-sets-and-discovery.ipynb b/notebooks/03-custom-sets-and-discovery.ipynb deleted file mode 100644 index 58f9d92..0000000 --- a/notebooks/03-custom-sets-and-discovery.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Custom sets and dataset discovery\n", - "\n", - "This notebook shows how to define a small user-provided set, plug it into a\n", - "policy, and inspect the packaged dataset catalog.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import atomref as ar\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "custom_cov = ar.ElementScalarSet.from_mapping(\n", - " ref=ar.DatasetRef(\"covalent_radius\", \"demo_user_cov\"),\n", - " values={\"C\": 0.77, \"O\": 0.67},\n", - " name=\"Demo user covalent set\",\n", - " units=\"angstrom\",\n", - " description=\"Example custom set for notebook usage.\",\n", - " notes=(\"Notebook example\",),\n", - ")\n", - "\n", - "policy = ar.RadiiPolicy(\n", - " kind=\"covalent\",\n", - " base_set=custom_cov,\n", - " transfers=(\n", - " ar.SubstitutionTransfer(\n", - " source=ar.DatasetRef(\"covalent_radius\", \"cordero2008\")\n", - " ),\n", - " ),\n", - ")\n", - "\n", - "for symbol in (\"C\", \"O\", \"N\"):\n", - " print(symbol, ar.lookup_covalent_radius(symbol, policy=policy))\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for info in ar.list_radii_set_infos(\"van_der_waals\", usage_role=\"target\"):\n", - " print(info.ref.set_id, info.semantic_class, info.origin_class, info.phase_context)\n", - "\n", - "rahm = ar.get_dataset_info(ar.DatasetRef(\"atomic_radius\", \"rahm2016\"))\n", - "print(rahm.name)\n", - "print(rahm.semantic_class, rahm.phase_context, rahm.usage_role)\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/04-ias-method-selection-study.ipynb b/notebooks/04-ias-method-selection-study.ipynb deleted file mode 100644 index b2d73ce..0000000 --- a/notebooks/04-ias-method-selection-study.ipynb +++ /dev/null @@ -1,1079 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cdde20c5", - "metadata": {}, - "source": [ - "\n", - "# Choosing a practical neutral-proatom IAS proxy\n", - "\n", - "This notebook is supporting information for the Stage 4 numerical decision in\n", - "`atomref`. It compares two pairwise quantities derived from the packaged neutral\n", - "spherical proatomic densities:\n", - "\n", - "1. a **stable proatomic boundary**, based on equal proatomic contributions or a\n", - " midpoint between fixed low-density contours;\n", - "2. an optional **promolecular line-density minimum**, searched only where both\n", - " proatomic contributions remain above the cutoff and only to a declared\n", - " practical spatial resolution.\n", - "\n", - "The notebook does **not** implement the public API and does not claim to locate\n", - "an exact QTAIM zero-flux surface. Its helper functions are research prototypes\n", - "used to test the revised contract.\n" - ] - }, - { - "cell_type": "markdown", - "id": "59023f54", - "metadata": {}, - "source": [ - "\n", - "## Numerical policy examined here\n", - "\n", - "The per-atom neutral-tail cutoff is\n", - "\n", - "\\[\n", - "\\rho_c = 10^{-4}\\ \\mathrm{electron/bohr^3}.\n", - "\\]\n", - "\n", - "The default boundary mode uses homonuclear symmetry, equal proatomic\n", - "contributions in the overlap region, and the midpoint between cutoff contours\n", - "when a low-density gap opens. The optional minimum mode searches only the\n", - "interval where both contributions are at least \\(\\rho_c\\), and coalesces\n", - "features below `0.01 bohr` (about `0.0053 Å`).\n" - ] - }, - { - "cell_type": "markdown", - "id": "fcdd3855", - "metadata": {}, - "source": [ - "## Scientific context and references\n", - "\n", - "The two modes answer different questions.\n", - "\n", - "- QTAIM defines atomic basins through zero-flux surfaces of the **molecular**\n", - " electron density; a critical point on an internuclear path is therefore a\n", - " molecular-density object, not something that neutral spherical proatoms can\n", - " reproduce exactly. See R. F. W. Bader, *Chemical Reviews* **91** (1991),\n", - " 893–928, [doi:10.1021/cr00005a013](https://doi.org/10.1021/cr00005a013).\n", - "- Hirshfeld's stockholder construction assigns molecular density in proportion\n", - " to free-atom reference densities. In the present two-proatom line model,\n", - " equal neutral-proatom densities therefore give equal pairwise stockholder\n", - " contributions. See F. L. Hirshfeld, *Theoretica Chimica Acta* **44** (1977),\n", - " 129–138, [doi:10.1007/BF00549096](https://doi.org/10.1007/BF00549096).\n", - "- Radial-density constructions can be useful atom/bond models, but they remain\n", - " model definitions that need comparison with molecular-density results. See\n", - " P. L. Warburton, R. A. Poirier, and D. Nippard, *J. Phys. Chem. A* **115**\n", - " (2011), 852–867, [doi:10.1021/jp1093417](https://doi.org/10.1021/jp1093417).\n", - "\n", - "These references motivate the interpretation. The numerical experiments below\n", - "justify only the implementation policy for the supplied neutral-proatom data;\n", - "they do not validate either mode as an exact QTAIM surface.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "51dd32b7", - "metadata": { - "execution": { - "iopub.execute_input": "2026-07-13T17:50:59.619558Z", - "iopub.status.busy": "2026-07-13T17:50:59.619410Z", - "iopub.status.idle": "2026-07-13T17:50:59.929755Z", - "shell.execute_reply": "2026-07-13T17:50:59.928308Z" - } - }, - "outputs": [], - "source": [ - "from __future__ import annotations\n", - "\n", - "from bisect import bisect_right\n", - "from dataclasses import dataclass\n", - "from functools import lru_cache\n", - "import math\n", - "import random\n", - "import time\n", - "\n", - "from pathlib import Path\n", - "import sys\n", - "\n", - "ROOT = Path.cwd().resolve()\n", - "if not (ROOT / \"src\" / \"atomref\").is_dir():\n", - " for candidate in (ROOT.parent, ROOT.parent.parent):\n", - " if (candidate / \"src\" / \"atomref\").is_dir():\n", - " ROOT = candidate\n", - " break\n", - "if not (ROOT / \"src\" / \"atomref\").is_dir():\n", - " raise RuntimeError(\"Run this notebook from the atomref repository checkout\")\n", - "sys.path.insert(0, str(ROOT / \"src\"))\n", - "\n", - "import atomref as ar\n", - "\n", - "try:\n", - " import matplotlib.pyplot as plt\n", - "except ImportError: # The package itself has no plotting dependency.\n", - " plt = None\n", - "\n", - "ASSET_DIR = ROOT / \"docs\" / \"assets\" / \"ias-method-study\"\n", - "ASSET_DIR.mkdir(parents=True, exist_ok=True)\n", - "\n", - "TAIL_CUTOFF = 1.0e-4\n", - "PRACTICAL_RESOLUTION = 0.01\n", - "INITIAL_STEP = 0.02\n", - "CONFIRM_STEP = 0.01\n", - "FALLBACK_STEP = 0.005\n", - "COMPETITIVE_RELATIVE_GAP = 1.0e-4\n", - "\n", - "\n", - "@dataclass(frozen=True)\n", - "class StudyEstimate:\n", - " method: str\n", - " status: str\n", - " position: float | None\n", - " rho_a: float | None\n", - " rho_b: float | None\n", - " rho_sum: float | None\n", - " contour_separation: float\n", - " search_resolution: float | None = None\n", - " search_converged: bool | None = None\n", - " alternative_position: float | None = None\n", - " relative_depth_gap: float | None = None\n", - " probes: int = 0\n", - "\n", - "\n", - "@lru_cache(maxsize=None)\n", - "def profile(symbol: str):\n", - " result = ar.get_proatomic_density_profile(symbol)\n", - " if result is None:\n", - " raise ValueError(f\"missing profile: {symbol}\")\n", - " return result\n", - "\n", - "\n", - "@lru_cache(maxsize=None)\n", - "def cutoff_radius(symbol: str) -> float:\n", - " p = profile(symbol)\n", - " right = next(i for i, value in enumerate(p.densities) if value <= TAIL_CUTOFF)\n", - " if p.densities[right] == TAIL_CUTOFF:\n", - " return p.radii[right]\n", - " left = right - 1\n", - " lr0 = math.log(p.radii[left])\n", - " lr1 = math.log(p.radii[right])\n", - " ld0 = math.log(p.densities[left])\n", - " ld1 = math.log(p.densities[right])\n", - " fraction = (math.log(TAIL_CUTOFF) - ld0) / (ld1 - ld0)\n", - " return math.exp(lr0 + fraction * (lr1 - lr0))\n", - "\n", - "\n", - "def rho(symbol: str, radius: float) -> float:\n", - " return profile(symbol)._evaluate_bohr(radius)\n", - "\n", - "\n", - "def objective(atom_a: str, atom_b: str, x: float, distance: float) -> float:\n", - " return rho(atom_a, x) + rho(atom_b, distance - x)\n", - "\n", - "\n", - "def continuous_log_density(symbol: str, radius: float) -> float:\n", - " p = profile(symbol)\n", - " if radius <= p.radii[0]:\n", - " return math.log(p.densities[0])\n", - " index = min(bisect_right(p.radii, radius) - 1, len(p.radii) - 2)\n", - " lr0 = math.log(p.radii[index])\n", - " lr1 = math.log(p.radii[index + 1])\n", - " ld0 = math.log(p.densities[index])\n", - " ld1 = math.log(p.densities[index + 1])\n", - " fraction = (math.log(radius) - lr0) / (lr1 - lr0)\n", - " return ld0 + fraction * (ld1 - ld0)\n", - "\n", - "\n", - "def equal_contribution_position(atom_a: str, atom_b: str, distance: float) -> float | None:\n", - " if atom_a == atom_b:\n", - " return distance / 2.0\n", - "\n", - " def difference(x: float) -> float:\n", - " return continuous_log_density(atom_a, x) - continuous_log_density(\n", - " atom_b, distance - x\n", - " )\n", - "\n", - " left_value = difference(0.0)\n", - " right_value = difference(distance)\n", - " if left_value == 0.0:\n", - " return 0.0\n", - " if right_value == 0.0:\n", - " return distance\n", - " if left_value * right_value > 0.0:\n", - " return None\n", - "\n", - " left = 0.0\n", - " right = distance\n", - " for _ in range(100):\n", - " midpoint = (left + right) / 2.0\n", - " value = difference(midpoint)\n", - " if right - left <= 1.0e-10:\n", - " break\n", - " if value > 0.0:\n", - " left = midpoint\n", - " else:\n", - " right = midpoint\n", - " return (left + right) / 2.0\n", - "\n", - "\n", - "def component_values(atom_a: str, atom_b: str, distance: float, x: float):\n", - " value_a = rho(atom_a, x)\n", - " value_b = rho(atom_b, distance - x)\n", - " return value_a, value_b, value_a + value_b\n", - "\n", - "\n", - "def boundary_estimate(atom_a: str, atom_b: str, distance: float) -> StudyEstimate:\n", - " radius_a = cutoff_radius(atom_a)\n", - " radius_b = cutoff_radius(atom_b)\n", - " separation = distance - radius_a - radius_b\n", - "\n", - " if atom_a == atom_b:\n", - " x = distance / 2.0\n", - " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", - " status = \"low_density_gap\" if separation > 0.0 else \"ok\"\n", - " return StudyEstimate(\n", - " \"homonuclear_midpoint\", status, x, value_a, value_b, total, separation\n", - " )\n", - "\n", - " if separation > 0.0:\n", - " x = (radius_a + distance - radius_b) / 2.0\n", - " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", - " return StudyEstimate(\n", - " \"cutoff_gap_midpoint\",\n", - " \"low_density_gap\",\n", - " x,\n", - " value_a,\n", - " value_b,\n", - " total,\n", - " separation,\n", - " )\n", - "\n", - " x = equal_contribution_position(atom_a, atom_b, distance)\n", - " if x is None or not (0.0 < x < distance):\n", - " return StudyEstimate(\n", - " \"none\", \"one_atom_dominates\", None, None, None, None, separation\n", - " )\n", - " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", - " return StudyEstimate(\n", - " \"equal_proatom_density\", \"ok\", x, value_a, value_b, total, separation\n", - " )\n", - "\n", - "\n", - "def overlap_interval(atom_a: str, atom_b: str, distance: float) -> tuple[float, float]:\n", - " return (\n", - " max(0.0, distance - cutoff_radius(atom_b)),\n", - " min(distance, cutoff_radius(atom_a)),\n", - " )\n", - "\n", - "\n", - "def golden_minimize(function, left: float, right: float):\n", - " ratio = (math.sqrt(5.0) - 1.0) / 2.0\n", - " x1 = right - ratio * (right - left)\n", - " x2 = left + ratio * (right - left)\n", - " f1 = function(x1)\n", - " f2 = function(x2)\n", - " for _ in range(70):\n", - " if f1 <= f2:\n", - " right, x2, f2 = x2, x1, f1\n", - " x1 = right - ratio * (right - left)\n", - " f1 = function(x1)\n", - " else:\n", - " left, x1, f1 = x1, x2, f2\n", - " x2 = left + ratio * (right - left)\n", - " f2 = function(x2)\n", - " return (x1, f1) if f1 <= f2 else (x2, f2)\n", - "\n", - "\n", - "def grid_valleys(atom_a: str, atom_b: str, distance: float, step: float):\n", - " left, right = overlap_interval(atom_a, atom_b, distance)\n", - " if right <= left:\n", - " return [], 0\n", - "\n", - " count = max(3, math.ceil((right - left) / step) + 1)\n", - " coordinates = [left + (right - left) * i / (count - 1) for i in range(count)]\n", - " equal = equal_contribution_position(atom_a, atom_b, distance)\n", - " if equal is not None and left < equal < right:\n", - " coordinates.append(equal)\n", - " coordinates = sorted(set(coordinates))\n", - "\n", - " values = [objective(atom_a, atom_b, x, distance) for x in coordinates]\n", - " function = lambda x: objective(atom_a, atom_b, x, distance)\n", - " candidates = []\n", - " for index in range(1, len(coordinates) - 1):\n", - " if values[index] <= values[index - 1] and values[index] <= values[index + 1]:\n", - " candidates.append(\n", - " golden_minimize(\n", - " function, coordinates[index - 1], coordinates[index + 1]\n", - " )\n", - " )\n", - " return candidates, len(coordinates)\n", - "\n", - "\n", - "def coalesce(candidates, resolution: float = PRACTICAL_RESOLUTION):\n", - " if not candidates:\n", - " return []\n", - " ordered = sorted(candidates, key=lambda item: item[0])\n", - " groups: list[list[tuple[float, float]]] = []\n", - " for candidate in ordered:\n", - " if not groups or candidate[0] - groups[-1][-1][0] > resolution:\n", - " groups.append([candidate])\n", - " else:\n", - " groups[-1].append(candidate)\n", - " return [min(group, key=lambda item: item[1]) for group in groups]\n", - "\n", - "\n", - "def selections_compatible(first, second) -> bool:\n", - " if first is None or second is None:\n", - " return first is second\n", - " position_ok = abs(first[0] - second[0]) <= PRACTICAL_RESOLUTION\n", - " density_ok = abs(first[1] - second[1]) <= (\n", - " COMPETITIVE_RELATIVE_GAP * min(first[1], second[1])\n", - " )\n", - " return position_ok and density_ok\n", - "\n", - "\n", - "def practical_minimum_estimate(atom_a: str, atom_b: str, distance: float) -> StudyEstimate:\n", - " separation = distance - cutoff_radius(atom_a) - cutoff_radius(atom_b)\n", - " if atom_a == atom_b:\n", - " x = distance / 2.0\n", - " value_a, value_b, total = component_values(atom_a, atom_b, distance, x)\n", - " status = \"low_density_gap\" if separation > 0.0 else \"ok\"\n", - " return StudyEstimate(\n", - " \"homonuclear_midpoint\",\n", - " status,\n", - " x,\n", - " value_a,\n", - " value_b,\n", - " total,\n", - " separation,\n", - " search_resolution=PRACTICAL_RESOLUTION,\n", - " search_converged=True,\n", - " )\n", - "\n", - " left, right = overlap_interval(atom_a, atom_b, distance)\n", - " if right <= left:\n", - " return StudyEstimate(\n", - " \"none\", \"low_density_gap\", None, None, None, None, separation\n", - " )\n", - "\n", - " all_candidates = []\n", - " pass_best = []\n", - " probes = 0\n", - " for step in (INITIAL_STEP, CONFIRM_STEP):\n", - " candidates, count = grid_valleys(atom_a, atom_b, distance, step)\n", - " probes += count\n", - " all_candidates.extend(candidates)\n", - " pass_best.append(min(candidates, key=lambda item: item[1]) if candidates else None)\n", - "\n", - " need_fallback = not selections_compatible(pass_best[0], pass_best[1])\n", - " if need_fallback:\n", - " candidates, count = grid_valleys(atom_a, atom_b, distance, FALLBACK_STEP)\n", - " probes += count\n", - " all_candidates.extend(candidates)\n", - " pass_best.append(min(candidates, key=lambda item: item[1]) if candidates else None)\n", - "\n", - " resolved = coalesce(all_candidates)\n", - " if not resolved:\n", - " return StudyEstimate(\n", - " \"none\",\n", - " \"no_resolved_interior_minimum\",\n", - " None,\n", - " None,\n", - " None,\n", - " None,\n", - " separation,\n", - " search_resolution=FALLBACK_STEP if need_fallback else CONFIRM_STEP,\n", - " search_converged=not need_fallback,\n", - " probes=probes,\n", - " )\n", - "\n", - " ordered = sorted(resolved, key=lambda item: item[1])\n", - " selected = ordered[0]\n", - " alternative = ordered[1] if len(ordered) > 1 else None\n", - " converged = (\n", - " selections_compatible(pass_best[-2], pass_best[-1])\n", - " if need_fallback\n", - " else True\n", - " )\n", - "\n", - " status = \"ok\"\n", - " relative_gap = None\n", - " if alternative is not None:\n", - " relative_gap = (alternative[1] - selected[1]) / selected[1]\n", - " if relative_gap <= COMPETITIVE_RELATIVE_GAP:\n", - " status = \"ambiguous_competing_minima\"\n", - " if not converged:\n", - " status = \"search_unstable\"\n", - "\n", - " boundary_value = min(\n", - " objective(atom_a, atom_b, 0.0, distance),\n", - " objective(atom_a, atom_b, distance, distance),\n", - " )\n", - " if boundary_value < selected[1] * (1.0 - 2.0e-14):\n", - " status = \"boundary_dominated\"\n", - "\n", - " value_a, value_b, total = component_values(\n", - " atom_a, atom_b, distance, selected[0]\n", - " )\n", - " return StudyEstimate(\n", - " \"promolecular_density_minimum\",\n", - " status,\n", - " selected[0],\n", - " value_a,\n", - " value_b,\n", - " total,\n", - " separation,\n", - " search_resolution=FALLBACK_STEP if need_fallback else CONFIRM_STEP,\n", - " search_converged=converged,\n", - " alternative_position=None if alternative is None else alternative[0],\n", - " relative_depth_gap=relative_gap,\n", - " probes=probes,\n", - " )\n", - "\n", - "\n", - "def dense_reference_minimum(atom_a: str, atom_b: str, distance: float, step=0.001):\n", - " if atom_a == atom_b:\n", - " x = distance / 2.0\n", - " return x, objective(atom_a, atom_b, x, distance)\n", - " candidates, _ = grid_valleys(atom_a, atom_b, distance, step)\n", - " if not candidates:\n", - " return None\n", - " return min(candidates, key=lambda item: item[1])\n", - "\n", - "\n", - "def deterministic_cases(count: int, seed: int = 20260713):\n", - " symbols = [element.symbol for element in ar.iter_elements() if element.z <= 103]\n", - " generator = random.Random(seed)\n", - " cases = []\n", - " for index in range(count):\n", - " atom_a = generator.choice(symbols)\n", - " atom_b = generator.choice(symbols)\n", - " if index % 2:\n", - " distance = generator.uniform(0.2, 12.0)\n", - " else:\n", - " distance = 10.0 ** generator.uniform(math.log10(0.2), math.log10(12.0))\n", - " cases.append((atom_a, atom_b, distance))\n", - " return cases\n", - "\n", - "\n", - "def run_comparison(count=300):\n", - " cases = deterministic_cases(count)\n", - " practical = []\n", - " reference = []\n", - " start = time.perf_counter()\n", - " for case in cases:\n", - " practical.append(practical_minimum_estimate(*case))\n", - " practical_seconds = time.perf_counter() - start\n", - "\n", - " start = time.perf_counter()\n", - " for case in cases:\n", - " reference.append(dense_reference_minimum(*case))\n", - " reference_seconds = time.perf_counter() - start\n", - "\n", - " status_mismatch = 0\n", - " errors = []\n", - " for case, estimate, expected in zip(cases, practical, reference):\n", - " found = estimate.position is not None\n", - " expected_found = expected is not None\n", - " if found != expected_found:\n", - " # A sub-resolution dense-reference minimum can intentionally be absent.\n", - " status_mismatch += 1\n", - " continue\n", - " if found:\n", - " dx = abs(estimate.position - expected[0])\n", - " relative_density = abs(estimate.rho_sum - expected[1]) / expected[1]\n", - " errors.append((dx, relative_density, case, estimate, expected))\n", - "\n", - " return {\n", - " \"cases\": cases,\n", - " \"practical\": practical,\n", - " \"reference\": reference,\n", - " \"practical_seconds\": practical_seconds,\n", - " \"reference_seconds\": reference_seconds,\n", - " \"status_mismatch\": status_mismatch,\n", - " \"errors\": errors,\n", - " }\n" - ] - }, - { - "cell_type": "markdown", - "id": "bedc9faf", - "metadata": {}, - "source": [ - "\n", - "## Cutoff radii across H–Lr\n", - "\n", - "Every packaged profile is monotone over its stored positive segments, so the\n", - "cutoff radius is unique and can be obtained by directly inverting one log–log\n", - "segment. The plot shows that the chosen contour is deliberately diffuse: the\n", - "radii span roughly 1.8–4.2 Å.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e69152d0", - "metadata": { - "execution": { - "iopub.execute_input": "2026-07-13T17:50:59.932778Z", - "iopub.status.busy": "2026-07-13T17:50:59.932476Z", - "iopub.status.idle": "2026-07-13T17:51:00.494663Z", - "shell.execute_reply": "2026-07-13T17:51:00.493410Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Smallest cutoff radius: He = 3.373985 bohr (1.785436 Å)\n", - "Largest cutoff radius: Ba = 7.966363 bohr (4.215618 Å)\n", - "Representative radii:\n", - " H: 4.128012 bohr = 2.184450 Å\n", - " He: 3.373985 bohr = 1.785436 Å\n", - " Li: 6.469571 bohr = 3.423549 Å\n", - " C: 4.895923 bohr = 2.590811 Å\n", - " O: 4.333035 bohr = 2.292943 Å\n", - " Fe: 6.132435 bohr = 3.245145 Å\n", - " U: 7.398370 bohr = 3.915049 Å\n", - " Lr: 7.736203 bohr = 4.093822 Å\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_7667/2764722380.py:32: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", - " plt.show()\n" - ] - } - ], - "source": [ - "\n", - "cutoff_rows = []\n", - "for element in ar.iter_elements():\n", - " if element.z <= 103:\n", - " radius_bohr = cutoff_radius(element.symbol)\n", - " cutoff_rows.append(\n", - " (element.z, element.symbol, radius_bohr, radius_bohr * ar.proatoms.BOHR_TO_ANGSTROM)\n", - " )\n", - "\n", - "smallest = min(cutoff_rows, key=lambda row: row[2])\n", - "largest = max(cutoff_rows, key=lambda row: row[2])\n", - "print(\n", - " f\"Smallest cutoff radius: {smallest[1]} = {smallest[2]:.6f} bohr \"\n", - " f\"({smallest[3]:.6f} Å)\"\n", - ")\n", - "print(\n", - " f\"Largest cutoff radius: {largest[1]} = {largest[2]:.6f} bohr \"\n", - " f\"({largest[3]:.6f} Å)\"\n", - ")\n", - "print(\"Representative radii:\")\n", - "for symbol in (\"H\", \"He\", \"Li\", \"C\", \"O\", \"Fe\", \"U\", \"Lr\"):\n", - " row = next(item for item in cutoff_rows if item[1] == symbol)\n", - " print(f\" {symbol:>2}: {row[2]:.6f} bohr = {row[3]:.6f} Å\")\n", - "\n", - "if plt is not None:\n", - " figure, axis = plt.subplots()\n", - " axis.plot([row[0] for row in cutoff_rows], [row[3] for row in cutoff_rows])\n", - " axis.set_xlabel(\"Atomic number\")\n", - " axis.set_ylabel(\"Cutoff radius (Å)\")\n", - " axis.set_title(r\"Neutral-proatom radius at $\\rho=10^{-4}$ electron/bohr$^3$\")\n", - " figure.tight_layout()\n", - " figure.savefig(ASSET_DIR / \"cutoff-radii.png\", dpi=160)\n", - " plt.show()\n", - "else:\n", - " print(\"Matplotlib is unavailable; the cutoff-radius plot was skipped.\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "31b82eb4", - "metadata": {}, - "source": [ - "\n", - "## Representative pairwise results\n", - "\n", - "The same profiles can support two useful but different answers. The boundary\n", - "mode is the recommended geometry-facing default. The minimum mode is retained\n", - "as a Bader-oriented neutral-promolecular proxy and may explicitly decline to\n", - "return a resolved minimum.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "b318ce07", - "metadata": { - "execution": { - "iopub.execute_input": "2026-07-13T17:51:00.496829Z", - "iopub.status.busy": "2026-07-13T17:51:00.496644Z", - "iopub.status.idle": "2026-07-13T17:51:00.503842Z", - "shell.execute_reply": "2026-07-13T17:51:00.502753Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "pair R/bohr boundary method boundary x minimum status minimum x\n", - "Li-Li 5.0000 homonuclear_midpoint 2.500000000 ok 2.500000000\n", - "H-U 3.8000 equal_proatom_density 1.184253035 ok 1.191871522\n", - "C-O 1.5000 equal_proatom_density 0.556215252 ok 0.611760239\n", - "H-O 12.0000 cutoff_gap_midpoint 5.897488625 low_density_gap None\n", - "H-U 1.5800 equal_proatom_density 0.017084468 no_resolved_interior_minimum None\n" - ] - } - ], - "source": [ - "\n", - "examples = [\n", - " (\"Li\", \"Li\", 5.0),\n", - " (\"H\", \"U\", 3.8),\n", - " (\"C\", \"O\", 1.5),\n", - " (\"H\", \"O\", 12.0),\n", - " (\"H\", \"U\", 1.58),\n", - "]\n", - "\n", - "header = (\n", - " \"pair\", \"R/bohr\", \"boundary method\", \"boundary x\", \"minimum status\", \"minimum x\"\n", - ")\n", - "print(f\"{header[0]:<8} {header[1]:>9} {header[2]:<25} {header[3]:>12} {header[4]:<31} {header[5]:>12}\")\n", - "for atom_a, atom_b, distance in examples:\n", - " boundary = boundary_estimate(atom_a, atom_b, distance)\n", - " minimum = practical_minimum_estimate(atom_a, atom_b, distance)\n", - " bx = \"None\" if boundary.position is None else f\"{boundary.position:.9f}\"\n", - " mx = \"None\" if minimum.position is None else f\"{minimum.position:.9f}\"\n", - " print(\n", - " f\"{atom_a+'-'+atom_b:<8} {distance:9.4f} {boundary.method:<25} {bx:>12} \"\n", - " f\"{minimum.status:<31} {mx:>12}\"\n", - " )\n" - ] - }, - { - "cell_type": "markdown", - "id": "69d51b79", - "metadata": {}, - "source": [ - "\n", - "## Why homonuclear symmetry overrides raw shell minima\n", - "\n", - "For Li–Li at 5 bohr, the exact all-minima investigation found symmetric\n", - "off-centre minima slightly below the midpoint. Returning either off-centre\n", - "point would be a poor pairwise separator for identical atoms. Both revised\n", - "modes therefore return \\(R/2\\) and preserve the raw profile behaviour only as a\n", - "diagnostic.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "f8592476", - "metadata": { - "execution": { - "iopub.execute_input": "2026-07-13T17:51:00.505976Z", - "iopub.status.busy": "2026-07-13T17:51:00.505758Z", - "iopub.status.idle": "2026-07-13T17:51:00.724501Z", - "shell.execute_reply": "2026-07-13T17:51:00.723118Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Raw fine-grid valley coordinates near the internuclear region:\n", - " x=2.118430478685 bohr, rho_sum=0.00654500903481724\n", - " x=2.472517881659 bohr, rho_sum=0.00658890668816022\n", - " x=2.500000037765 bohr, rho_sum=0.00658920833374835\n", - " x=2.527482056015 bohr, rho_sum=0.00658890668816022\n", - " x=2.881569505107 bohr, rho_sum=0.00654500903481724\n", - "Symmetry-selected coordinate: 2.500000000000 bohr\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_7667/2275166722.py:24: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", - " plt.show()\n" - ] - } - ], - "source": [ - "\n", - "atom_a, atom_b, distance = \"Li\", \"Li\", 5.0\n", - "xs = [1.75 + (3.25 - 1.75) * i / 1600 for i in range(1601)]\n", - "ys = [objective(atom_a, atom_b, x, distance) for x in xs]\n", - "raw_valleys, _ = grid_valleys(atom_a, atom_b, distance, 0.001)\n", - "\n", - "print(\"Raw fine-grid valley coordinates near the internuclear region:\")\n", - "for x, value in raw_valleys:\n", - " if 1.75 <= x <= 3.25:\n", - " print(f\" x={x:.12f} bohr, rho_sum={value:.15g}\")\n", - "print(f\"Symmetry-selected coordinate: {distance/2:.12f} bohr\")\n", - "\n", - "if plt is not None:\n", - " figure, axis = plt.subplots()\n", - " axis.plot(xs, ys, label=r\"$\\rho_{Li}(x)+\\rho_{Li}(R-x)$\")\n", - " shown = [(x, value) for x, value in raw_valleys if 1.75 <= x <= 3.25]\n", - " axis.scatter([item[0] for item in shown], [item[1] for item in shown], label=\"raw local valleys\")\n", - " axis.axvline(distance / 2.0, linestyle=\"--\", label=\"symmetry midpoint\")\n", - " axis.set_xlabel(\"Position from atom A (bohr)\")\n", - " axis.set_ylabel(r\"Promolecular line density (electron/bohr$^3$)\")\n", - " axis.set_title(\"Li–Li at R = 5 bohr\")\n", - " axis.legend()\n", - " figure.tight_layout()\n", - " figure.savefig(ASSET_DIR / \"li-li-symmetry.png\", dpi=160)\n", - " plt.show()\n", - "else:\n", - " print(\"Matplotlib is unavailable; the Li–Li plot was skipped.\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "aa43c79c", - "metadata": {}, - "source": [ - "\n", - "## Boundary and minimum are different scientific definitions\n", - "\n", - "For C–O at 1.5 bohr, the equal-contribution point and the selected promolecular\n", - "minimum differ visibly. This is not a solver failure: the first balances the two\n", - "reference-atom contributions, while the second minimizes their sum.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "9f5d836a", - "metadata": { - "execution": { - "iopub.execute_input": "2026-07-13T17:51:00.727097Z", - "iopub.status.busy": "2026-07-13T17:51:00.726871Z", - "iopub.status.idle": "2026-07-13T17:51:00.926802Z", - "shell.execute_reply": "2026-07-13T17:51:00.925663Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Boundary coordinate: 0.556215252014 bohr\n", - "Minimum coordinate: 0.611760239435 bohr\n", - "Coordinate difference: 0.055544987420 bohr\n", - "Boundary component densities: 0.414069174671, 0.414069174631\n", - "Minimum component densities: 0.325849689041, 0.475793723795\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_7667/4039951775.py:30: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", - " plt.show()\n" - ] - } - ], - "source": [ - "\n", - "atom_a, atom_b, distance = \"C\", \"O\", 1.5\n", - "boundary = boundary_estimate(atom_a, atom_b, distance)\n", - "minimum = practical_minimum_estimate(atom_a, atom_b, distance)\n", - "xs = [distance * i / 1000 for i in range(1001)]\n", - "rho_a_values = [rho(atom_a, x) for x in xs]\n", - "rho_b_values = [rho(atom_b, distance - x) for x in xs]\n", - "total_values = [a + b for a, b in zip(rho_a_values, rho_b_values)]\n", - "\n", - "print(f\"Boundary coordinate: {boundary.position:.12f} bohr\")\n", - "print(f\"Minimum coordinate: {minimum.position:.12f} bohr\")\n", - "print(f\"Coordinate difference: {abs(boundary.position-minimum.position):.12f} bohr\")\n", - "print(f\"Boundary component densities: {boundary.rho_a:.12g}, {boundary.rho_b:.12g}\")\n", - "print(f\"Minimum component densities: {minimum.rho_a:.12g}, {minimum.rho_b:.12g}\")\n", - "\n", - "if plt is not None:\n", - " figure, axis = plt.subplots()\n", - " axis.plot(xs, rho_a_values, label=r\"$\\rho_C(x)$\")\n", - " axis.plot(xs, rho_b_values, label=r\"$\\rho_O(R-x)$\")\n", - " axis.plot(xs, total_values, label=\"sum\")\n", - " axis.axvline(boundary.position, linestyle=\"--\", label=\"proatomic boundary\")\n", - " axis.axvline(minimum.position, linestyle=\":\", label=\"promolecular minimum\")\n", - " axis.set_xlim(0.35, 0.90)\n", - " axis.set_ylim(0.15, 2.8)\n", - " axis.set_xlabel(\"Position from C (bohr)\")\n", - " axis.set_ylabel(r\"Density (electron/bohr$^3$)\")\n", - " axis.set_title(\"C–O interatomic region at R = 1.5 bohr\")\n", - " axis.legend(loc=\"upper right\")\n", - " figure.tight_layout()\n", - " figure.savefig(ASSET_DIR / \"c-o-method-comparison.png\", dpi=160)\n", - " plt.show()\n", - "else:\n", - " print(\"Matplotlib is unavailable; the C–O plot was skipped.\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "442a4cb8", - "metadata": {}, - "source": [ - "\n", - "## Practical minimum search against a slower reference\n", - "\n", - "The production proposal is not an all-minima theorem. It asks whether a valley\n", - "is resolved at 0.01 bohr and checks it at two grid scales, with one finer\n", - "fallback. The comparison below uses 300 deterministic H–Lr cases, plus seven\n", - "adversarial cases discovered during Stage 4A, against a slower 0.001-bohr\n", - "fine-grid search over the same cutoff-bounded interval.\n", - "\n", - "A dense-reference minimum may be intentionally rejected when it is narrower\n", - "than the public resolution or lies immediately beside a nucleus at an extreme\n", - "short distance.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "0767eaed", - "metadata": { - "execution": { - "iopub.execute_input": "2026-07-13T17:51:00.929029Z", - "iopub.status.busy": "2026-07-13T17:51:00.928793Z", - "iopub.status.idle": "2026-07-13T17:51:02.256214Z", - "shell.execute_reply": "2026-07-13T17:51:02.255336Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cases: 307\n", - "Both methods returned a minimum: 298\n", - "Both returned no resolved minimum: 7\n", - "Practical-resolution nonmatches: 2\n", - "Joint results within 0.01 bohr: 298/298\n", - "Largest position difference: 0.0012907 bohr for C-O at R=1.5 bohr\n", - "Largest relative density difference: 5.96583e-06\n", - "Practical search time: 0.244 s\n", - "Slower reference time: 1.076 s\n", - "\n", - "Intentionally unresolved dense-reference minima:\n", - " C-Lu R=0.221730136797: status=no_resolved_interior_minimum, reference_x=0.00284120268288 bohr\n", - " He-Ru R=0.655290907435: status=no_resolved_interior_minimum, reference_x=0.0112558333887 bohr\n" - ] - } - ], - "source": [ - "\n", - "adversarial_cases = [\n", - " (\"Ni\", \"Te\", 1.5897045597171517),\n", - " (\"Al\", \"Pb\", 1.3645306063699802),\n", - " (\"Pr\", \"Re\", 0.7592738817632778),\n", - " (\"C\", \"Lu\", 0.2217301367970158),\n", - " (\"He\", \"Ru\", 0.6552909074349161),\n", - " (\"H\", \"U\", 1.58),\n", - " (\"C\", \"O\", 1.5),\n", - "]\n", - "cases = deterministic_cases(300) + adversarial_cases\n", - "\n", - "start = time.perf_counter()\n", - "practical_results = [practical_minimum_estimate(*case) for case in cases]\n", - "practical_seconds = time.perf_counter() - start\n", - "\n", - "start = time.perf_counter()\n", - "reference_results = [dense_reference_minimum(*case) for case in cases]\n", - "reference_seconds = time.perf_counter() - start\n", - "\n", - "joint_errors = []\n", - "intentional_nonmatches = []\n", - "both_absent = 0\n", - "for case, practical, reference in zip(cases, practical_results, reference_results):\n", - " if practical.position is None and reference is None:\n", - " both_absent += 1\n", - " elif (practical.position is None) != (reference is None):\n", - " intentional_nonmatches.append((case, practical, reference))\n", - " else:\n", - " joint_errors.append(\n", - " (\n", - " abs(practical.position - reference[0]),\n", - " abs(practical.rho_sum - reference[1]) / reference[1],\n", - " case,\n", - " )\n", - " )\n", - "\n", - "max_position = max(joint_errors, key=lambda item: item[0])\n", - "max_density = max(joint_errors, key=lambda item: item[1])\n", - "within_resolution = sum(error[0] <= PRACTICAL_RESOLUTION for error in joint_errors)\n", - "\n", - "print(f\"Cases: {len(cases)}\")\n", - "print(f\"Both methods returned a minimum: {len(joint_errors)}\")\n", - "print(f\"Both returned no resolved minimum: {both_absent}\")\n", - "print(f\"Practical-resolution nonmatches: {len(intentional_nonmatches)}\")\n", - "print(f\"Joint results within 0.01 bohr: {within_resolution}/{len(joint_errors)}\")\n", - "print(\n", - " f\"Largest position difference: {max_position[0]:.6g} bohr \"\n", - " f\"for {max_position[2][0]}-{max_position[2][1]} at R={max_position[2][2]:.6g} bohr\"\n", - ")\n", - "print(f\"Largest relative density difference: {max_density[1]:.6g}\")\n", - "print(f\"Practical search time: {practical_seconds:.3f} s\")\n", - "print(f\"Slower reference time: {reference_seconds:.3f} s\")\n", - "\n", - "if intentional_nonmatches:\n", - " print(\"\\nIntentionally unresolved dense-reference minima:\")\n", - " for case, practical, reference in intentional_nonmatches:\n", - " print(\n", - " f\" {case[0]}-{case[1]} R={case[2]:.12g}: \"\n", - " f\"status={practical.status}, reference_x={reference[0]:.12g} bohr\"\n", - " )\n" - ] - }, - { - "cell_type": "markdown", - "id": "b9d1be2c", - "metadata": {}, - "source": [ - "\n", - "## Cached timing comparison\n", - "\n", - "Timing is machine-dependent, so these figures are evidence rather than portable\n", - "thresholds. The important point is the scale difference: the boundary mode\n", - "needs one cached contour calculation plus a monotone bisection, while the\n", - "optional minimum mode remains below about a millisecond per ordinary pair in\n", - "this pure-Python prototype.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "6a476f18", - "metadata": { - "execution": { - "iopub.execute_input": "2026-07-13T17:51:02.258944Z", - "iopub.status.busy": "2026-07-13T17:51:02.258712Z", - "iopub.status.idle": "2026-07-13T17:51:06.515839Z", - "shell.execute_reply": "2026-07-13T17:51:06.514778Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "boundary: median 0.0541 ms/pair; runs=[0.05746927700010929, 0.055642846000182544, 0.05410611999991488, 0.05177689599986479, 0.050906101000009585]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " minimum: median 0.7887 ms/pair; runs=[0.8100914009999087, 0.7820286119999764, 0.7892370699998992, 0.7887017269999888, 0.7813305360000413]\n" - ] - } - ], - "source": [ - "\n", - "import statistics\n", - "\n", - "timing_cases = deterministic_cases(1000)\n", - "for case in timing_cases[:30]:\n", - " boundary_estimate(*case)\n", - " practical_minimum_estimate(*case)\n", - "\n", - "for label, function in (\n", - " (\"boundary\", boundary_estimate),\n", - " (\"minimum\", practical_minimum_estimate),\n", - "):\n", - " samples = []\n", - " for _ in range(5):\n", - " start = time.perf_counter()\n", - " [function(*case) for case in timing_cases]\n", - " samples.append((time.perf_counter() - start) * 1000.0 / len(timing_cases))\n", - " print(f\"{label:>8}: median {statistics.median(samples):.4f} ms/pair; runs={samples}\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "48e2df3d", - "metadata": {}, - "source": [ - "\n", - "## What the exhaustive Stage 4A work established\n", - "\n", - "The archived Stage 4A design, validation report, and two independent reviews\n", - "remain useful supporting evidence. They showed that an exact “return every\n", - "mathematical local minimum” contract really does require disproportionate\n", - "numerical machinery:\n", - "\n", - "| Case | Exact-topology observation | Product consequence |\n", - "|---|---|---|\n", - "| H–F near a knot | A minimum occurred 27 binary64 coordinates below a knot and needed high-precision derivative signs. | Important only when every microminimum is binding. |\n", - "| C–O exact knot | The stored knot and its immediate neighbor could differ by one density ULP. | Exact-knot topology is below the new 0.01-bohr resolution. |\n", - "| Li–Li, 5 bohr | Five local minima were found; the two off-centre minima were slightly deepest. | A raw minimum can violate the desired homonuclear separator symmetry. |\n", - "| H–U, 1.58 bohr | Fourteen isolated minima were found while a boundary value was lower. | Counting minima does not produce a useful IAS estimate. |\n", - "| At–Bk event collision | Two exact-distinct profile events rounded to one float and caused orientation-dependent candidate counts in the prototype. | Exact event identity is expensive and unnecessary for the revised contract. |\n", - "| Ne–Ne open segment | Ordinary log/exp value error exceeded the previous global-tie budget. | Numerical tie lists require a separate high-precision value model. |\n", - "\n", - "These are real numerical facts, not mistakes to hide. The revised API simply\n", - "places them outside the production semantics: boundary mode is unaffected, and\n", - "minimum mode deliberately coalesces or rejects sub-resolution structure.\n" - ] - }, - { - "cell_type": "markdown", - "id": "9abe91dc", - "metadata": {}, - "source": [ - "\n", - "## Decision recorded by this study\n", - "\n", - "The recommended Stage 4 API has three entry points:\n", - "\n", - "```python\n", - "estimate_proatomic_boundary(...)\n", - "estimate_promolecular_density_minimum(...)\n", - "estimate_ias_position(..., mode=\"boundary\" | \"minimum\")\n", - "```\n", - "\n", - "`boundary` is the default. It is the best fit for stable geometry and\n", - "Laguerre/Voronoi calibration. `minimum` remains available for Bader-oriented\n", - "research, but it searches only the significant-overlap region, returns one\n", - "resolved candidate rather than every local minimum, and reports ambiguity or a\n", - "non-result explicitly.\n", - "\n", - "For identical atoms, both modes return exactly `R/2`. No universal 0.5 Å ban is\n", - "needed: extreme short-distance cases naturally become `one_atom_dominates`,\n", - "`no_resolved_interior_minimum`, or another explicit diagnostic.\n", - "\n", - "The remaining scientific limitation is deliberate: neither neutral-proatom\n", - "mode has yet been calibrated against a curated set of molecular-density QTAIM\n", - "interatomic-surface coordinates.\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.5" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pyproject.toml b/pyproject.toml index 32d0bbe..8297664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,15 +53,17 @@ test = [ "tomli>=2; python_version < '3.11'", ] notebook = [ + "ipykernel>=6.29", "matplotlib>=3.8", + "mkdocs-jupyter>=0.26,<0.27", + "nbclient>=0.10,<0.12", + "nbformat>=5.10,<6", ] docs = [ "mkdocs>=1.6,<2", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.25", - "mkdocs-include-markdown-plugin>=6.2", "pymdown-extensions>=10.0", - "tomli>=2; python_version < '3.11'", ] dev = [ "build>=1.2", @@ -86,7 +88,6 @@ include = [ "/tests", "/docs", "/tools", - "/notebooks", "/.flake8", "/mkdocs.yml", "/README.md", diff --git a/tests/meta/test_notebooks.py b/tests/meta/test_notebooks.py index 350a46e..f5c1777 100644 --- a/tests/meta/test_notebooks.py +++ b/tests/meta/test_notebooks.py @@ -1,43 +1,140 @@ from __future__ import annotations +import base64 +import json from pathlib import Path -import subprocess -import sys REPO_ROOT = Path(__file__).resolve().parents[2] -CHECK_SCRIPT = REPO_ROOT / "tools" / "check_notebooks.py" -EXPORT_SCRIPT = REPO_ROOT / "tools" / "export_notebooks.py" -NOTEBOOKS = REPO_ROOT / "notebooks" -EXPORTED_NOTEBOOKS = REPO_ROOT / "docs" / "notebooks" - - -def test_notebook_files_exist() -> None: - expected = { - "01-quickstart.ipynb", - "02-policies-and-assessment.ipynb", - "03-custom-sets-and-discovery.ipynb", - "04-ias-method-selection-study.ipynb", - "05-proatomic-density-and-ias.ipynb", - } - actual = {path.name for path in NOTEBOOKS.glob("*.ipynb")} - assert expected.issubset(actual) - - -def test_notebooks_validate_and_execute() -> None: - subprocess.run([sys.executable, str(CHECK_SCRIPT)], cwd=REPO_ROOT, check=True) - - -def test_exported_notebook_pages_are_in_sync() -> None: - expected = { - "01-quickstart.md", - "02-policies-and-assessment.md", - "03-custom-sets-and-discovery.md", - } - actual = {path.name for path in EXPORTED_NOTEBOOKS.glob("*.md")} - assert expected.issubset(actual) - subprocess.run( - [sys.executable, str(EXPORT_SCRIPT), "--check"], - cwd=REPO_ROOT, - check=True, +NOTEBOOKS = REPO_ROOT / "docs" / "notebooks" +EXPECTED_NOTEBOOKS = { + "01-quickstart.ipynb", + "02-policies-and-assessment.ipynb", + "03-custom-sets-and-discovery.ipynb", + "04-ias-method-selection-study.ipynb", + "05-proatomic-density-and-ias.ipynb", +} +EXPECTED_SAVED_PNG_OUTPUTS = { + "01-quickstart.ipynb": 0, + "02-policies-and-assessment.ipynb": 0, + "03-custom-sets-and-discovery.ipynb": 0, + "04-ias-method-selection-study.ipynb": 3, + "05-proatomic-density-and-ias.ipynb": 2, +} +PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" + + +def _load_notebook(path: Path) -> dict[str, object]: + data = json.loads(path.read_text(encoding="utf-8")) + assert isinstance(data, dict) + assert data.get("nbformat") == 4 + return data + + +def _source(cell: dict[str, object]) -> str: + source = cell.get("source", []) + if isinstance(source, str): + return source + assert isinstance(source, list) + assert all(isinstance(line, str) for line in source) + return "".join(source) + + +def _text(value: object) -> str: + if value is None: + return "" + if isinstance(value, str): + return value + assert isinstance(value, list) + assert all(isinstance(line, str) for line in value) + return "".join(value) + + +def _cells(data: dict[str, object]) -> list[dict[str, object]]: + cells = data.get("cells") + assert isinstance(cells, list) + assert all(isinstance(cell, dict) for cell in cells) + return cells + + +def test_notebooks_have_one_direct_documentation_source() -> None: + assert {path.name for path in NOTEBOOKS.glob("*.ipynb")} == EXPECTED_NOTEBOOKS + assert not list((REPO_ROOT / "notebooks").glob("*.ipynb")) + assert not list(NOTEBOOKS.glob("*.md")) + assert not (REPO_ROOT / "tools" / "export_notebooks.py").exists() + + +def test_notebooks_have_narrative_structure_and_saved_success() -> None: + for name in sorted(EXPECTED_NOTEBOOKS): + cells = _cells(_load_notebook(NOTEBOOKS / name)) + markdown_cells = [cell for cell in cells if cell.get("cell_type") == "markdown"] + code_cells = [cell for cell in cells if cell.get("cell_type") == "code"] + markdown = "\n".join(_source(cell) for cell in markdown_cells) + + assert markdown.lstrip().startswith("# "), name + assert "prerequisite" in markdown.lower(), name + assert "## " in markdown, name + assert "limitation" in markdown.lower(), name + assert code_cells, name + + outputs = [ + output + for cell in code_cells + for output in cell.get("outputs", []) + if isinstance(output, dict) + ] + assert outputs, name + assert not any(output.get("output_type") == "error" for output in outputs) + + text_outputs = [] + png_outputs = [] + for output in outputs: + data = output.get("data", {}) + assert isinstance(data, dict) + if _text(output.get("text")) or _text(data.get("text/plain")): + text_outputs.append(output) + if _text(data.get("image/png")): + png_outputs.append(output) + + assert text_outputs, name + assert len(png_outputs) == EXPECTED_SAVED_PNG_OUTPUTS[name] + for output in png_outputs: + data = output["data"] + assert isinstance(data, dict) + payload = base64.b64decode(_text(data["image/png"]), validate=True) + assert payload.startswith(PNG_SIGNATURE), name + + for index, cell in enumerate(cells): + if cell.get("cell_type") != "code" or not _source(cell).strip(): + continue + assert index > 0, name + previous = cells[index - 1] + assert previous.get("cell_type") == "markdown", name + assert _source(previous).strip(), name + + +def test_notebook_site_content_includes_math_text_and_png() -> None: + notebooks = [_load_notebook(NOTEBOOKS / name) for name in EXPECTED_NOTEBOOKS] + cells = [cell for notebook in notebooks for cell in _cells(notebook)] + markdown = "\n".join( + _source(cell) for cell in cells if cell.get("cell_type") == "markdown" + ) + outputs = [ + output + for cell in cells + for output in cell.get("outputs", []) + if isinstance(output, dict) + ] + + assert "$\\rho_c$" in markdown + assert "$$\n\\rho_c" in markdown + assert any( + output.get("output_type") == "stream" + and bool("".join(output.get("text", []))) + for output in outputs + ) + assert any( + isinstance(output.get("data"), dict) + and bool(output["data"].get("image/png")) + for output in outputs ) diff --git a/tests/meta/test_text_generation_tools.py b/tests/meta/test_text_generation_tools.py deleted file mode 100644 index b6203a7..0000000 --- a/tests/meta/test_text_generation_tools.py +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import annotations - -import importlib.util -from pathlib import Path -import sys - - -REPO_ROOT = Path(__file__).resolve().parents[2] -MODULE_PATH = REPO_ROOT / "tools" / "export_notebooks.py" - -spec = importlib.util.spec_from_file_location("export_notebooks_tool", MODULE_PATH) -assert spec is not None and spec.loader is not None -export_notebooks = importlib.util.module_from_spec(spec) -sys.modules[spec.name] = export_notebooks -spec.loader.exec_module(export_notebooks) - - -def test_export_notebooks_check_ignores_crlf(tmp_path: Path) -> None: - """Notebook export checks should ignore Windows vs Unix newline differences.""" - - output_dir = tmp_path / "docs" - output_dir.mkdir() - - for notebook_name, output_name in export_notebooks.NOTEBOOK_OUTPUTS.items(): - rendered = export_notebooks._export_markdown( - export_notebooks.NOTEBOOKS / notebook_name - ) - (output_dir / output_name).write_text( - rendered.replace("\n", "\r\n"), - encoding="utf-8", - newline="", - ) - - assert export_notebooks.export_notebooks(output_dir, check=True) == 0 diff --git a/tools/README.md b/tools/README.md index 7e3649a..6f840d8 100644 --- a/tools/README.md +++ b/tools/README.md @@ -9,14 +9,12 @@ release preparation. `atomref-proatoms` 2.0.0 source and write or check the deterministic neutral H–Lr consumer ZIP. This is a maintainer-only tool and performs no network access. -- `check_dist.py` — verify that wheel and source-distribution artifacts contain - the key files expected by the project. -- `check_notebooks.py` — validate notebook JSON, release-notebook saved state, - required Markdown and outputs, then execute notebook code cells headlessly. +- `check_dist.py` — verify wheel and source-distribution contents and optionally + test clean base, `notebook`, and `all` installations from the built wheel. +- `check_notebooks.py` — smoke-execute temporary notebook copies through a + standard Jupyter kernel and discard the resulting outputs. - `check_registry.py` — validate curated registry metadata against every packaged scalar and radial payload. -- `export_notebooks.py` — render the bundled notebooks into Markdown pages under - `docs/notebooks/`. - `gen_readme.py` — regenerate `README.md` from `docs/index.md`. - `release_check.py` — run the full release-preparation checklist, including linting, tests, docs, builds, and artifact validation. @@ -26,7 +24,6 @@ release preparation. ```bash python tools/check_registry.py python tools/check_notebooks.py -python tools/export_notebooks.py python tools/gen_readme.py python tools/release_check.py ``` diff --git a/tools/check_notebooks.py b/tools/check_notebooks.py index 42bed06..916b836 100644 --- a/tools/check_notebooks.py +++ b/tools/check_notebooks.py @@ -1,24 +1,22 @@ #!/usr/bin/env python3 -"""Validate notebook JSON structure and execute notebook code cells.""" +"""Smoke-execute notebooks with standard Jupyter tooling in a temporary tree.""" from __future__ import annotations -from contextlib import redirect_stderr, redirect_stdout -import io -import json +import argparse import os from pathlib import Path import shutil -import sys import tempfile +from unittest.mock import patch + +import nbformat +from nbclient import NotebookClient REPO_ROOT = Path(__file__).resolve().parents[1] SRC = REPO_ROOT / "src" -if str(SRC) not in sys.path: - sys.path.insert(0, str(SRC)) - -NOTEBOOKS = REPO_ROOT / "notebooks" +NOTEBOOK_DIR = REPO_ROOT / "docs" / "notebooks" REQUIRED_NOTEBOOKS = ( "01-quickstart.ipynb", "02-policies-and-assessment.ipynb", @@ -26,240 +24,120 @@ "04-ias-method-selection-study.ipynb", "05-proatomic-density-and-ias.ipynb", ) - -RELEASE_NOTEBOOK_REQUIREMENTS = { - "04-ias-method-selection-study.ipynb": { - "markdown": ( - "# Choosing a practical neutral-proatom IAS proxy", - "## Numerical policy examined here", - "## Representative pairwise results", - "## Why homonuclear symmetry overrides raw shell minima", - "## Decision recorded by this study", - ), - "output_cells": (6, 8, 10, 12, 14, 16), - "plot_cells": (), - }, - "05-proatomic-density-and-ias.ipynb": { - "markdown": ( - "# Neutral proatomic density and pairwise IAS-proxy workflows", - "## Dataset discovery and provenance", - "## Profile retrieval and scalar evaluation", - "## Radial-density profiles", - "## Ordinary heteronuclear pair: boundary and minimum", - "## Component and summed line densities", - "## Exact homonuclear midpoint", - "## Low-density gap", - "## No resolved interior minimum", - "## Mode selection, statuses, and limitations", - ), - "output_cells": (3, 5, 7, 9, 11, 13, 15, 17, 19), - "plot_cells": (9, 13), - }, -} +DEFAULT_TIMEOUT_SECONDS = 300 class NotebookCheckError(RuntimeError): - """Raised when a notebook is malformed or fails to execute.""" - - -def iter_notebooks() -> tuple[Path, ...]: - """Return the notebooks that are expected to ship with the project.""" - - return tuple(NOTEBOOKS / name for name in REQUIRED_NOTEBOOKS) - - -def load_notebook(path: Path) -> dict[str, object]: - """Load one notebook JSON document.""" - - data = json.loads(path.read_text(encoding="utf-8")) - if not isinstance(data, dict): - raise NotebookCheckError(f"{path.name}: expected top-level JSON object") - return data - - -def _cell_source(cell: dict[str, object], *, path: Path, index: int) -> str: - """Return normalized source text for one notebook cell.""" + """Raised when the requested notebook smoke check cannot be prepared.""" - source = cell.get("source", []) - if isinstance(source, str): - return source - if isinstance(source, list) and all(isinstance(line, str) for line in source): - return "".join(source) - raise NotebookCheckError(f"{path.name}: cell {index} has invalid source") +def default_notebooks() -> tuple[Path, ...]: + """Return the five notebooks shipped as documentation.""" -def iter_code_cells(data: dict[str, object], *, path: Path) -> tuple[str, ...]: - """Return notebook code-cell sources in order.""" + return tuple(NOTEBOOK_DIR / name for name in REQUIRED_NOTEBOOKS) - cells = data.get("cells") - if not isinstance(cells, list): - raise NotebookCheckError(f"{path.name}: missing notebook cell list") - code: list[str] = [] - for index, cell in enumerate(cells): - if not isinstance(cell, dict): - raise NotebookCheckError(f"{path.name}: cell {index} is not an object") - cell_type = cell.get("cell_type") - if cell_type != "code": - continue - code.append(_cell_source(cell, path=path, index=index + 1)) - if not code: - raise NotebookCheckError(f"{path.name}: contains no code cells") - return tuple(code) +def _resolve_notebooks(paths: list[Path]) -> tuple[Path, ...]: + """Resolve requested notebooks and reject missing or ambiguous inputs.""" - -def validate_saved_release_notebook( - data: dict[str, object], - *, - path: Path, -) -> None: - """Validate the small saved-state contract for release notebooks.""" - - requirements = RELEASE_NOTEBOOK_REQUIREMENTS.get(path.name) - if requirements is None: - return - if data.get("nbformat") != 4: - raise NotebookCheckError(f"{path.name}: expected notebook format 4") - - cells = data.get("cells") - if not isinstance(cells, list): - raise NotebookCheckError(f"{path.name}: missing notebook cell list") - - markdown_parts: list[str] = [] - execution_counts: list[int] = [] - for index, cell in enumerate(cells, start=1): - if not isinstance(cell, dict): - raise NotebookCheckError(f"{path.name}: cell {index} is not an object") - source = _cell_source(cell, path=path, index=index) - if cell.get("cell_type") == "markdown": - markdown_parts.append(source) - continue - if cell.get("cell_type") != "code" or not source.strip(): - continue - - count = cell.get("execution_count") - if isinstance(count, bool) or not isinstance(count, int) or count <= 0: - raise NotebookCheckError( - f"{path.name}: nonempty code cell {index} lacks an execution count" - ) - execution_counts.append(count) - - if index == 1 or not isinstance(cells[index - 2], dict): - raise NotebookCheckError( - f"{path.name}: code cell {index} lacks preceding Markdown" - ) - previous = cells[index - 2] - if previous.get("cell_type") != "markdown" or not _cell_source( - previous, - path=path, - index=index - 1, - ).strip(): + notebooks = tuple( + path.resolve() for path in (paths if paths else list(default_notebooks())) + ) + names: set[str] = set() + for path in notebooks: + if not path.is_file(): + raise NotebookCheckError(f"missing notebook: {path}") + if path.suffix != ".ipynb": + raise NotebookCheckError(f"not a Jupyter notebook: {path}") + if path.name in names: raise NotebookCheckError( - f"{path.name}: code cell {index} lacks preceding Markdown" + f"notebook names must be unique for temporary execution: {path.name}" ) - - outputs = cell.get("outputs") - if not isinstance(outputs, list): - raise NotebookCheckError(f"{path.name}: cell {index} has invalid outputs") - if any( - isinstance(output, dict) and output.get("output_type") == "error" - for output in outputs - ): - raise NotebookCheckError(f"{path.name}: cell {index} saved an error") - - expected_counts = list(range(1, len(execution_counts) + 1)) - if execution_counts != expected_counts: - raise NotebookCheckError( - f"{path.name}: execution counts are not sequential from 1" - ) - - markdown = "\n".join(markdown_parts) - missing_markdown = [ - heading for heading in requirements["markdown"] if heading not in markdown - ] - if missing_markdown: - raise NotebookCheckError( - f"{path.name}: missing required Markdown: {', '.join(missing_markdown)}" - ) - - for cell_number in requirements["output_cells"]: - cell = cells[cell_number - 1] - outputs = cell.get("outputs") if isinstance(cell, dict) else None - if not isinstance(outputs, list) or not outputs: - raise NotebookCheckError( - f"{path.name}: expected saved output in cell {cell_number}" - ) - - for cell_number in requirements["plot_cells"]: - cell = cells[cell_number - 1] - outputs = cell.get("outputs") if isinstance(cell, dict) else None - has_png = isinstance(outputs, list) and any( - isinstance(output, dict) - and output.get("output_type") == "display_data" - and isinstance(output.get("data"), dict) - and "image/png" in output["data"] - for output in outputs + names.add(path.name) + if not notebooks: + raise NotebookCheckError("no notebooks selected") + return notebooks + + +def _execute_temporary_copy(path: Path, *, working_dir: Path) -> None: + """Execute and write one temporary notebook copy, failing on cell errors.""" + + with path.open(encoding="utf-8") as stream: + notebook = nbformat.read(stream, as_version=4) + kernel_name = notebook.get("metadata", {}).get("kernelspec", {}).get( + "name", "python3" + ) + client = NotebookClient( + notebook, + timeout=DEFAULT_TIMEOUT_SECONDS, + kernel_name=kernel_name, + resources={"metadata": {"path": str(working_dir)}}, + allow_errors=False, + ) + executed = client.execute() + with path.open("w", encoding="utf-8", newline="\n") as stream: + nbformat.write(executed, stream) + + +def smoke_execute(paths: list[Path]) -> int: + """Execute selected notebooks only after copying them into a temporary tree.""" + + notebooks = _resolve_notebooks(paths) + with tempfile.TemporaryDirectory(prefix="atomref-notebooks-") as tmp: + temporary_root = Path(tmp) + temporary_notebooks = temporary_root / "docs" / "notebooks" + temporary_notebooks.mkdir(parents=True) + shutil.copytree( + SRC, + temporary_root / "src", + ignore=shutil.ignore_patterns("__pycache__", "*.py[co]"), ) - if not has_png: - raise NotebookCheckError( - f"{path.name}: expected an embedded PNG in cell {cell_number}" - ) - - -def execute_notebook(path: Path) -> None: - """Execute all code cells from one notebook in a shared namespace.""" - - if not path.exists(): - raise NotebookCheckError(f"missing notebook: {path}") - data = load_notebook(path) - validate_saved_release_notebook(data, path=path) - namespace = {"__name__": "__main__"} - for index, source in enumerate(iter_code_cells(data, path=path), start=1): - if not source.strip(): - continue - try: - code = compile(source, f"{path.name}::cell{index}", "exec") - with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()): - exec(code, namespace, namespace) - except Exception as exc: # noqa: BLE001 - raise NotebookCheckError( - f"{path.name}: execution failed in code cell {index}: {exc}" - ) from exc + copied = [] + for source in notebooks: + destination = temporary_notebooks / source.name + shutil.copy2(source, destination) + copied.append(destination) + + environment = { + "IPYTHONDIR": str(temporary_root / "ipython"), + "JUPYTER_CONFIG_DIR": str(temporary_root / "jupyter"), + "MPLBACKEND": "Agg", + "MPLCONFIGDIR": str(temporary_root / "matplotlib"), + "PYTHONPYCACHEPREFIX": str(temporary_root / "pycache"), + } + pythonpath = os.environ.get("PYTHONPATH") + environment["PYTHONPATH"] = str(temporary_root / "src") + if pythonpath: + environment["PYTHONPATH"] += os.pathsep + pythonpath + + with patch.dict(os.environ, environment, clear=False): + for notebook in copied: + _execute_temporary_copy( + notebook, + working_dir=temporary_notebooks, + ) + + print(f"Smoke-executed {len(notebooks)} notebook(s) in temporary kernels.") + return 0 def main() -> int: - """Validate and execute every required notebook.""" + """Parse notebook paths and run the temporary Jupyter smoke check.""" - notebooks = iter_notebooks() - previous_cwd = Path.cwd() - previous_backend = os.environ.get("MPLBACKEND") - previous_config = os.environ.get("MPLCONFIGDIR") - with tempfile.TemporaryDirectory(prefix="atomref-notebooks-") as tmp: - isolated_root = Path(tmp) - shutil.copytree(SRC, isolated_root / "src") - matplotlib_config = isolated_root / "matplotlib" - matplotlib_config.mkdir() - try: - os.environ["MPLBACKEND"] = "Agg" - os.environ["MPLCONFIGDIR"] = str(matplotlib_config) - os.chdir(isolated_root) - for notebook in notebooks: - execute_notebook(notebook) - pyplot = sys.modules.get("matplotlib.pyplot") - if pyplot is not None: - pyplot.close("all") - finally: - os.chdir(previous_cwd) - if previous_backend is None: - os.environ.pop("MPLBACKEND", None) - else: - os.environ["MPLBACKEND"] = previous_backend - if previous_config is None: - os.environ.pop("MPLCONFIGDIR", None) - else: - os.environ["MPLCONFIGDIR"] = previous_config - print(f"Validated {len(notebooks)} notebook(s).") - return 0 + parser = argparse.ArgumentParser( + description=( + "Smoke-execute documentation notebooks in a disposable tree without " + "changing or comparing committed outputs." + ) + ) + parser.add_argument( + "notebooks", + nargs="*", + type=Path, + help="optional notebook paths; defaults to every shipped notebook", + ) + args = parser.parse_args() + return smoke_execute(args.notebooks) if __name__ == "__main__": diff --git a/tools/export_notebooks.py b/tools/export_notebooks.py deleted file mode 100644 index aa6761d..0000000 --- a/tools/export_notebooks.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -"""Export bundled notebooks to Markdown pages for the docs.""" - -from __future__ import annotations - -from contextlib import redirect_stdout -import argparse -import io -import json -from pathlib import Path -import sys - - -REPO_ROOT = Path(__file__).resolve().parents[1] -SRC = REPO_ROOT / "src" -if str(SRC) not in sys.path: - sys.path.insert(0, str(SRC)) - -NOTEBOOKS = REPO_ROOT / "notebooks" -DEFAULT_OUTPUT_DIR = REPO_ROOT / "docs" / "notebooks" -NOTEBOOK_OUTPUTS = { - "01-quickstart.ipynb": "01-quickstart.md", - "02-policies-and-assessment.ipynb": "02-policies-and-assessment.md", - "03-custom-sets-and-discovery.ipynb": "03-custom-sets-and-discovery.md", -} -HEADER = ( - "\n" - "\n\n" -) - - -class NotebookExportError(RuntimeError): - """Raised when notebook export fails.""" - - -def _load_notebook(path: Path) -> dict[str, object]: - """Load one notebook JSON document.""" - - data = json.loads(path.read_text(encoding="utf-8")) - if not isinstance(data, dict): - raise NotebookExportError(f"{path.name}: expected top-level JSON object") - return data - - -def _cell_source(cell: dict[str, object], *, path: Path, index: int) -> str: - """Return normalized source text for one notebook cell.""" - - source = cell.get("source", []) - if isinstance(source, str): - return source - if isinstance(source, list) and all(isinstance(line, str) for line in source): - return "".join(source) - raise NotebookExportError(f"{path.name}: invalid source in cell {index}") - - -def _export_markdown(path: Path) -> str: - """Render one notebook as Markdown, executing code cells for output.""" - - data = _load_notebook(path) - cells = data.get("cells") - if not isinstance(cells, list): - raise NotebookExportError(f"{path.name}: missing notebook cell list") - - namespace = {"__name__": "__main__"} - parts: list[str] = [HEADER] - parts.append( - f"[Open the original notebook on GitHub]" - f"(https://github.com/DeloneCommons/atomref/blob/main/notebooks/{path.name})\n" - ) - - for index, cell in enumerate(cells, start=1): - if not isinstance(cell, dict): - raise NotebookExportError(f"{path.name}: cell {index} is not an object") - source = _cell_source(cell, path=path, index=index) - cell_type = cell.get("cell_type") - if cell_type == "markdown": - text = source.strip() - if text: - parts.append(f"{text}\n") - continue - if cell_type != "code": - continue - code_text = source.rstrip() - parts.append("```python\n") - parts.append(f"{code_text}\n") - parts.append("```\n") - if not code_text.strip(): - continue - - stdout = io.StringIO() - try: - code = compile(code_text, f"{path.name}::cell{index}", "exec") - with redirect_stdout(stdout): - exec(code, namespace, namespace) - except Exception as exc: # noqa: BLE001 - raise NotebookExportError( - f"{path.name}: execution failed in code cell {index}: {exc}" - ) from exc - - output = stdout.getvalue().rstrip() - if output: - parts.append("**Output**\n\n") - parts.append("```text\n") - parts.append(f"{output}\n") - parts.append("```\n") - - return "\n".join(part.rstrip() for part in parts if part).rstrip() + "\n" - - -def export_notebooks(output_dir: Path, *, check: bool = False) -> int: - """Export bundled notebooks or verify that exported pages are in sync.""" - - output_dir.mkdir(parents=True, exist_ok=True) - for notebook_name, output_name in NOTEBOOK_OUTPUTS.items(): - notebook_path = NOTEBOOKS / notebook_name - rendered = _export_markdown(notebook_path) - output_path = output_dir / output_name - if check: - current = output_path.read_text(encoding="utf-8").replace("\r\n", "\n") - if current != rendered: - print( - f"{output_path} is out of sync with {notebook_path.name}", - file=sys.stderr, - ) - return 1 - else: - output_path.write_text(rendered, encoding="utf-8", newline="\n") - return 0 - - -def main() -> int: - """Export notebook Markdown pages or check that they are current.""" - - parser = argparse.ArgumentParser() - parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR) - parser.add_argument( - "--check", - action="store_true", - help="exit with status 1 when exported pages are out of sync", - ) - args = parser.parse_args() - return export_notebooks(args.output_dir, check=args.check) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tools/release_check.py b/tools/release_check.py index 29f436d..5aa2716 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -107,7 +107,6 @@ def main() -> int: _run("flake8", "src", "tests", "tools") _run(sys.executable, "tools/check_registry.py") _run(sys.executable, "tools/check_notebooks.py") - _run(sys.executable, "tools/export_notebooks.py", "--check") _run(sys.executable, "tools/gen_readme.py", "--check") _run(sys.executable, "-m", "pytest", "-q") if not args.skip_docs: From 0d98d854e65affc626fd6927b0d96eecfb76aa26 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 00:23:33 +0300 Subject: [PATCH 11/23] docs: complete typed API reference --- docs/api/atomref.md | 70 ++++++ docs/api/elements.md | 14 +- docs/api/errors.md | 20 ++ docs/api/index.md | 38 +-- docs/api/policy.md | 18 +- docs/api/proatoms.md | 41 +++- docs/api/radii.md | 21 ++ docs/api/registry.md | 24 +- docs/api/transfer.md | 15 +- docs/api/xh.md | 19 +- mkdocs.yml | 18 ++ src/atomref/__init__.py | 2 +- src/atomref/elements.py | 80 ++++++- src/atomref/errors.py | 38 ++- src/atomref/policy.py | 121 ++++++++-- src/atomref/proatoms.py | 432 +++++++++++++++++++++++++++++----- src/atomref/radii.py | 218 +++++++++++++++-- src/atomref/registry.py | 274 +++++++++++++++++++-- src/atomref/transfer.py | 82 ++++++- src/atomref/xh.py | 121 +++++++++- tests/meta/test_public_api.py | 48 +++- 21 files changed, 1528 insertions(+), 186 deletions(-) create mode 100644 docs/api/errors.md diff --git a/docs/api/atomref.md b/docs/api/atomref.md index 3536e34..185f78e 100644 --- a/docs/api/atomref.md +++ b/docs/api/atomref.md @@ -4,3 +4,73 @@ The top-level package re-exports the main user-facing API so that most code can simply do `import atomref as ar`. ::: atomref + options: + members: + - __version__ + - Element + - canonicalize_element_symbol + - get_element + - iter_elements + - is_valid_element_symbol + - BuiltinSet + - CoverageInfo + - DatasetInfo + - DatasetRef + - ElementRadialSet + - ElementScalarSet + - QuantityInfo + - Reference + - get_builtin_set + - get_dataset_info + - get_quantity_info + - list_dataset_ids + - list_dataset_infos + - list_quantities + - LinearFit + - LinearTransfer + - SubstitutionTransfer + - LookupResult + - ValuePolicy + - lookup_value + - get_value + - BOHR_TO_ANGSTROM + - DEFAULT_PROATOMIC_DENSITY_SET + - PROATOMIC_TAIL_CUTOFF + - IAS_MINIMUM_RESOLUTION_BOHR + - IASPositionResult + - ProatomicDensityProfile + - ProatomicDensitySet + - estimate_proatomic_boundary + - estimate_promolecular_density_minimum + - estimate_ias_position + - list_proatomic_density_sets + - list_proatomic_density_set_infos + - get_proatomic_density_set + - get_proatomic_density_set_info + - get_proatomic_density_profile + - get_proatomic_density + - RadiiPolicy + - RadiiElementAssessment + - RadiiPolicyAssessment + - DEFAULT_COVALENT_POLICY + - DEFAULT_VDW_POLICY + - list_radii_sets + - list_radii_set_infos + - get_radii_set + - get_radii_set_info + - lookup_covalent_radius + - get_covalent_radius + - lookup_vdw_radius + - get_vdw_radius + - assess_radii_policy + - XHPolicy + - DEFAULT_XH_POLICY + - list_xh_sets + - list_xh_set_infos + - get_xh_set + - get_xh_set_info + - lookup_xh_bond_length + - get_xh_bond_length + filters: + - "!^_[^_]" + - "!^__post_init__$" diff --git a/docs/api/elements.md b/docs/api/elements.md index 2f066c7..761d053 100644 --- a/docs/api/elements.md +++ b/docs/api/elements.md @@ -1,7 +1,17 @@ # atomref.elements -Element identity is intentionally minimal in the current implementation: -atomic number, symbol, and name. The module also contains the canonicalization helpers used throughout the +Element identity is intentionally minimal: atomic number, symbol, and name. +The module also contains the canonicalization helpers used throughout the package. ::: atomref.elements + options: + members: + - Element + - canonicalize_element_symbol + - is_valid_element_symbol + - get_element + - iter_elements + filters: + - "!^_[^_]" + - "!^__post_init__$" diff --git a/docs/api/errors.md b/docs/api/errors.md new file mode 100644 index 0000000..cf7d81b --- /dev/null +++ b/docs/api/errors.md @@ -0,0 +1,20 @@ +# atomref.errors + +`atomref` distinguishes missing scientific data from invalid configuration or +malformed packaged data. Ordinary lookup misses use `None` or a missing +[`LookupResult`][atomref.LookupResult]; catchable operational failures use the +exceptions below. + +The exceptions are imported from `atomref.errors` and are deliberately not +re-exported from the top-level package. + +::: atomref.errors + options: + members: + - AtomrefError + - DatasetError + - MissingValueError + - PolicyError + filters: + - "!^_[^_]" + - "!^__post_init__$" diff --git a/docs/api/index.md b/docs/api/index.md index 5ed901b..52781a2 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -8,26 +8,32 @@ documented because they expose the actual data model behind the package. ## Common tasks -- get a single value: use `get_covalent_radius(...)`, `get_vdw_radius(...)`, or - `get_xh_bond_length(...)` -- evaluate a neutral proatomic profile or pairwise estimate: use - `get_proatomic_density(...)`, `estimate_proatomic_boundary(...)`, or - `estimate_promolecular_density_minimum(...)` -- inspect provenance: use `lookup_covalent_radius(...)`, - `lookup_vdw_radius(...)`, `lookup_xh_bond_length(...)`, or the generic - `lookup_value(...)` -- browse packaged datasets: use `list_quantities()`, `get_quantity_info(...)`, - `list_dataset_infos(...)`, `list_radii_set_infos(...)`, or - `list_xh_set_infos(...)` -- load a packaged set directly: use `get_builtin_set(...)`, `get_radii_set(...)`, - or `get_xh_set(...)` -- define a custom set: use `ElementScalarSet.from_mapping(...)` -- define transfer-backed lookup behavior: use `ValuePolicy`, `RadiiPolicy`, - `XHPolicy`, `SubstitutionTransfer`, and `LinearTransfer` +- get a single value: use + [`get_covalent_radius()`][atomref.get_covalent_radius], + [`get_vdw_radius()`][atomref.get_vdw_radius], or + [`get_xh_bond_length()`][atomref.get_xh_bond_length] +- evaluate a neutral profile or pairwise estimate: use + [`get_proatomic_density()`][atomref.get_proatomic_density], + [`estimate_proatomic_boundary()`][atomref.estimate_proatomic_boundary], or + [`estimate_promolecular_density_minimum()`][atomref.estimate_promolecular_density_minimum] +- inspect provenance with the corresponding `lookup_*` function or the generic + [`lookup_value()`][atomref.lookup_value] +- browse packaged datasets with [`list_quantities()`][atomref.list_quantities], + [`get_quantity_info()`][atomref.get_quantity_info], or a quantity-specific + metadata-listing helper +- load a packaged set directly with + [`get_builtin_set()`][atomref.get_builtin_set] +- define a custom set with + [`ElementScalarSet.from_mapping()`][atomref.ElementScalarSet.from_mapping] +- configure transfer-backed lookup with [ValuePolicy][atomref.ValuePolicy], + [RadiiPolicy][atomref.RadiiPolicy], [XHPolicy][atomref.XHPolicy], + [SubstitutionTransfer][atomref.SubstitutionTransfer], and + [LinearTransfer][atomref.LinearTransfer] ## Module reference - [Top-level package](atomref.md) +- [Exceptions](errors.md) - [Elements](elements.md) - [Registry and packaged datasets](registry.md) - [Transfer models](transfer.md) diff --git a/docs/api/policy.md b/docs/api/policy.md index 29b4142..ed71759 100644 --- a/docs/api/policy.md +++ b/docs/api/policy.md @@ -5,10 +5,10 @@ X–H-specific convenience APIs. Use it when you want to work directly with the shared value-selection engine: -- `ValuePolicy` — generic element-domain policy configuration, -- `lookup_value(...)` — resolve one value together with provenance, -- `get_value(...)` — resolve only the numeric value, -- `LookupResult` — the structured result object returned by the resolver. +- [ValuePolicy][atomref.ValuePolicy] — generic element-domain configuration, +- [`lookup_value()`][atomref.lookup_value] — one value with provenance, +- [`get_value()`][atomref.get_value] — only the numeric value, +- [LookupResult][atomref.LookupResult] — the structured resolver result. A few practical notes: @@ -24,3 +24,13 @@ A few practical notes: wrapper policies such as `RadiiPolicy` and `XHPolicy`. ::: atomref.policy + options: + members: + - LookupSource + - LookupResult + - ValuePolicy + - lookup_value + - get_value + filters: + - "!^_[^_]" + - "!^__post_init__$" diff --git a/docs/api/proatoms.md b/docs/api/proatoms.md index 0e69a7d..9d99959 100644 --- a/docs/api/proatoms.md +++ b/docs/api/proatoms.md @@ -1,15 +1,19 @@ # atomref.proatoms -This module exposes the accepted `0.2.0` density and two-mode pairwise API: +This module exposes the neutral-density and two-mode pairwise API: -- `ProatomicDensityProfile` and `ProatomicDensitySet` for immutable radial data; -- `get_proatomic_density_profile()` and `get_proatomic_density()` for scalar +- [ProatomicDensityProfile][atomref.ProatomicDensityProfile] and + [ProatomicDensitySet][atomref.ProatomicDensitySet] for immutable radial data; +- [`get_proatomic_density()`][atomref.get_proatomic_density] for scalar positive-region log-log evaluation; -- `estimate_proatomic_boundary()` for the stable default divider; -- `estimate_promolecular_density_minimum()` for the optional cutoff-bounded, - resolution-limited minimum proxy; -- `estimate_ias_position()` for explicit mode dispatch; -- `IASPositionResult` for coordinates, statuses, cutoff/search diagnostics, +- [`estimate_proatomic_boundary()`][atomref.estimate_proatomic_boundary] for + the stable default divider; +- [`estimate_promolecular_density_minimum()`][atomref.estimate_promolecular_density_minimum] + for the optional cutoff-bounded, resolution-limited minimum proxy; +- [`estimate_ias_position()`][atomref.estimate_ias_position] for explicit mode + dispatch; +- [IASPositionResult][atomref.IASPositionResult] for coordinates, statuses, + cutoff/search diagnostics, units, and provenance. See the [proatomic-density guide](../guide/proatomic_density.md) for source @@ -17,3 +21,24 @@ identity, units, the 20-bohr range, fixed cutoff, mode selection, statuses, and limitations. ::: atomref.proatoms + options: + members: + - DEFAULT_PROATOMIC_DENSITY_SET + - BOHR_TO_ANGSTROM + - PROATOMIC_TAIL_CUTOFF + - IAS_MINIMUM_RESOLUTION_BOHR + - IASPositionResult + - ProatomicDensitySet + - ProatomicDensityProfile + - list_proatomic_density_sets + - list_proatomic_density_set_infos + - get_proatomic_density_set_info + - get_proatomic_density_set + - get_proatomic_density_profile + - get_proatomic_density + - estimate_proatomic_boundary + - estimate_promolecular_density_minimum + - estimate_ias_position + filters: + - "!^_[^_]" + - "!^__post_init__$" diff --git a/docs/api/radii.md b/docs/api/radii.md index ff5e214..992840e 100644 --- a/docs/api/radii.md +++ b/docs/api/radii.md @@ -6,3 +6,24 @@ It provides radii policies, packaged radii-set discovery, lookup helpers, and policy-assessment reports. ::: atomref.radii + options: + members: + - RadiiKind + - RadiiSet + - RadiiPolicy + - RadiiElementAssessment + - RadiiPolicyAssessment + - DEFAULT_COVALENT_POLICY + - DEFAULT_VDW_POLICY + - list_radii_sets + - list_radii_set_infos + - get_radii_set_info + - get_radii_set + - lookup_covalent_radius + - get_covalent_radius + - lookup_vdw_radius + - get_vdw_radius + - assess_radii_policy + filters: + - "!^_[^_]" + - "!^__post_init__$" diff --git a/docs/api/registry.md b/docs/api/registry.md index 93a0b13..82afddc 100644 --- a/docs/api/registry.md +++ b/docs/api/registry.md @@ -16,9 +16,31 @@ In the current runtime, the implemented lookup domain is `element`. The registry still stores `domain` explicitly because the metadata design is meant to stay reusable as the package grows. -`get_builtin_set()` dispatches `element_scalar_csv` and +[`get_builtin_set()`][atomref.get_builtin_set] dispatches `element_scalar_csv` and `element_radial_csv_zip` storage and returns the `BuiltinSet` union. Policy consumers explicitly narrow that result to `ElementScalarSet`; radial profiles do not participate in scalar policy or transfer behavior. ::: atomref.registry + options: + members: + - QuantityId + - DomainId + - DatasetRef + - Reference + - CoverageInfo + - QuantityInfo + - DatasetInfo + - ElementScalarSet + - ElementRadialSet + - BuiltinSet + - ScalarDatasetLike + - list_quantities + - get_quantity_info + - list_dataset_ids + - list_dataset_infos + - get_dataset_info + - get_builtin_set + filters: + - "!^_[^_]" + - "!^__post_init__$" diff --git a/docs/api/transfer.md b/docs/api/transfer.md index 17e07ad..1815273 100644 --- a/docs/api/transfer.md +++ b/docs/api/transfer.md @@ -5,8 +5,8 @@ sources. In the current runtime the built-in models are: -- direct substitution (`SubstitutionTransfer`), -- one-predictor linear transfer (`LinearTransfer`). +- direct substitution ([SubstitutionTransfer][atomref.SubstitutionTransfer]), +- one-predictor linear transfer ([LinearTransfer][atomref.LinearTransfer]). A transfer source may be: @@ -37,3 +37,14 @@ against a partial covalent-radii policy that is itself completed from a broader support set. ::: atomref.transfer + options: + members: + - TransferValueSource + - SupportsValuePolicy + - LinearFit + - SubstitutionTransfer + - LinearTransfer + - TransferModel + filters: + - "!^_[^_]" + - "!^__post_init__$" diff --git a/docs/api/xh.md b/docs/api/xh.md index f96db27..8d78357 100644 --- a/docs/api/xh.md +++ b/docs/api/xh.md @@ -6,7 +6,7 @@ current release line. It is intentionally narrow: - one packaged sparse target dataset, `csd_legacy_xh_cno`, -- one wrapper policy, `XHPolicy`, +- one wrapper policy, [XHPolicy][atomref.XHPolicy], - convenience helpers for listing packaged X–H sets and resolving X–H values. The built-in quantity is keyed by the **parent element `X`** in `X–H` and is @@ -19,6 +19,21 @@ In the default policy: - other parent elements may be inferred from `cordero2008`, - policy-backed predictors are supported as well, with conservative nested-fit defaults and one additional nested prediction step allowed by default, -- fuller X–H literature support is planned for `0.2.x`. +- the API does not infer a rigorous molecular bond length or perform atom + typing beyond the parent-element policy. ::: atomref.xh + options: + members: + - XHSet + - XHPolicy + - DEFAULT_XH_POLICY + - list_xh_sets + - list_xh_set_infos + - get_xh_set_info + - get_xh_set + - lookup_xh_bond_length + - get_xh_bond_length + filters: + - "!^_[^_]" + - "!^__post_init__$" diff --git a/mkdocs.yml b/mkdocs.yml index 4d0c26d..bdab673 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,8 +19,25 @@ plugins: python: paths: [src] options: + docstring_style: google show_root_heading: true show_source: false + show_signature: true + separate_signature: true + show_signature_annotations: true + signature_crossrefs: true + merge_init_into_class: true + annotations_path: brief + show_docstring_parameters: true + show_docstring_returns: true + show_docstring_raises: true + show_docstring_attributes: true + show_docstring_examples: true + docstring_options: + returns_multiple_items: false + filters: + - "!^_[^_]" + - "!^__post_init__$" nav: - Home: index.md @@ -53,6 +70,7 @@ nav: - Overview: api/index.md - atomref: api/atomref.md - atomref.elements: api/elements.md + - atomref.errors: api/errors.md - atomref.registry: api/registry.md - atomref.transfer: api/transfer.md - atomref.policy: api/policy.md diff --git a/src/atomref/__init__.py b/src/atomref/__init__.py index e7e8682..905d681 100644 --- a/src/atomref/__init__.py +++ b/src/atomref/__init__.py @@ -1,4 +1,4 @@ -"""Public package exports for :mod:`atomref`.""" +"""Public package exports for `atomref`.""" from .__about__ import __version__ from .elements import ( diff --git a/src/atomref/elements.py b/src/atomref/elements.py index 5245b80..4b062de 100644 --- a/src/atomref/elements.py +++ b/src/atomref/elements.py @@ -15,7 +15,17 @@ @dataclass(frozen=True, slots=True) class Element: - """Chemical element identity keyed by atomic number and symbol.""" + """Chemical element identity keyed by atomic number and symbol. + + Attributes: + z: Atomic number. Packaged elements span 1 (H) through 118 (Og). + symbol: Conventional case-sensitive element symbol. + name: English element name. + + Examples: + >>> get_element("cl") + Element(z=17, symbol='Cl', name='Chlorine') + """ z: int symbol: str @@ -49,7 +59,28 @@ def canonicalize_element_symbol(token: str | None) -> str | None: The function accepts strings such as ``"cl"``, ``" Cl "`` or ``"Cl12"`` and returns ``"Cl"`` when a leading element-like token can be - identified. Missing-value markers and non-element strings return ``None``. + identified. It normalizes spelling but does not validate that the result is + a known element. + + Args: + token: Free-form token, or `None`. Empty strings and the missing-value + markers ``"?"`` and ``"."`` are treated as missing. + + Returns: + A conventionally capitalized leading element-like token, or `None` if + no such token is present. + + Examples: + >>> canonicalize_element_symbol(" Cl12 ") + 'Cl' + >>> canonicalize_element_symbol("?") is None + True + + Notes: + Call + [is_valid_element_symbol][atomref.elements.is_valid_element_symbol] or + [get_element][atomref.elements.get_element] when membership in the + packaged periodic table must also be checked. """ raw = _normalize_element_token(token) @@ -88,7 +119,21 @@ def _elements_in_z_order() -> tuple[Element, ...]: def is_valid_element_symbol(symbol: str | None) -> bool: - """Return ``True`` if ``symbol`` is a known packaged element symbol.""" + """Check whether a canonical symbol is present in the periodic table. + + Args: + symbol: Case-sensitive canonical symbol, or `None`. + + Returns: + `True` only for an exact packaged symbol. This function does not trim or + canonicalize its argument. + + Examples: + >>> is_valid_element_symbol("Cl") + True + >>> is_valid_element_symbol("cl") + False + """ if symbol is None: return False @@ -96,7 +141,23 @@ def is_valid_element_symbol(symbol: str | None) -> bool: def get_element(symbol: str | None) -> Element | None: - """Look up packaged element identity from a symbol-like token.""" + """Look up packaged element identity from a symbol-like token. + + Args: + symbol: Free-form symbol token accepted by + [canonicalize_element_symbol][atomref.elements.canonicalize_element_symbol], + or `None`. + + Returns: + The matching immutable [Element][atomref.elements.Element], or `None` if + the token is missing or does not identify a packaged element. + + Examples: + >>> get_element(" Cl12 ").z + 17 + >>> get_element("not-an-element") is None + True + """ sym = canonicalize_element_symbol(symbol) if sym is None: @@ -105,6 +166,15 @@ def get_element(symbol: str | None) -> Element | None: def iter_elements() -> tuple[Element, ...]: - """Return all packaged elements in increasing atomic-number order.""" + """Return all packaged elements in increasing atomic-number order. + + Returns: + An immutable tuple containing H through Og, ordered by atomic number. + + Examples: + >>> elements = iter_elements() + >>> elements[0].symbol, elements[-1].symbol + ('H', 'Og') + """ return _elements_in_z_order() diff --git a/src/atomref/errors.py b/src/atomref/errors.py index d31660a..07c1402 100644 --- a/src/atomref/errors.py +++ b/src/atomref/errors.py @@ -1,17 +1,45 @@ -"""Package-local exceptions used across :mod:`atomref`.""" +"""Catchable exceptions raised by `atomref` submodules.""" class AtomrefError(Exception): - """Base class for package-defined errors.""" + """Base class for package-defined operational errors. + + Notes: + The exception classes are documented from `atomref.errors` but are not + re-exported from the top-level `atomref` namespace. + """ class DatasetError(AtomrefError): - """Raised when packaged data or registry metadata are invalid.""" + """Report an unavailable, unknown, or malformed dataset. + + Examples: + Catch this exception when a user-selected dataset identifier may be + unavailable: + + >>> from atomref.errors import DatasetError + >>> try: + ... raise DatasetError("unknown dataset") + ... except DatasetError: + ... pass + """ class MissingValueError(AtomrefError): - """Raised when a required reference value is unavailable.""" + """Report that an operation requires an unavailable reference value. + + Notes: + Ordinary lookup functions generally represent missing scientific data + with `None` or a missing [LookupResult][atomref.LookupResult] rather than + raising this exception. + """ class PolicyError(AtomrefError): - """Raised for invalid policy configuration or transfer resolution.""" + """Report invalid policy configuration or transfer resolution. + + Examples: + This includes incompatible radii kinds, invalid transfer controls, and + cyclic nested policies. Callers that accept user-authored policies can + catch `PolicyError` to distinguish those failures from missing values. + """ diff --git a/src/atomref/policy.py b/src/atomref/policy.py index 6e6b960..92105b1 100644 --- a/src/atomref/policy.py +++ b/src/atomref/policy.py @@ -41,6 +41,7 @@ "fallback", "missing", ] +"""Provenance labels emitted by the scalar policy resolver.""" PolicyToken = tuple[str, int] _ACTIVE_POLICY_TOKENS: contextvars.ContextVar[tuple[PolicyToken, ...]] = ( @@ -52,10 +53,29 @@ class LookupResult: """Result of resolving one value through a policy. - ``value`` carries the final scalar value when one could be produced, while - ``source`` and the remaining metadata explain how that value was obtained. - ``transfer_depth`` counts how many transfer steps were involved in producing - the returned value. Direct base and override values therefore have depth 0. + Attributes: + value: Resolved scalar in the target policy's units, or `None` when no + rule supplied a value. + source: Rule that supplied `value`, or ``"missing"``. + target: Dataset identity the policy is resolving. + resolved_from: Ordered source datasets contributing to a transferred + value. + is_placeholder: Whether the returned scalar equals its source dataset's + declared placeholder value. + fit: Linear-fit diagnostics when `source` is ``"transfer_linear"``. + notes: Human-readable resolution and rejection diagnostics. + transfer_depth: Number of transfer steps involved. Base, override, + fallback, and missing results have depth 0. + + Examples: + >>> import atomref as ar + >>> result = ar.lookup_covalent_radius("C") + >>> result.value, result.source + (0.76, 'base') + + Notes: + `is_placeholder` describes the returned numeric value, not whether a + transfer occurred. """ value: float | None @@ -68,7 +88,14 @@ class LookupResult: transfer_depth: int = 0 def __float__(self) -> float: - """Coerce the resolved value to ``float`` or raise if it is missing.""" + """Coerce a present resolved value to `float`. + + Returns: + The resolved scalar value. + + Raises: + TypeError: If this result represents a missing value. + """ if self.value is None: raise TypeError("reference value is missing") @@ -79,10 +106,37 @@ def __float__(self) -> float: class ValuePolicy(Generic[K]): """Ordered rule set for resolving element-domain scalar values. - The current runtime resolves only element-domain policies even though the - metadata layer already records a more general ``domain`` concept. During - construction, element-domain override keys are normalized to canonical - element symbols and validated as finite floats. + Attributes: + base: Packaged [DatasetRef][atomref.registry.DatasetRef] or custom + [ElementScalarSet][atomref.registry.ElementScalarSet] that owns the + target quantity and units. + transfers: Ordered substitution or linear-transfer rules. Defaults to + no transfers. + overrides: Explicit key-to-value replacements checked before the base + set. Element keys are canonicalized and values must be finite. + fallback: Final finite scalar used after all transfers fail, or `None`. + Defaults to `None`. + blocked: Element symbols that must resolve as missing. Blocked keys take + precedence over overrides and all other rules. + + Raises: + DatasetError: If the base reference is unknown or has a radial payload. + PolicyError: If fallback, override, or blocked configuration is invalid. + + Examples: + >>> import atomref as ar + >>> policy = ar.ValuePolicy( + ... base=ar.DatasetRef("covalent_radius", "cordero2008"), + ... overrides={"C": 0.77}, + ... ) + >>> ar.get_value("C", policy=policy) + 0.77 + + Notes: + Resolution order is blocked, override, base, transfers, fallback, then + missing. The current resolver supports element-domain scalar data only. + Values are not converted between units; every source in one policy must + be dimensionally compatible with the base set. """ base: ScalarDatasetLike @@ -219,9 +273,10 @@ def _policy_resolution_tokens( ) -> tuple[PolicyToken, ...]: """Return all tokens that should be considered active for one resolution. - We always track the concrete :class:`ValuePolicy` object identity. When a - wrapper object such as :class:`atomref.radii.RadiiPolicy` or - :class:`atomref.xh.XHPolicy` is the logical source, we also track the + We always track the concrete [ValuePolicy][atomref.policy.ValuePolicy] + object identity. When a + wrapper object such as [RadiiPolicy][atomref.RadiiPolicy] or + [XHPolicy][atomref.XHPolicy] is the logical source, we also track the wrapper identity so recursion through freshly materialized generic policies is still detected. """ @@ -782,14 +837,50 @@ def _get_value_from_policy_source( def lookup_value(symbol: str | None, *, policy: ValuePolicy[str]) -> LookupResult: """Public entry point for generic element-domain scalar lookup. - This is the same resolver used internally by the radii convenience layer. - In the current implementation the runtime supports only element-domain policies. + Args: + symbol: Symbol-like element token, or `None`. D/T map to H. + policy: Element-domain scalar policy to apply. + + Returns: + A [LookupResult][atomref.policy.LookupResult] containing the value or an + explicit missing result, together with provenance and transfer + diagnostics. + + Raises: + DatasetError: If a referenced dataset is unknown or non-scalar. + PolicyError: If transfer configuration is invalid, fitting cannot meet + its contract, or nested policies form a cycle. + + Examples: + >>> import atomref as ar + >>> policy = ar.DEFAULT_COVALENT_POLICY.as_value_policy() + >>> result = ar.lookup_value("O", policy=policy) + >>> result.value + 0.66 + + Notes: + Invalid or uncovered elements normally produce `source="missing"` + rather than raising. This is the same resolver used by the radii and + X-H convenience layers. """ return _lookup_value_with_owner(symbol, policy=policy, owner=None) def get_value(symbol: str | None, *, policy: ValuePolicy[str]) -> float | None: - """Return only the resolved scalar value for an element-domain policy.""" + """Return only the scalar selected by an element-domain policy. + + Args: + symbol: Symbol-like element token, or `None`. D/T map to H. + policy: Element-domain scalar policy to apply. + + Returns: + The selected finite scalar in the policy's target units, or `None` when + resolution is missing. + + Raises: + DatasetError: If a referenced dataset is unknown or non-scalar. + PolicyError: If transfer configuration or nested resolution is invalid. + """ return lookup_value(symbol, policy=policy).value diff --git a/src/atomref/proatoms.py b/src/atomref/proatoms.py index 9d88e8d..9b6113d 100644 --- a/src/atomref/proatoms.py +++ b/src/atomref/proatoms.py @@ -55,25 +55,76 @@ class IASPositionResult: """Immutable result of one pairwise neutral-proatom estimate. - Coordinates are measured from ``atom_a`` toward ``atom_b`` and are - reported in ``distance_unit``. Density-valued fields are reported in - ``density_unit``. A valid but scientifically non-applicable request uses - ``method="none"`` and leaves its coordinate fields as :data:`None`. - - Reversing the atoms maps primary and alternative coordinates ``x`` to - ``R - x``, swaps A/B component fields, and relabels dominance. Method, - status, summed density, cutoff regime, and orientation-independent search - diagnostics remain equivalent under that relabeling. - - ``boundary_dominated`` takes status precedence over ``search_unstable``, - which takes precedence over ``ambiguous_competing_minima``. The separate - ``search_converged`` and ``ambiguous`` fields preserve all diagnostics. - - At an odd subnormal homonuclear distance, the mathematical midpoint is not - representable in binary64. In that case the coordinate is the ordinary - floating-point result of ``R / 2``, the complementary coordinate preserves - the requested distance, and the fraction is derived from the exposed - coordinate rather than claiming an unrepresentable ``0.5`` position. + Attributes: + atom_a: Canonical symbol of the first requested atom. + atom_b: Canonical symbol of the second requested atom. + distance: Requested positive pair distance, no greater than 20 bohr + after conversion. + distance_unit: Unit used by `distance`, coordinates, cutoff radii, + contour separation, and search resolution: ``"angstrom"`` or + ``"bohr"``. + density_unit: Unit used by all density-valued fields: + ``"electron/bohr^3"`` or ``"electron/angstrom^3"``. + requested_mode: Requested ``"boundary"`` or ``"minimum"`` policy. + method: Actual construction: symmetry midpoint, equal-proatom divider, + cutoff-gap midpoint, promolecular minimum, or ``"none"``. + status: Scientific and numerical outcome. Callers should inspect this + together with `method` and `position_from_a`. + position_from_a: Primary coordinate measured from atom A toward atom B + in `distance_unit`, or `None` for a typed non-result. + position_from_b: Complementary coordinate measured from atom B in + `distance_unit`, or `None`. + fraction_from_a: `position_from_a / distance`, normally in [0, 1], or + `None` when no coordinate is returned. + rho_a: Atom A component density at the primary coordinate, or `None`. + rho_b: Atom B component density at the primary coordinate, or `None`. + rho_sum: Sum of the two components at the primary coordinate, or `None`. + cutoff_density: Fixed per-atom tail cutoff converted to `density_unit`. + cutoff_radius_a: Atom A radius at the fixed cutoff in `distance_unit`. + cutoff_radius_b: Atom B radius at the fixed cutoff in `distance_unit`. + contour_separation: Signed `distance - cutoff_radius_a - + cutoff_radius_b` in `distance_unit`; positive values indicate a gap. + cutoff_regime: ``"overlap"``, ``"contact"``, or ``"gap"`` according to + the signed contour separation. + dominant_atom: Canonical symbol of the atom that dominates an unlike + interval, or `None`. + dominant_atom_role: Whether `dominant_atom` is ``"atom_a"`` or + ``"atom_b"``, or `None`. + alternative_position_from_a: Competitive alternative-minimum coordinate + from atom A in `distance_unit`, or `None`. + alternative_position_from_b: Complementary alternative coordinate from + atom B in `distance_unit`, or `None`. + alternative_rho_sum: Summed density at the alternative, or `None`. + relative_depth_gap: Nonnegative dimensionless relative density gap + between alternative and selected minima, or `None`. + ambiguous: Whether a competitive resolved alternative meets the fixed + relative-depth criterion. + search_resolution: Finest minimum-search spacing in `distance_unit`, or + `None` when minimum search is not applicable. + search_converged: Whether required minimum-search passes agreed, or + `None` outside applicable minimum searches. + search_passes: Number of practical minimum-search passes, or `None` when + not applicable. + dataset_id: Canonical packaged proatomic-density dataset ID. + interpolation_contract: Stable radial interpolation identifier. + pairwise_contract: Stable cutoff and numerical-search identifier. + coordinate_orientation: Explicit orientation label. The default and + current value is ``"from_atom_a_toward_atom_b"``. + + Notes: + A valid but scientifically non-applicable request uses ``method="none"`` + and leaves coordinate fields as `None`. Reversing the atoms maps each + coordinate `x` to `R - x`, swaps A/B fields, and relabels dominance. + + ``"boundary_dominated"`` takes status precedence over + ``"search_unstable"``, which takes precedence over + ``"ambiguous_competing_minima"``. Separate boolean diagnostics preserve + the underlying conditions. Neither mode is an exact molecular QTAIM + surface or critical-point calculation. + + At an odd subnormal homonuclear distance, the mathematical midpoint may + not be representable in binary64. The exposed coordinate then uses the + ordinary `R / 2` result and preserves the complementary distance. """ atom_a: str @@ -164,12 +215,36 @@ def _coerce_public_max_radius(storage: Mapping[str, object], ref: DatasetRef) -> class ProatomicDensityProfile: """Immutable view of one neutral spherical proatomic-density profile. - Radii and stored densities use the dataset's native units, bohr and - electron/bohr^3. Calling the profile evaluates one scalar coordinate using - positive-region log-log interpolation. The public domain ends at the - dataset-declared maximum (20 bohr for the packaged neutral H-Lr set). At the - origin and below the first finite grid point, the first stored value is - returned as a finite-grid convention rather than an exact nuclear value. + Attributes: + dataset: Immutable radial dataset owning the shared grid and profiles. + atomic_number: Selected integer atomic number. The packaged neutral set + supports 1 (H) through 103 (Lr). + symbol: Canonical element symbol initialized from `atomic_number`. + ref: Canonical registry reference for `dataset`. + info: Curated metadata and provenance for `dataset`. + radii: Shared immutable source grid in bohr, including the endpoint + bracket above the public domain. + densities: Immutable sampled values in electron/bohr^3. + interpolation_contract: Stable identifier for evaluation behavior. + public_max_radius_bohr: Inclusive public radius limit in bohr; 20 for + the packaged neutral H-Lr set. + + Raises: + DatasetError: If the atomic number is invalid, the profile is absent, + or radial data or metadata violate the interpolation contract. + + Examples: + >>> profile = get_proatomic_density_profile("O") + >>> profile is not None + True + >>> profile(1.5, radius_unit="bohr") > 0.0 + True + + Notes: + Evaluation is scalar and uses positive-region log-log interpolation. + At the origin and below the first finite grid point, the first stored + value is returned as a finite-grid convention, not an exact nuclear + density. """ dataset: ElementRadialSet = field(repr=False) @@ -240,37 +315,63 @@ def __post_init__(self) -> None: @property def ref(self) -> DatasetRef: - """Canonical registry reference for the source dataset.""" + """Return the canonical registry reference. + + Returns: + Source [DatasetRef][atomref.registry.DatasetRef]. + """ return self.dataset.ref @property def info(self) -> DatasetInfo: - """Curated metadata and provenance for the source dataset.""" + """Return curated source metadata and provenance. + + Returns: + Source [DatasetInfo][atomref.registry.DatasetInfo]. + """ return self.dataset.info @property def radii(self) -> tuple[float, ...]: - """Shared immutable source grid in bohr, including the endpoint bracket.""" + """Return the shared immutable source grid. + + Returns: + Positive radii in bohr, including the endpoint bracket above the + 20-bohr public domain. + """ return self.dataset.radii @property def densities(self) -> tuple[float, ...]: - """Immutable stored density values in electron/bohr^3.""" + """Return immutable stored density values. + + Returns: + Positive sampled values in electron/bohr^3, aligned with `radii`. + """ return self._densities @property def interpolation_contract(self) -> str: - """Stable identifier for this profile's interpolation behavior.""" + """Return the interpolation-contract identifier. + + Returns: + ``"loglog_positive_bracketed_v1"``. + """ return _INTERPOLATION_CONTRACT @property def public_max_radius_bohr(self) -> float: - """Largest supported public radius coordinate, in bohr.""" + """Return the inclusive public radius limit. + + Returns: + Largest supported coordinate in bohr; 20 for the packaged neutral + dataset. + """ return self._public_max_radius_bohr @@ -285,11 +386,28 @@ def evaluate( ) -> float: """Evaluate the density at one radius. - ``radius_unit`` accepts ``"angstrom"`` or ``"bohr"`` independently - of ``density_unit``, which accepts ``"electron/bohr^3"`` or - ``"electron/angstrom^3"``. Invalid units, negative or non-finite - radii, and coordinates above the dataset-declared public maximum raise - :class:`ValueError`. The packaged neutral H-Lr set uses 20 bohr. + Args: + radius: Finite scalar coordinate in `radius_unit`. It must map to + the inclusive interval 0 through 20 bohr for the packaged set. + radius_unit: ``"angstrom"`` (default) or ``"bohr"``. + density_unit: ``"electron/bohr^3"`` (default) or + ``"electron/angstrom^3"``. This choice is independent of the + coordinate unit. + + Returns: + Finite positive interpolated density in `density_unit`. + + Raises: + ValueError: If a unit is unknown or `radius` is negative, + non-finite, nonscalar, or above the public limit. + DatasetError: If the stored radial grid cannot bracket evaluation. + + Examples: + >>> profile = get_proatomic_density_profile("O") + >>> profile is not None + True + >>> profile.evaluate(0.75) > 0.0 + True """ radius_bohr = _radius_to_bohr( @@ -309,7 +427,22 @@ def __call__( "electron/bohr^3", "electron/angstrom^3" ] = "electron/bohr^3", ) -> float: - """Evaluate the density; equivalent to :meth:`evaluate`.""" + """Evaluate the density; equivalent to + [evaluate][atomref.proatoms.ProatomicDensityProfile.evaluate]. + + Args: + radius: Finite coordinate from 0 through the public 20-bohr limit. + radius_unit: ``"angstrom"`` (default) or ``"bohr"``. + density_unit: ``"electron/bohr^3"`` (default) or + ``"electron/angstrom^3"``. + + Returns: + Finite positive interpolated density in `density_unit`. + + Raises: + ValueError: If a unit or radius is outside the public contract. + DatasetError: If the stored radial data cannot support evaluation. + """ return self.evaluate( radius, @@ -399,13 +532,28 @@ def _density_from_native( def list_proatomic_density_sets() -> tuple[str, ...]: - """List packaged proatomic-density dataset identifiers.""" + """List packaged proatomic-density dataset identifiers. + + Returns: + Canonical set IDs in curated registry order. + + Raises: + DatasetError: If registry metadata is unavailable or malformed. + """ return list_dataset_ids(_QUANTITY) def list_proatomic_density_set_infos() -> tuple[DatasetInfo, ...]: - """Return metadata for all packaged proatomic-density datasets.""" + """Return metadata for all packaged proatomic-density datasets. + + Returns: + Immutable [DatasetInfo][atomref.registry.DatasetInfo] objects in curated + registry order. + + Raises: + DatasetError: If registry metadata is unavailable or malformed. + """ return list_dataset_infos(_QUANTITY) @@ -413,7 +561,18 @@ def list_proatomic_density_set_infos() -> tuple[DatasetInfo, ...]: def get_proatomic_density_set_info( set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, ) -> DatasetInfo: - """Return metadata for one packaged proatomic-density dataset or alias.""" + """Return metadata for one packaged proatomic-density dataset. + + Args: + set_id: Canonical set ID or accepted alias. Defaults to + [DEFAULT_PROATOMIC_DENSITY_SET][atomref.proatoms.DEFAULT_PROATOMIC_DENSITY_SET]. + + Returns: + Curated method, units, provenance, coverage, and storage metadata. + + Raises: + DatasetError: If the set is unknown or metadata is malformed. + """ return get_dataset_info(DatasetRef(_QUANTITY, set_id)) @@ -421,7 +580,19 @@ def get_proatomic_density_set_info( def get_proatomic_density_set( set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, ) -> ProatomicDensitySet: - """Return one cached immutable packaged proatomic-density dataset.""" + """Return one cached immutable packaged proatomic-density dataset. + + Args: + set_id: Canonical set ID or accepted alias. Defaults to + [DEFAULT_PROATOMIC_DENSITY_SET][atomref.proatoms.DEFAULT_PROATOMIC_DENSITY_SET]. + + Returns: + Element-indexed neutral profiles sharing one radial grid. + + Raises: + DatasetError: If the set is unknown, malformed, or has a scalar rather + than radial payload. + """ loaded = get_builtin_set(DatasetRef(_QUANTITY, set_id)) if not isinstance(loaded, ElementRadialSet): @@ -487,11 +658,31 @@ def get_proatomic_density_profile( ) -> ProatomicDensityProfile | None: """Return a cached neutral profile, or ``None`` for unsupported elements. - Element symbols are canonicalized using the package's normal element rules, - and integer atomic numbers are accepted. Deuterium (``D``) and tritium - (``T``) return hydrogen's electronic profile. Invalid values, including - booleans, return ``None``. No substitution, correlation, or ionic selection - is performed. + Args: + element: Element symbol, integer atomic number, or `None`. D/T map to + hydrogen's electronic profile; booleans are rejected. + set_id: Canonical set ID or accepted alias. Defaults to + [DEFAULT_PROATOMIC_DENSITY_SET][atomref.proatoms.DEFAULT_PROATOMIC_DENSITY_SET]. + + Returns: + A cached + [ProatomicDensityProfile][atomref.proatoms.ProatomicDensityProfile], or + `None` for an invalid, unsupported, or uncovered element. The packaged + set covers H through Lr. + + Raises: + DatasetError: If the selected dataset is unknown, malformed, or + non-radial. + + Examples: + >>> get_proatomic_density_profile(8).symbol + 'O' + >>> get_proatomic_density_profile("Og") is None + True + + Notes: + No neighboring-element substitution, correlation, ionic selection, or + scalar [ValuePolicy][atomref.ValuePolicy] is applied. """ resolved = _resolve_density_element(element) @@ -516,10 +707,33 @@ def get_proatomic_density( ) -> float | None: """Evaluate one neutral proatomic density, or return ``None`` if absent. - The default coordinate unit is angstrom and the default output unit is - electron/bohr^3. The supported interval ends at the dataset-declared public - maximum (20 bohr for the packaged neutral H-Lr set). Evaluation is scalar - and dependency-free. + Args: + element: Element symbol, integer atomic number, or `None`. D/T map to H. + radius: Finite nonnegative scalar coordinate in `radius_unit`, no greater + than 20 bohr after conversion. + set_id: Canonical set ID or alias. Defaults to the packaged neutral set. + radius_unit: ``"angstrom"`` (default) or ``"bohr"``. + density_unit: ``"electron/bohr^3"`` (default) or + ``"electron/angstrom^3"``. + + Returns: + Finite positive scalar density in `density_unit`, or `None` for an + invalid, unsupported, or uncovered element. + + Raises: + ValueError: If a unit is unknown or the radius is negative, non-finite, + nonscalar, or above 20 bohr after conversion. + DatasetError: If the selected dataset is unknown, malformed, or + non-radial. + + Examples: + >>> rho = get_proatomic_density("O", 0.75) + >>> rho is not None and rho > 0.0 + True + + Notes: + Radius and density units are independent. Evaluation is scalar and + dependency-free, with no extrapolation beyond 20 bohr. """ profile = get_proatomic_density_profile(element, set_id=set_id) @@ -1600,10 +1814,41 @@ def estimate_proatomic_boundary( ) -> IASPositionResult | None: """Estimate a stable pairwise neutral-proatom boundary. - The coordinate is measured from ``atom_a`` toward ``atom_b``. Overlapping - unlike profiles use equal neutral-proatom density; separated fixed-cutoff - contours use the midpoint of their gap. This is not a molecular QTAIM - zero-flux surface. + Args: + atom_a: First element symbol or atomic number. D/T map to H. + atom_b: Second element symbol or atomic number. D/T map to H. + distance: Finite positive pair distance in `distance_unit`, no greater + than 20 bohr after conversion. + distance_unit: ``"angstrom"`` (default) or ``"bohr"``. Returned + coordinates and cutoff radii use the same unit. + density_unit: ``"electron/bohr^3"`` (default) or + ``"electron/angstrom^3"`` for reported density fields. + set_id: Canonical proatomic-density set ID or alias. Defaults to + [DEFAULT_PROATOMIC_DENSITY_SET][atomref.proatoms.DEFAULT_PROATOMIC_DENSITY_SET]. + + Returns: + An [IASPositionResult][atomref.proatoms.IASPositionResult] oriented from + atom A toward atom B, or `None` when either profile is invalid or + unavailable. A valid one-atom-dominance case returns a typed result with + no coordinate. + + Raises: + ValueError: If distance or a unit is outside the public contract. + DatasetError: If the selected dataset is unknown, malformed, or + non-radial. + + Examples: + >>> result = estimate_proatomic_boundary("C", "O", 1.5) + >>> result is not None + True + >>> result.requested_mode + 'boundary' + + Notes: + Homonuclear pairs use the exact midpoint. Overlapping unlike profiles + use equal neutral-proatom density; separated fixed-cutoff contours use + the midpoint of their gap. This stable divider is not a molecular QTAIM + zero-flux surface. """ return _estimate_pairwise( @@ -1630,10 +1875,45 @@ def estimate_promolecular_density_minimum( ) -> IASPositionResult | None: """Estimate one practically resolved promolecular line-density minimum. - The search is confined to the interval where both neutral proatomic - components meet the fixed tail cutoff. It has a declared resolution of - :data:`IAS_MINIMUM_RESOLUTION_BOHR`, exposes only strict-interior unlike-atom - candidates, and never falls back to boundary mode. + Args: + atom_a: First element symbol or atomic number. D/T map to H. + atom_b: Second element symbol or atomic number. D/T map to H. + distance: Finite positive pair distance in `distance_unit`, no greater + than 20 bohr after conversion. + distance_unit: ``"angstrom"`` (default) or ``"bohr"``. Returned + coordinates and search resolution use the same unit. + density_unit: ``"electron/bohr^3"`` (default) or + ``"electron/angstrom^3"`` for reported density fields. + set_id: Canonical proatomic-density set ID or alias. Defaults to + [DEFAULT_PROATOMIC_DENSITY_SET][atomref.proatoms.DEFAULT_PROATOMIC_DENSITY_SET]. + + Returns: + An [IASPositionResult][atomref.proatoms.IASPositionResult] oriented from + atom A toward atom B, or `None` when either profile is invalid or + unavailable. A valid pair with no resolved strict-interior minimum + returns a typed result with no coordinate. + + Raises: + ValueError: If distance or a unit is outside the public contract. + DatasetError: If the selected dataset is unknown, malformed, or + non-radial. + + Examples: + >>> result = estimate_promolecular_density_minimum("C", "O", 1.5) + >>> result is not None + True + >>> result.requested_mode + 'minimum' + + Notes: + Search is confined to the interval where both neutral components meet + [PROATOMIC_TAIL_CUTOFF][atomref.proatoms.PROATOMIC_TAIL_CUTOFF]. It has + declared resolution + [IAS_MINIMUM_RESOLUTION_BOHR][atomref.proatoms.IAS_MINIMUM_RESOLUTION_BOHR], + exposes only strict-interior unlike-atom candidates, and never falls + back to boundary mode. It is a practical + neutral-promolecular proxy, not an exact molecular-density critical + point. """ return _estimate_pairwise( @@ -1659,7 +1939,41 @@ def estimate_ias_position( ] = "electron/bohr^3", set_id: str = DEFAULT_PROATOMIC_DENSITY_SET, ) -> IASPositionResult | None: - """Dispatch explicitly to one of the two pairwise neutral-proatom modes.""" + """Dispatch explicitly to one pairwise neutral-proatom mode. + + Args: + atom_a: First element symbol or atomic number. D/T map to H. + atom_b: Second element symbol or atomic number. D/T map to H. + distance: Finite positive pair distance in `distance_unit`, no greater + than 20 bohr after conversion. + mode: ``"boundary"`` (default) or ``"minimum"``. + distance_unit: ``"angstrom"`` (default) or ``"bohr"``. + density_unit: ``"electron/bohr^3"`` (default) or + ``"electron/angstrom^3"``. + set_id: Canonical proatomic-density set ID or alias. Defaults to + [DEFAULT_PROATOMIC_DENSITY_SET][atomref.proatoms.DEFAULT_PROATOMIC_DENSITY_SET]. + + Returns: + The same [IASPositionResult][atomref.proatoms.IASPositionResult] or + missing-profile `None` produced by the corresponding direct estimator. + + Raises: + ValueError: If `mode`, distance, or a unit is outside the public + contract. + DatasetError: If the selected dataset is unknown, malformed, or + non-radial. + + Examples: + >>> direct = estimate_proatomic_boundary("C", "O", 1.5) + >>> selected = estimate_ias_position("C", "O", 1.5) + >>> selected == direct + True + + Notes: + The minimum mode never silently falls back to boundary mode. The two + modes are related scientific approximations, not interchangeable names + for one calculation. + """ if mode not in ("boundary", "minimum"): raise ValueError(f"unknown IAS position mode: {mode!r}") diff --git a/src/atomref/radii.py b/src/atomref/radii.py index 2855fc1..bd54ef8 100644 --- a/src/atomref/radii.py +++ b/src/atomref/radii.py @@ -28,7 +28,10 @@ from .transfer import LinearFit, LinearTransfer, SubstitutionTransfer, TransferModel RadiiKind = Literal["covalent", "van_der_waals"] +"""Supported radii quantity selector.""" + RadiiSet = ElementScalarSet +"""Typing alias for an immutable element-indexed radii dataset.""" _KIND_TO_QUANTITY = { @@ -41,8 +44,26 @@ class RadiiPolicy: """Policy wrapper specialized for radii lookup. - ``kind`` determines the target quantity, while the remaining fields mirror - the generic :class:`atomref.policy.ValuePolicy` interface. + Attributes: + kind: Target radii quantity, ``"covalent"`` or ``"van_der_waals"``. + base_set: Packaged set ID or custom + [ElementScalarSet][atomref.registry.ElementScalarSet]. + transfers: Ordered substitution or linear-transfer rules. Defaults to + no transfers. + overrides: Explicit finite, nonnegative element values checked before + the base set. Defaults to an empty mapping. + fallback: Final finite, nonnegative value, or `None`. Defaults to `None`. + + Examples: + >>> import atomref as ar + >>> policy = ar.RadiiPolicy(kind="covalent", base_set="cordero2008") + >>> ar.get_covalent_radius("C", policy=policy) + 0.76 + + Notes: + Packaged radii use angstrom. Custom sets, overrides, fallbacks, and + transfer sources must use compatible units because policies do not + perform unit conversion. """ kind: RadiiKind @@ -52,7 +73,17 @@ class RadiiPolicy: fallback: float | None = None def as_value_policy(self) -> ValuePolicy[str]: - """Convert the radii policy into the generic scalar-value policy.""" + """Convert this wrapper into the generic scalar policy. + + Returns: + An element-domain [ValuePolicy][atomref.policy.ValuePolicy] + preserving the configured rule order. + + Raises: + DatasetError: If a packaged set is unknown or non-scalar. + PolicyError: If `kind`, base quantity, override, or fallback is + invalid. + """ quantity = _quantity_for_kind(self.kind) if isinstance(self.base_set, ElementScalarSet): @@ -92,7 +123,12 @@ def as_value_policy(self) -> ValuePolicy[str]: @dataclass(frozen=True, slots=True) class RadiiElementAssessment: - """Per-element row in a radii policy assessment report.""" + """Per-element row in a radii policy assessment report. + + Attributes: + symbol: Canonical element symbol. + lookup: Full lookup result for that element. + """ symbol: str lookup: LookupResult @@ -100,7 +136,27 @@ class RadiiElementAssessment: @dataclass(frozen=True, slots=True) class RadiiPolicyAssessment: - """Summary of how a radii policy behaved over a set of elements.""" + """Summary of how a radii policy behaved over a set of elements. + + Attributes: + kind: Assessed radii quantity. + policy: Policy that was assessed. + elements: Canonical, deduplicated symbols in atomic-number order. + n_elements: Number of assessed elements. + n_override: Results supplied by explicit overrides. + n_base: Results supplied directly by the base set. + n_transfer_substitution: Results supplied by substitution transfers. + n_transfer_linear: Results supplied by linear transfers. + n_fallback: Results supplied by the fallback. + n_missing: Elements without a resolved value. + n_placeholders: Returned values equal to a declared placeholder. + missing_symbols: Symbols counted by `n_missing`. + placeholder_symbols: Symbols counted by `n_placeholders`. + fits: Successful linear-fit diagnostics for configured transfers. + warnings: Fit-assessment errors retained as report warnings. + per_element: Detailed rows when assessment used `detail=True`; otherwise + an empty tuple. + """ kind: RadiiKind policy: RadiiPolicy @@ -126,7 +182,7 @@ class RadiiPolicyAssessment: def _coerce_non_negative_radii_value(value: object, *, what: str) -> float: """Validate a radii-like policy number. - The generic :class:`atomref.policy.ValuePolicy` accepts any finite scalar. + The generic [ValuePolicy][atomref.ValuePolicy] accepts any finite scalar. Radii-specific convenience helpers are stricter and reject negative values. """ @@ -180,7 +236,19 @@ def list_radii_sets( *, usage_role: str | None = None, ) -> tuple[str, ...]: - """List packaged radii-set ids for one radii kind.""" + """List packaged radii-set IDs for one radii kind. + + Args: + kind: ``"covalent"`` or ``"van_der_waals"``. + usage_role: Optional case-insensitive metadata-role filter. + + Returns: + Canonical set IDs in curated registry order. + + Raises: + PolicyError: If `kind` is unsupported. + DatasetError: If registry metadata is malformed. + """ return list_dataset_ids(_quantity_for_kind(kind), usage_role=usage_role) @@ -190,19 +258,57 @@ def list_radii_set_infos( *, usage_role: str | None = None, ) -> tuple[DatasetInfo, ...]: - """Return packaged metadata objects for radii sets of one kind.""" + """Return packaged metadata objects for radii sets of one kind. + + Args: + kind: ``"covalent"`` or ``"van_der_waals"``. + usage_role: Optional case-insensitive metadata-role filter. + + Returns: + Immutable [DatasetInfo][atomref.registry.DatasetInfo] objects in curated + registry order. + + Raises: + PolicyError: If `kind` is unsupported. + DatasetError: If registry metadata is malformed. + """ return list_dataset_infos(_quantity_for_kind(kind), usage_role=usage_role) def get_radii_set_info(kind: RadiiKind, set_id: str) -> DatasetInfo: - """Return metadata for one packaged radii set.""" + """Return metadata for one packaged radii set. + + Args: + kind: ``"covalent"`` or ``"van_der_waals"``. + set_id: Canonical packaged set ID or accepted alias. + + Returns: + Curated metadata, including angstrom units and provenance. + + Raises: + PolicyError: If `kind` is unsupported. + DatasetError: If `set_id` is unknown or metadata is malformed. + """ return get_dataset_info(DatasetRef(_quantity_for_kind(kind), set_id)) def get_radii_set(kind: RadiiKind, set_id: str) -> RadiiSet: - """Load one packaged radii set as an :class:`ElementScalarSet`.""" + """Load one packaged radii set as an + [ElementScalarSet][atomref.registry.ElementScalarSet]. + + Args: + kind: ``"covalent"`` or ``"van_der_waals"``. + set_id: Canonical packaged set ID or accepted alias. + + Returns: + A cached immutable scalar set whose values are in angstrom. + + Raises: + PolicyError: If `kind` is unsupported. + DatasetError: If the set is unknown, malformed, or non-scalar. + """ return resolve_scalar_dataset_like(DatasetRef(_quantity_for_kind(kind), set_id)) @@ -225,7 +331,24 @@ def lookup_covalent_radius( *, policy: RadiiPolicy | None = None, ) -> LookupResult: - """Resolve a covalent radius together with provenance information.""" + """Resolve a covalent radius together with provenance. + + Args: + symbol: Symbol-like element token, or `None`. D/T map to H. + policy: Covalent [RadiiPolicy][atomref.radii.RadiiPolicy]; `None` selects + [DEFAULT_COVALENT_POLICY][atomref.DEFAULT_COVALENT_POLICY]. + + Returns: + Lookup result whose value is in angstrom, or an explicit missing result. + + Raises: + DatasetError: If a referenced dataset is unknown or non-scalar. + PolicyError: If the policy has the wrong kind or invalid configuration. + + Examples: + >>> lookup_covalent_radius("C").value + 0.76 + """ active = DEFAULT_COVALENT_POLICY if policy is None else policy _validate_policy_kind(active, expected="covalent") @@ -237,7 +360,20 @@ def get_covalent_radius( *, policy: RadiiPolicy | None = None, ) -> float | None: - """Return only the selected covalent-radius value, without provenance.""" + """Return only the selected covalent radius. + + Args: + symbol: Symbol-like element token, or `None`. D/T map to H. + policy: Covalent [RadiiPolicy][atomref.radii.RadiiPolicy]; `None` selects + [DEFAULT_COVALENT_POLICY][atomref.DEFAULT_COVALENT_POLICY]. + + Returns: + Selected radius in angstrom, or `None` when resolution is missing. + + Raises: + DatasetError: If a referenced dataset is unknown or non-scalar. + PolicyError: If the policy has the wrong kind or invalid configuration. + """ active = DEFAULT_COVALENT_POLICY if policy is None else policy _validate_policy_kind(active, expected="covalent") @@ -249,7 +385,20 @@ def lookup_vdw_radius( *, policy: RadiiPolicy | None = None, ) -> LookupResult: - """Resolve a van der Waals radius together with provenance information.""" + """Resolve a van der Waals radius together with provenance. + + Args: + symbol: Symbol-like element token, or `None`. D/T map to H. + policy: van der Waals [RadiiPolicy][atomref.radii.RadiiPolicy]; `None` + selects [DEFAULT_VDW_POLICY][atomref.DEFAULT_VDW_POLICY]. + + Returns: + Lookup result whose value is in angstrom, or an explicit missing result. + + Raises: + DatasetError: If a referenced dataset is unknown or non-scalar. + PolicyError: If the policy has the wrong kind or invalid configuration. + """ active = DEFAULT_VDW_POLICY if policy is None else policy _validate_policy_kind(active, expected="van_der_waals") @@ -261,7 +410,20 @@ def get_vdw_radius( *, policy: RadiiPolicy | None = None, ) -> float | None: - """Return only the selected van der Waals-radius value, without provenance.""" + """Return only the selected van der Waals radius. + + Args: + symbol: Symbol-like element token, or `None`. D/T map to H. + policy: van der Waals [RadiiPolicy][atomref.radii.RadiiPolicy]; `None` + selects [DEFAULT_VDW_POLICY][atomref.DEFAULT_VDW_POLICY]. + + Returns: + Selected radius in angstrom, or `None` when resolution is missing. + + Raises: + DatasetError: If a referenced dataset is unknown or non-scalar. + PolicyError: If the policy has the wrong kind or invalid configuration. + """ active = DEFAULT_VDW_POLICY if policy is None else policy _validate_policy_kind(active, expected="van_der_waals") @@ -274,7 +436,33 @@ def assess_radii_policy( policy: RadiiPolicy, detail: bool = False, ) -> RadiiPolicyAssessment: - """Assess how a radii policy resolves values over a set of elements.""" + """Assess how a radii policy resolves values over a set of elements. + + Args: + elements: Element tokens to normalize, deduplicate, and sort by atomic + number. + policy: Radii policy to evaluate. + detail: Include a + [RadiiElementAssessment][atomref.radii.RadiiElementAssessment] for + each element when `True`. Defaults to `False`. + + Returns: + Counts, missing/placeholder symbols, fit summaries, warnings, and + optional per-element detail in a + [RadiiPolicyAssessment][atomref.radii.RadiiPolicyAssessment]. + + Raises: + ValueError: If an element token is missing or invalid. + DatasetError: If a referenced dataset is unknown or non-scalar. + PolicyError: If policy or transfer configuration is invalid. + + Examples: + >>> report = assess_radii_policy( + ... ["C", "O"], policy=DEFAULT_COVALENT_POLICY, detail=True + ... ) + >>> report.n_elements, len(report.per_element) + (2, 2) + """ elems = _normalize_assessment_elements(elements) value_policy = policy.as_value_policy() diff --git a/src/atomref/registry.py b/src/atomref/registry.py index e7c663f..0674509 100644 --- a/src/atomref/registry.py +++ b/src/atomref/registry.py @@ -20,15 +20,25 @@ from .errors import DatasetError QuantityId = str +"""Typing alias for a registry quantity identifier.""" + DomainId = str +"""Typing alias for a registry lookup-domain identifier.""" @dataclass(frozen=True, slots=True) class DatasetRef: """Stable reference to a packaged dataset. - The ``quantity`` identifies the operational property family, while - ``set_id`` names a specific curated dataset within that family. + Attributes: + quantity: Operational property family, such as ``"covalent_radius"`` + or ``"proatomic_density"``. + set_id: Canonical dataset identifier or an accepted alias when the + reference is passed to a registry lookup. + + Examples: + >>> DatasetRef("covalent_radius", "cordero2008") + DatasetRef(quantity='covalent_radius', set_id='cordero2008') """ quantity: QuantityId @@ -37,7 +47,18 @@ class DatasetRef: @dataclass(frozen=True, slots=True) class Reference: - """Bibliographic record attached to packaged dataset metadata.""" + """Bibliographic record attached to packaged dataset metadata. + + Attributes: + authors: Author string as recorded by the curated metadata. + year: Publication year. + title: Work title. + venue: Journal, repository, or other publication venue. + doi: DOI without an implied URL prefix. + url: Source or publication URL. + publisher: Publisher or archive name. + note: Additional attribution or interpretation note. + """ authors: str | None = None year: int | None = None @@ -51,7 +72,17 @@ class Reference: @dataclass(frozen=True, slots=True) class CoverageInfo: - """Coverage summary for an element-indexed dataset.""" + """Coverage summary for an element-indexed dataset. + + Attributes: + n_values: Number of non-missing element values or profiles. + z_min: Lowest covered atomic number, or `None` for empty coverage. + z_max: Highest covered atomic number, or `None` for empty coverage. + has_placeholders: Whether at least one covered scalar equals the + dataset's declared placeholder value. + covered_z: Covered atomic numbers in increasing order. + missing_z: Missing atomic numbers in increasing order. + """ n_values: int z_min: int | None = None @@ -63,7 +94,15 @@ class CoverageInfo: @dataclass(frozen=True, slots=True) class QuantityInfo: - """Metadata shared by all datasets that belong to one quantity.""" + """Metadata shared by all datasets that belong to one quantity. + + Attributes: + quantity: Registry quantity identifier. + domain: Lookup domain. The current resolver supports ``"element"``. + units: Scientific units shared by the quantity, or `None` when the + quantity is unitless or unspecified. + description: Human-readable quantity description. + """ quantity: QuantityId domain: DomainId @@ -75,9 +114,29 @@ class QuantityInfo: class DatasetInfo: """Curated metadata for one packaged dataset. - This object keeps operational classification such as ``ref.quantity`` and - ``usage_role`` separate from scientific classification such as - ``semantic_class`` and ``phase_context``. + This object keeps operational classification such as `ref.quantity` and + `usage_role` separate from scientific classification such as + `semantic_class` and `phase_context`. + + Attributes: + ref: Canonical quantity and dataset identifier. + domain: Lookup domain, currently ``"element"`` for packaged data. + units: Units of stored scalar values or density profiles. + name: Human-readable dataset name. + description: Concise scientific description. + usage_role: Operational role such as ``"target"`` or ``"support"``. + semantic_class: Scientific class of the values. + origin_class: Origin category used during curation. + phase_context: Physical phase or environment associated with values. + method_summary: Concise computational or experimental method. + placeholder_value: Declared scalar placeholder, if one exists. + extraction_source: Record of the upstream extraction source. + aliases: Accepted alternative dataset identifiers. + references: Bibliographic and source records. + notes: Additional immutable metadata notes. + storage: Read-only packaged-storage description, or `None` for a custom + in-memory set. + coverage: Element-coverage summary, when available. """ ref: DatasetRef @@ -101,7 +160,19 @@ class DatasetInfo: @dataclass(frozen=True, slots=True) class ElementScalarSet: - """Element-indexed scalar dataset stored densely by atomic number.""" + """Immutable element-indexed scalar dataset stored by atomic number. + + Attributes: + ref: Dataset identity. + info: Curated metadata, including the scientific units. + values_by_z: Dense immutable tuple indexed by atomic number. Index zero + is unused and missing elements contain `None`. + + Notes: + Scalar values have the units recorded by `info.units`. Policies and + transfers do not perform unit conversion, so custom sources combined in + one policy must use compatible units. + """ ref: DatasetRef info: DatasetInfo @@ -124,7 +195,41 @@ def from_mapping( notes: Iterable[str] = (), placeholder_value: float | None = None, ) -> "ElementScalarSet": - """Build a custom element-domain dataset from a symbol-keyed mapping.""" + """Build a custom element-domain dataset from a symbol-keyed mapping. + + Args: + ref: Stable identity for the custom dataset. + values: Element symbols mapped to finite scalar values or `None`. + Symbols are canonicalized, and D/T map to H. + name: Human-readable dataset name. + units: Scientific units for every non-missing value, or `None`. + description: Optional scientific description. + usage_role: Operational role. Defaults to ``"user"``. + semantic_class: Scientific classification. Defaults to ``"user"``. + origin_class: Origin classification. Defaults to ``"user"``. + phase_context: Optional physical phase or environment. + references: Bibliographic records to preserve with the set. + notes: Additional metadata notes. + placeholder_value: Optional finite scalar used as a placeholder. + + Returns: + A frozen [ElementScalarSet][atomref.registry.ElementScalarSet] with + coverage metadata computed for the packaged periodic table. + + Raises: + DatasetError: If an element key is invalid, two keys normalize to + the same element, or a value is not finite. + + Examples: + >>> custom = ElementScalarSet.from_mapping( + ... ref=DatasetRef("covalent_radius", "my_set"), + ... values={"C": 0.76, "O": 0.66}, + ... name="My radii", + ... units="angstrom", + ... ) + >>> custom.get("O") + 0.66 + """ n_z = max(e.z for e in iter_elements()) values_by_z: list[float | None] = [None] * (n_z + 1) @@ -197,7 +302,15 @@ def from_mapping( return cls(ref=ref, info=info, values_by_z=tuple(values_by_z)) def get(self, symbol: str | None) -> float | None: - """Return the scalar value for ``symbol`` or ``None`` if absent.""" + """Return one element's scalar value. + + Args: + symbol: Symbol-like element token, or `None`. D/T map to H. + + Returns: + The stored scalar in `info.units`, or `None` for an invalid or + uncovered element. + """ sym = _normalize_element_domain_symbol(symbol) elem = get_element(sym) @@ -208,7 +321,16 @@ def get(self, symbol: str | None) -> float | None: @dataclass(frozen=True, slots=True) class ElementRadialSet: - """Element-indexed radial profiles sampled on one shared grid.""" + """Immutable element-indexed radial profiles sampled on one shared grid. + + Attributes: + ref: Dataset identity. + info: Curated metadata and storage description. + radii: Shared immutable radial grid in the storage-declared coordinate + unit. + profiles_by_z: Dense immutable tuple of profiles indexed by atomic + number. Index zero is unused and missing profiles contain `None`. + """ ref: DatasetRef info: DatasetInfo @@ -216,7 +338,16 @@ class ElementRadialSet: profiles_by_z: tuple[tuple[float, ...] | None, ...] def get(self, element: str | int | None) -> tuple[float, ...] | None: - """Return the immutable profile for an element symbol or atomic number.""" + """Return the immutable sampled profile for one element. + + Args: + element: Symbol-like token, integer atomic number, or `None`. D/T + map to H; booleans are rejected despite being integer subclasses. + + Returns: + The stored profile in the density units described by `info`, or + `None` for an invalid or uncovered element. + """ if isinstance(element, int) and not isinstance(element, bool): z = element @@ -234,7 +365,10 @@ def get(self, element: str | int | None) -> tuple[float, ...] | None: BuiltinSet = ElementScalarSet | ElementRadialSet +"""Union of packaged scalar and radial dataset payloads.""" + ScalarDatasetLike = DatasetRef | ElementScalarSet +"""Typing alias for packaged references and custom scalar datasets.""" _DASH_TRANSLATION = str.maketrans( @@ -276,7 +410,7 @@ def _freeze_json_like(value: object) -> object: Registry metadata is cached globally. Returning raw dicts or lists from that cache would let callers mutate shared package state through the metadata - objects returned by :func:`get_dataset_info`. + objects returned by [get_dataset_info][atomref.registry.get_dataset_info]. """ if isinstance(value, dict): @@ -288,7 +422,9 @@ def _freeze_json_like(value: object) -> object: def _coerce_finite_float(value: object, *, what: str) -> float: - """Return ``value`` as a finite float or raise :class:`DatasetError`.""" + """Return `value` as a finite float or raise + [DatasetError][atomref.errors.DatasetError]. + """ try: out = float(value) @@ -327,13 +463,35 @@ def _datasets_for_quantity(quantity: QuantityId) -> Mapping[str, object]: def list_quantities() -> tuple[str, ...]: - """List packaged quantity identifiers in registry order.""" + """List packaged quantity identifiers in registry order. + + Returns: + Canonical quantity identifiers in their curated registry order. + + Raises: + DatasetError: If the packaged registry is unavailable or malformed. + """ return tuple(_get_quantities_mapping().keys()) def get_quantity_info(quantity: QuantityId) -> QuantityInfo: - """Return quantity-level metadata for a packaged quantity.""" + """Return quantity-level metadata for a packaged quantity. + + Args: + quantity: Canonical registry quantity identifier. + + Returns: + Immutable [QuantityInfo][atomref.registry.QuantityInfo] for the requested + quantity. + + Raises: + DatasetError: If the quantity is unknown or its metadata is malformed. + + Examples: + >>> get_quantity_info("covalent_radius").units + 'angstrom' + """ raw = _get_quantities_mapping().get(quantity) if not isinstance(raw, dict): @@ -389,8 +547,17 @@ def list_dataset_ids( ) -> tuple[str, ...]: """List packaged dataset identifiers for a quantity. - When ``usage_role`` is provided, only datasets with a matching normalized - role such as ``"target"`` or ``"support"`` are returned. + Args: + quantity: Canonical registry quantity identifier. + usage_role: Optional case-insensitive role filter, such as ``"target"`` + or ``"support"``. `None` includes every role. + + Returns: + Canonical dataset identifiers in curated registry order. + + Raises: + DatasetError: If the quantity is unknown or registry metadata is + malformed. """ dataset_ids = tuple(_datasets_for_quantity(quantity).keys()) @@ -410,7 +577,21 @@ def list_dataset_ids( def list_dataset_infos( quantity: QuantityId, *, usage_role: str | None = None ) -> tuple[DatasetInfo, ...]: - """Return packaged dataset metadata objects for a quantity.""" + """Return packaged dataset metadata objects for a quantity. + + Args: + quantity: Canonical registry quantity identifier. + usage_role: Optional case-insensitive role filter. `None` includes every + role. + + Returns: + Immutable [DatasetInfo][atomref.registry.DatasetInfo] objects in curated + registry order. + + Raises: + DatasetError: If the quantity is unknown or registry metadata is + malformed. + """ return tuple( get_dataset_info(DatasetRef(quantity, set_id)) @@ -419,7 +600,7 @@ def list_dataset_infos( def _coerce_reference(obj: object) -> Reference: - """Coerce a raw registry reference entry into :class:`Reference`.""" + """Coerce a raw registry entry into [Reference][atomref.registry.Reference].""" if not isinstance(obj, dict): raise DatasetError("invalid reference entry in registry.json") @@ -438,7 +619,7 @@ def _coerce_reference(obj: object) -> Reference: def _coerce_coverage(obj: object) -> CoverageInfo | None: - """Coerce raw coverage metadata into :class:`CoverageInfo`.""" + """Coerce raw metadata into [CoverageInfo][atomref.registry.CoverageInfo].""" if not isinstance(obj, dict): return None @@ -457,7 +638,26 @@ def _coerce_coverage(obj: object) -> CoverageInfo | None: def get_dataset_info(ref: DatasetRef) -> DatasetInfo: - """Return curated metadata for a packaged dataset reference.""" + """Return curated metadata for a packaged dataset reference. + + Args: + ref: Quantity and dataset identifier. Dataset aliases are accepted with + Unicode-dash, case, and surrounding-whitespace normalization. + + Returns: + Immutable metadata whose `ref` contains the canonical dataset ID. + + Raises: + DatasetError: If the quantity or dataset is unknown, or registry + metadata is malformed. + + Examples: + >>> info = get_dataset_info( + ... DatasetRef("covalent_radius", "cordero2008") + ... ) + >>> info.units + 'angstrom' + """ actual_set_id = _resolve_set_id(ref.quantity, ref.set_id) actual_ref = DatasetRef(quantity=ref.quantity, set_id=actual_set_id) @@ -824,7 +1024,31 @@ def _load_builtin_set(ref: DatasetRef) -> BuiltinSet: def get_builtin_set(ref: DatasetRef) -> BuiltinSet: - """Load a scalar or radial packaged dataset through the shared registry.""" + """Load a scalar or radial packaged dataset through the shared registry. + + Args: + ref: Quantity and packaged dataset identifier or alias. + + Returns: + A cached immutable [ElementScalarSet][atomref.registry.ElementScalarSet] + or [ElementRadialSet][atomref.registry.ElementRadialSet], chosen from the + dataset's declared storage kind. + + Raises: + DatasetError: If the reference is unknown, storage metadata is invalid, + or the packaged payload fails validation. + + Examples: + >>> loaded = get_builtin_set( + ... DatasetRef("covalent_radius", "cordero2008") + ... ) + >>> isinstance(loaded, ElementScalarSet) + True + + Notes: + Scalar policies narrow this union internally. Radial profiles never + participate in substitution or linear-transfer policy behavior. + """ canonical_ref = get_dataset_info(ref).ref return _load_builtin_set(canonical_ref) @@ -833,7 +1057,7 @@ def get_builtin_set(ref: DatasetRef) -> BuiltinSet: def resolve_scalar_dataset_like( dataset: ScalarDatasetLike | ElementRadialSet, ) -> ElementScalarSet: - """Resolve a packaged reference or custom set, requiring scalar payload.""" + """Resolve an internal scalar source and reject radial payloads.""" if isinstance(dataset, ElementScalarSet): return dataset diff --git a/src/atomref/transfer.py b/src/atomref/transfer.py index 62b5724..e5e6c9b 100644 --- a/src/atomref/transfer.py +++ b/src/atomref/transfer.py @@ -19,7 +19,7 @@ "transfer_linear", "fallback", ] -"""Source labels that may be admitted into nested linear-transfer workflows.""" +"""Source labels admitted into nested linear-transfer workflows.""" _ALLOWED_TRANSFER_VALUE_SOURCES = frozenset( { @@ -45,10 +45,20 @@ @runtime_checkable class SupportsValuePolicy(Protocol): - """Protocol for wrapper objects that can expose a generic value policy.""" + """Protocol for wrappers that expose a generic scalar value policy. + + Notes: + [RadiiPolicy][atomref.RadiiPolicy] and [XHPolicy][atomref.XHPolicy] + implement this structural protocol. Custom wrappers need not inherit + from it; providing a compatible `as_value_policy()` method is enough. + """ def as_value_policy(self) -> "ValuePolicy[str]": - """Return the generic element-domain value policy.""" + """Return the generic element-domain value policy. + + Returns: + A [ValuePolicy][atomref.ValuePolicy] over canonical element symbols. + """ @dataclass(frozen=True, slots=True) @@ -56,8 +66,17 @@ class LinearFit: """Summary statistics for a fitted linear transfer model. Parameters are stored in a compact, serializable form so they can be - attached to :class:`atomref.policy.LookupResult` objects and reused in + attached to [LookupResult][atomref.LookupResult] objects and reused in reporting code. + + Attributes: + coefficients: Fitted slopes, one per predictor. The current runtime + uses exactly one predictor. Units are target units divided by the + corresponding predictor units. + intercept: Fitted intercept in target-dataset units. + n_points: Number of overlapping element values used in the fit. + r2: Dimensionless coefficient of determination. + rmse: Root-mean-square residual in target-dataset units. """ coefficients: tuple[float, ...] @@ -72,6 +91,21 @@ class SubstitutionTransfer: """Use another dataset or policy directly when the base dataset is missing. The selected value is copied from the source rather than inferred. + + Attributes: + source: Packaged scalar reference, custom + [ElementScalarSet][atomref.registry.ElementScalarSet], generic + [ValuePolicy][atomref.ValuePolicy], or compatible wrapper policy. + + Examples: + >>> from atomref import DatasetRef, SubstitutionTransfer + >>> transfer = SubstitutionTransfer( + ... source=DatasetRef("covalent_radius", "csd_legacy_cov") + ... ) + + Notes: + Source and target values must use compatible units. The policy engine + does not perform dimensional conversion. """ source: ScalarDatasetLike | SupportsValuePolicy | ValuePolicy[str] @@ -85,15 +119,37 @@ class LinearTransfer: for forward compatibility, but the runtime intentionally accepts exactly one predictor source. - For nested policy predictors, two safeguards apply: - - - ``fit_sources`` / ``fit_max_depth`` control which predictor values may be - used when fitting the linear model itself; - - ``prediction_sources`` / ``prediction_max_depth`` control which nested - predictor values may be used for the final requested element. - - The defaults are intentionally conservative for fitting and permissive only - enough to allow one additional completion step at prediction time. + Attributes: + predictors: Predictor sources. The tuple must be nonempty, and the + current resolver supports exactly one predictor at evaluation time. + min_points: Minimum overlapping fit values. Must be at least 2 and + defaults to 2. + exclude_placeholders: Whether declared placeholder values are excluded + from fitting. Defaults to `True`. + fit_sources: Nested predictor result sources admitted to fitting. + Defaults to direct ``"base"`` and ``"override"`` values. + prediction_sources: Nested result sources admitted when predicting the + requested element. Defaults to base, override, substitution, and + linear-transfer values. + fit_max_depth: Maximum nested transfer depth admitted to fitting. + Defaults to 0 and must be nonnegative. + prediction_max_depth: Maximum nested transfer depth admitted for the + requested prediction. Defaults to 1 and must be nonnegative. + + Raises: + PolicyError: If predictors are empty, `min_points` is below 2, a source + control is empty or unknown, or either depth limit is negative. + + Examples: + >>> from atomref import DatasetRef, LinearTransfer + >>> transfer = LinearTransfer( + ... predictors=(DatasetRef("atomic_radius", "rahm2016"),) + ... ) + + Notes: + Fit controls and prediction controls are independent. Predictor and + target units must be internally consistent; no unit conversion is + performed. """ predictors: tuple[ diff --git a/src/atomref/xh.py b/src/atomref/xh.py index 29abf2c..9408265 100644 --- a/src/atomref/xh.py +++ b/src/atomref/xh.py @@ -26,6 +26,7 @@ from .transfer import LinearTransfer, TransferModel XHSet = ElementScalarSet +"""Typing alias for an immutable parent-element X-H bond-length dataset.""" _QUANTITY = "xh_bond_length" @@ -34,9 +35,25 @@ class XHPolicy: """Policy wrapper specialized for parent-element X-H bond lengths. - The quantity key is fixed to ``"xh_bond_length"`` and uses the parent - element ``X`` as the lookup key. ``H`` itself is not considered a valid - parent element for this quantity. + Attributes: + base_set: Packaged X-H set ID or custom + [ElementScalarSet][atomref.registry.ElementScalarSet]. + transfers: Ordered substitution or linear-transfer rules. Defaults to + no transfers. + overrides: Explicit finite, nonnegative parent-element values checked + before the base set. Defaults to an empty mapping. + fallback: Final finite, nonnegative value, or `None`. Defaults to `None`. + + Examples: + >>> policy = XHPolicy(base_set="csd_legacy_xh_cno") + >>> get_xh_bond_length("C", policy=policy) + 1.089 + + Notes: + The quantity key is fixed to ``"xh_bond_length"`` and uses parent + element X as its lookup key. H, D, and T are not valid parent elements. + Packaged values are in angstrom. Custom sources and policy values must + use compatible units because policies perform no unit conversion. """ base_set: str | XHSet @@ -45,7 +62,17 @@ class XHPolicy: fallback: float | None = None def as_value_policy(self) -> ValuePolicy[str]: - """Convert the X-H policy into the generic scalar-value policy.""" + """Convert this wrapper into the generic scalar policy. + + Returns: + An element-domain [ValuePolicy][atomref.policy.ValuePolicy] with + hydrogen blocked as a parent. + + Raises: + DatasetError: If a packaged set is unknown or non-scalar. + PolicyError: If the base quantity, override, or fallback is invalid, + or if H/D/T is used as an override parent. + """ if isinstance(self.base_set, ElementScalarSet): if self.base_set.ref.quantity != _QUANTITY: @@ -109,25 +136,67 @@ def _normalize_xh_symbol(symbol: str | None) -> str | None: def list_xh_sets(*, usage_role: str | None = None) -> tuple[str, ...]: - """List packaged X-H set ids.""" + """List packaged X-H set IDs. + + Args: + usage_role: Optional case-insensitive metadata-role filter. + + Returns: + Canonical set IDs in curated registry order. + + Raises: + DatasetError: If registry metadata is malformed. + """ return list_dataset_ids(_QUANTITY, usage_role=usage_role) def list_xh_set_infos(*, usage_role: str | None = None) -> tuple[DatasetInfo, ...]: - """Return packaged metadata objects for X-H sets.""" + """Return packaged metadata objects for X-H sets. + + Args: + usage_role: Optional case-insensitive metadata-role filter. + + Returns: + Immutable [DatasetInfo][atomref.registry.DatasetInfo] objects in curated + registry order. + + Raises: + DatasetError: If registry metadata is malformed. + """ return list_dataset_infos(_QUANTITY, usage_role=usage_role) def get_xh_set_info(set_id: str) -> DatasetInfo: - """Return metadata for one packaged X-H set.""" + """Return metadata for one packaged X-H set. + + Args: + set_id: Canonical packaged set ID or accepted alias. + + Returns: + Curated metadata, including angstrom units and provenance. + + Raises: + DatasetError: If the set is unknown or metadata is malformed. + """ return get_dataset_info(DatasetRef(_QUANTITY, set_id)) def get_xh_set(set_id: str) -> XHSet: - """Load one packaged X-H set as an :class:`ElementScalarSet`.""" + """Load one packaged X-H set as an + [ElementScalarSet][atomref.registry.ElementScalarSet]. + + Args: + set_id: Canonical packaged set ID or accepted alias. + + Returns: + A cached immutable parent-element set in angstrom. + + Raises: + DatasetError: If the set is unknown, malformed, or non-scalar. + """ return resolve_scalar_dataset_like(DatasetRef(_QUANTITY, set_id)) @@ -137,7 +206,26 @@ def lookup_xh_bond_length( *, policy: XHPolicy | None = None, ) -> LookupResult: - """Resolve a parent-element X-H bond length with provenance.""" + """Resolve a parent-element X-H bond length with provenance. + + Args: + symbol: Parent-element token, or `None`. H/D/T are explicitly blocked. + policy: X-H policy; `None` selects + [DEFAULT_XH_POLICY][atomref.DEFAULT_XH_POLICY]. + + Returns: + Lookup result whose value is in angstrom, or an explicit missing result. + A blocked hydrogen parent includes an explanatory note. + + Raises: + DatasetError: If a referenced dataset is unknown or non-scalar. + PolicyError: If policy or transfer configuration is invalid. + + Examples: + >>> result = lookup_xh_bond_length("C") + >>> result.value, result.source + (1.089, 'base') + """ active = DEFAULT_XH_POLICY if policy is None else policy lookup = _lookup_value_from_policy_source(symbol, source=active) @@ -156,7 +244,20 @@ def get_xh_bond_length( *, policy: XHPolicy | None = None, ) -> float | None: - """Return only the selected X-H bond-length value, without provenance.""" + """Return only the selected parent-element X-H bond length. + + Args: + symbol: Parent-element token, or `None`. H/D/T are explicitly blocked. + policy: X-H policy; `None` selects + [DEFAULT_XH_POLICY][atomref.DEFAULT_XH_POLICY]. + + Returns: + Selected bond length in angstrom, or `None` when resolution is missing. + + Raises: + DatasetError: If a referenced dataset is unknown or non-scalar. + PolicyError: If policy or transfer configuration is invalid. + """ active = DEFAULT_XH_POLICY if policy is None else policy return _get_value_from_policy_source(symbol, source=active) diff --git a/tests/meta/test_public_api.py b/tests/meta/test_public_api.py index 73a8f34..97138de 100644 --- a/tests/meta/test_public_api.py +++ b/tests/meta/test_public_api.py @@ -1,18 +1,30 @@ from __future__ import annotations +import inspect +from pathlib import Path + import atomref as ar -REQUIRED_PUBLIC_NAMES = { +REPO_ROOT = Path(__file__).resolve().parents[2] + + +EXPECTED_PUBLIC_NAMES = { + '__version__', 'BuiltinSet', 'Element', 'DatasetRef', 'DatasetInfo', + 'CoverageInfo', 'ElementRadialSet', 'ElementScalarSet', 'QuantityInfo', + 'Reference', 'LookupResult', + 'ValuePolicy', 'RadiiPolicy', + 'RadiiElementAssessment', + 'RadiiPolicyAssessment', 'DEFAULT_COVALENT_POLICY', 'DEFAULT_VDW_POLICY', 'BOHR_TO_ANGSTROM', @@ -22,10 +34,18 @@ 'IASPositionResult', 'ProatomicDensityProfile', 'ProatomicDensitySet', + 'LinearFit', 'LinearTransfer', 'SubstitutionTransfer', + 'canonicalize_element_symbol', + 'get_element', + 'iter_elements', + 'is_valid_element_symbol', 'get_builtin_set', + 'get_dataset_info', + 'get_quantity_info', 'get_radii_set', + 'get_radii_set_info', 'get_covalent_radius', 'lookup_covalent_radius', 'get_vdw_radius', @@ -33,6 +53,7 @@ 'XHPolicy', 'DEFAULT_XH_POLICY', 'get_xh_set', + 'get_xh_set_info', 'get_xh_bond_length', 'lookup_xh_bond_length', 'list_xh_sets', @@ -51,6 +72,9 @@ 'estimate_ias_position', 'list_proatomic_density_sets', 'list_proatomic_density_set_infos', + 'lookup_value', + 'get_value', + 'assess_radii_policy', } @@ -59,5 +83,23 @@ def test___all___exports_existing_objects() -> None: assert hasattr(ar, name), name -def test_core_public_api_names_are_exported() -> None: - assert REQUIRED_PUBLIC_NAMES.issubset(set(ar.__all__)) +def test_public_api_is_exact() -> None: + assert len(ar.__all__) == len(set(ar.__all__)) + assert set(ar.__all__) == EXPECTED_PUBLIC_NAMES + + +def test_docs_merge_init_signatures_into_public_classes() -> None: + config = (REPO_ROOT / "mkdocs.yml").read_text(encoding="utf-8") + assert "merge_init_into_class: true" in config + + +def test_lookup_value_examples_are_a_top_level_docstring_section() -> None: + docstring = inspect.getdoc(ar.lookup_value) + assert docstring is not None + lines = docstring.splitlines() + + raises_index = lines.index("Raises:") + examples_index = lines.index("Examples:") + notes_index = lines.index("Notes:") + + assert raises_index < examples_index < notes_index From 18bad5471fbe230c4ed6f9917d2b01156059cb67 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 00:24:06 +0300 Subject: [PATCH 12/23] docs: reposition atomref documentation --- README.md | 313 +++++++++++++++++--------------- docs/datasets/index.md | 2 +- docs/datasets/xh_bond_length.md | 7 +- docs/dev/architecture.md | 14 ++ docs/dev/data_curation.md | 68 +++++-- docs/dev/dev_plan.md | 21 --- docs/guide/non_goals.md | 11 +- docs/guide/quickstart.md | 107 ++++++++--- docs/index.md | 313 +++++++++++++++++--------------- mkdocs.yml | 1 - 10 files changed, 483 insertions(+), 374 deletions(-) delete mode 100644 docs/dev/dev_plan.md diff --git a/README.md b/README.md index 3ba67d8..2d80602 100644 --- a/README.md +++ b/README.md @@ -8,186 +8,199 @@ Documentation: https://delonecommons.github.io/atomref -`atomref` is a small pure-Python package for **curated atomic reference data**, -**provenance-aware lookup policies**, and neutral-proatom reference workflows -used by geometry and structure-analysis algorithms. - -It is not meant to be yet another periodic-table encyclopedia. The package is -for code that needs stable atomic reference values with explicit provenance, -clear fallback behavior, and honest handling of incomplete preferred datasets. - -What you get in the current release line: - -- stable element metadata, -- curated named radii sets, -- provisional X–H bond-length support for hydrogen-normalisation workflows, -- dataset provenance and coverage metadata, -- deterministic lookup policies, -- substitution and linear transfer from support datasets or policies into target datasets, -- guarded nested policy-backed transfers with explicit transfer depth, - conservative fit/prediction controls, and cycle detection, -- user-defined custom element-indexed scalar sets, -- frozen neutral H–Lr spherical proatomic-density profiles with scalar, - unit-aware evaluation, -- a stable pairwise neutral-proatom boundary and an optional cutoff-bounded, - resolution-limited promolecular-minimum proxy. - -## Core terms - -`atomref` uses a small vocabulary on purpose. - -- **quantity** — the operational property family being requested, such as - `covalent_radius`, `van_der_waals_radius`, `atomic_radius`, or - `xh_bond_length`, or `proatomic_density`. -- **domain** — the key space used to index that quantity. In the current - runtime, the supported domain is `element`, meaning lookups are keyed by an - element symbol. -- **dataset** — one curated named scalar table or radial-profile payload inside - a quantity, such as `cordero2008`, `alvarez2013`, or the neutral H–Lr - proatomic set. -- **policy** — the ordered rule set that decides what scalar value to return - when the preferred dataset is incomplete. - -The metadata layer records `domain` and storage kind explicitly. The current -runtime supports the `element` domain with packaged scalar tables and one -shared-grid radial-profile family. Lookup policies, substitution, and linear -transfer intentionally remain scalar-only; radial profiles are never completed -by correlation or neighboring-element substitution. - -## Why this exists - -Scientific software often wants a complete lookup table, but the best dataset -for the job is rarely complete. `atomref` makes that situation explicit. -Instead of hiding ad hoc defaults inside algorithm code, you choose a target -set, describe how missing values may be restored, and keep provenance on what -was actually returned. - -The built-in default behavior is intentionally simple and practical: - -- **Cordero covalent radii** (`cordero2008`) are the preferred covalent target - set, with missing values substituted from the **legacy CSD covalent radii** - (`csd_legacy_cov`). -- **Alvarez van der Waals radii** (`alvarez2013`) are the preferred vdW target - set, with missing values restored from the **Rahm isodensity atomic radii** - (`rahm2016`) through a fitted linear transfer. -- **CSD/ConQuest hydrogen-normalisation defaults** (`csd_legacy_xh_cno`) are a - provisional sparse X–H target set for `C`, `N`, and `O`, with other parent - elements inferred from **Cordero covalent radii** through a fitted linear - transfer. - -Nested policy predictors are supported too. `LinearTransfer` separates -**fit-time** use of nested predictor values from **prediction-time** use. By default, the fit may use only direct nested -values, while the final requested element may still use one additional -nested completion step. That is a useful compromise for workflows such as -provisional X–H inference from a chosen covalent-radii policy. - -## Quick example +`atomref` provides cited atomic values and frozen spherical free-atom electron +densities through a small Python API for crystallographic, quantum-chemical, +and molecular-structure algorithms. + +Use it when your software needs dependable atomic radii, X–H reference lengths, +neutral proatomic densities, or pairwise reference-atom boundaries without +embedding another project-local table and its fallback rules. The core runtime +is pure Python and has **no required third-party dependencies**. + +## Install and get a useful result + +```bash +pip install atomref +``` + +```python +import atomref as ar + +covalent_c = ar.get_covalent_radius("C") +xh_n = ar.get_xh_bond_length("N") +rho_o = ar.get_proatomic_density("O", 0.75) +boundary = ar.estimate_proatomic_boundary("C", "O", 1.43) +minimum = ar.estimate_promolecular_density_minimum("C", "O", 1.43) +``` + +The scalar results above are in documented units: radii and X–H lengths use +angstrom, while the density call returns electron/bohr³ by default. Pairwise +coordinates are measured from the first atom toward the second. For this C–O +example, `boundary` is the stable equal-proatom divider and `minimum` is the +optional, resolution-limited minimum of the summed promolecular line density. + +[Start with the quickstart](https://delonecommons.github.io/atomref/guide/quickstart/) +or open the [complete API reference](https://delonecommons.github.io/atomref/api/). + +## What it solves + +- **Bond and contact geometry:** select named covalent and van der Waals radii + instead of scattering constants through structure code. +- **Hydrogen normalization:** obtain provisional, provenance-aware X–H target + lengths keyed by the parent element. +- **Incomplete reference tables:** use explicit substitution or fitted transfer + policies and inspect how a value was obtained. +- **Free-atom density sampling:** evaluate immutable neutral H–Lr spherical + profiles with explicit coordinate and density units over a strict 0–20 bohr + domain. +- **Pairwise reference-atom models:** choose a stable proatomic boundary or an + explicitly cutoff-bounded promolecular-minimum proxy without presenting + either as an exact molecular QTAIM surface. + +Most convenience functions come in two forms: ```pycon >>> import atomref as ar ->>> ar.get_covalent_radius("C") -0.76 ->>> ar.get_vdw_radius("O") -1.5 ->>> ar.get_xh_bond_length("N") -1.015 ->>> lookup = ar.lookup_vdw_radius("Pm") ->>> lookup.value +>>> ar.get_vdw_radius("Pm") 2.8972265395148358 ->>> lookup.source -'transfer_linear' ->>> lookup.transfer_depth -1 ->>> lookup.resolved_from +>>> result = ar.lookup_vdw_radius("Pm") +>>> result.source, result.transfer_depth +('transfer_linear', 1) +>>> result.resolved_from (DatasetRef(quantity='atomic_radius', set_id='rahm2016'),) ->>> ar.get_proatomic_density("O", 0.75) -0.1141799693379811 ->>> boundary = ar.estimate_proatomic_boundary("C", "O", 1.5, distance_unit="bohr") ->>> boundary.method, boundary.status -('equal_proatom_density', 'ok') ``` -`get_*` returns only the number. `lookup_*` returns a `LookupResult` that also -records where the value came from, whether a transfer model or policy source was -involved, and how many transfer steps were needed (`transfer_depth`). +`get_*` returns the selected number. `lookup_*` returns a typed `LookupResult` +with the source, supporting datasets, placeholder state, fit information, and +transfer depth. -You can inspect the packaged quantity and dataset catalog directly: +## Why adopt `atomref`? -```pycon ->>> import atomref as ar ->>> ar.list_quantities() -('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') ->>> ar.get_quantity_info("xh_bond_length") -QuantityInfo(quantity='xh_bond_length', domain='element', units='angstrom', description='Element-indexed reference X-H bond lengths keyed by parent element X and intended for hydrogen-position normalisation or related geometry workflows.') ->>> [info.ref.set_id for info in ar.list_dataset_infos("van_der_waals_radius", usage_role="target")] -['bondi1964', 'rowland_taylor1996', 'alvarez2013', 'chernyshov2020'] +A local constants file is easy to start and difficult to maintain. It usually +accumulates uncited values, silent replacements for missing elements, ambiguous +units, and behavior that cannot be reviewed independently of the consuming +algorithm. `atomref` keeps those concerns in one versioned layer: + +- every packaged dataset has a stable identifier, coverage metadata, and + bibliographic provenance; +- lookup rules are explicit and deterministic rather than hidden in callers; +- direct, substituted, fitted, fallback, placeholder, and missing results stay + distinguishable; +- public types, units, valid ranges, and failure behavior are documented; +- packaged data and attribution are checked in both wheel and source + distributions; +- the dependency-free runtime can be embedded in lightweight scientific tools. + +The package does not claim that one table or policy is universally correct. It +makes the selected reference and the assumptions around it visible. + +## Installation choices + +The base install is sufficient for every runtime API: + +```bash +pip install atomref ``` -You can also load a packaged set directly: +Install the notebook extra to run the shipped Jupyter examples and their plots: -```pycon ->>> import atomref as ar ->>> vdw = ar.get_radii_set("van_der_waals", "alvarez2013") ->>> vdw.get("O") -1.5 ->>> xh = ar.get_xh_set("csd_legacy_xh_cno") ->>> xh.get("C") -1.089 ->>> info = ar.get_proatomic_density_set_info() ->>> radial = ar.get_builtin_set(info.ref) ->>> type(radial).__name__ -'ElementRadialSet' +```bash +pip install "atomref[notebook]" +``` + +Install all current user-facing optional features with: + +```bash +pip install "atomref[all]" ``` -The proatomic profiles are isolated neutral spherical references, not molecular -electron densities. Pairwise `boundary` mode is the stable default. Optional -`minimum` mode searches only above the fixed `1e-4 electron/bohr^3` per-atom -tail cutoff and has a practical resolution of `0.01 bohr`; it may return an -explicit diagnostic status without a coordinate. See the -[proatomic-density guide](https://delonecommons.github.io/atomref/guide/proatomic_density/) -for the 20-bohr domain, units, provenance, mode selection, and physical -limitations. +Contributor test, lint, build, upload, and release tools are intentionally not +part of `all`. See the [installation guide](https://delonecommons.github.io/atomref/guide/install/) +for development setup. + +## Data, provenance, and scientific scope + +The scalar catalog includes named covalent, van der Waals, atomic-isodensity, +and provisional X–H datasets. Registry metadata separates the requested +quantity from scientific classification and from whether a dataset is a direct +target or transfer support. + +The neutral proatomic profiles are a deterministic consumer snapshot of +`atomref-proatoms` 2.0.0 dataset +`pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`. They cover H–Lr and record the PBE0, +self-consistent spherical fractional-occupation UKS, spin-free one-electron +X2C, and dyall-v4z definition, source hashes, CC BY 4.0 data license, and both +concept and version-specific DOIs. + +These profiles are isolated, neutral, spherical reference atoms—not molecular +electron densities. Density evaluation is scalar, and the public radius domain +is exactly 0–20 bohr. Pairwise `boundary` mode is the stable default. Optional +`minimum` mode searches only where both proatoms remain above +`1e-4 electron/bohr^3`, has a declared `0.01 bohr` resolution, and may return a +typed diagnostic without a coordinate. -## Notebook walkthroughs +- [Dataset catalog and provenance](https://delonecommons.github.io/atomref/datasets/) +- [Proatomic-density and pairwise scientific guide](https://delonecommons.github.io/atomref/guide/proatomic_density/) +- [Policy guide](https://delonecommons.github.io/atomref/guide/policies/) +- [Explicit non-goals](https://delonecommons.github.io/atomref/guide/non_goals/) -The repository ships example notebooks for the main workflows. The first three -are also available as rendered Markdown pages. The `0.2.0` proatomic notebooks -are saved, executed source artifacts with outputs and plots. +## Executable notebook documentation + +The documentation renders the actual committed `.ipynb` files directly, +including Markdown, code, mathematics, saved text, and saved PNG plots. Site +builds do not execute or rewrite them; a separate temporary Jupyter smoke check +verifies that their code still runs. - [Notebook overview](https://delonecommons.github.io/atomref/guide/notebooks/) - [Quickstart notebook](https://delonecommons.github.io/atomref/notebooks/01-quickstart/) - [Policies and assessment notebook](https://delonecommons.github.io/atomref/notebooks/02-policies-and-assessment/) - [Custom sets and discovery notebook](https://delonecommons.github.io/atomref/notebooks/03-custom-sets-and-discovery/) -- [IAS method-selection study](https://delonecommons.github.io/atomref/dev/ias_method_selection/) -- [Executed proatomic density and IAS feature notebook](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) +- [IAS method-selection study](https://delonecommons.github.io/atomref/notebooks/04-ias-method-selection-study/) +- [Proatomic density and IAS workflows](https://delonecommons.github.io/atomref/notebooks/05-proatomic-density-and-ias/) -## Relationship to Delone Commons +## Dataset and policy discovery -`atomref` is designed as a standalone package, but within Delone Commons it is -primarily intended to support chemistry-aware packages such as: +The lower-level registry is available when an application needs to choose or +report an exact source: -- `molcryst`, for covalent-bond detection, contact analysis, and hydrogen workflows, -- future `chemvoro`, for chemistry-aware contact and hydrogen workflows. +```pycon +>>> import atomref as ar +>>> ar.list_quantities() +('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') +>>> [info.ref.set_id for info in ar.list_dataset_infos( +... "van_der_waals_radius", usage_role="target" +... )] +['bondi1964', 'rowland_taylor1996', 'alvarez2013', 'chernyshov2020'] +>>> profile_info = ar.get_proatomic_density_set_info() +>>> profile_info.ref.set_id +'pbe0_sfx2c_dyallv4z_h-lr_neutral_v2' +``` + +Custom element-indexed scalar sets can participate in the same policy layer. +Radial profiles deliberately do not: no scalar `ValuePolicy`, neighboring- +element substitution, or fitted correlation is applied to density data. + +## Relationship to Delone Commons -By contrast, `pyvoro2` and `pbcgraph` are intentionally general mathematical -packages and are not direct consumers of `atomref`. +`atomref` is a standalone package. Within Delone Commons it is intended for +chemistry-aware consumers such as `molcryst` and future `chemvoro` workflows. +General mathematical packages such as `pyvoro2` and `pbcgraph` remain +independent of atomic-reference policy. -## Data curation and developer tools +## Maintainer checks -The repository also ships small maintenance tools. The most important ones are: +The repository keeps a small set of release tools: -- `python tools/check_registry.py` — validate curated registry metadata against - every packaged scalar and radial payload, -- `python tools/check_notebooks.py` — validate saved release-notebook state and - execute notebook code cells, -- `python tools/export_notebooks.py` — turn notebooks into Markdown pages for - the docs, -- `python tools/gen_readme.py` — regenerate `README.md` from this page, -- `python tools/release_check.py` — run the full release-preparation checklist, - including linting, tests, docs, builds, and artifact validation. +- `python tools/check_registry.py` validates registry metadata against every + packaged scalar and radial payload; +- `python tools/check_notebooks.py` smoke-executes temporary notebook copies + through a standard Jupyter kernel and discards the results; +- `python tools/gen_readme.py` regenerates this README from `docs/index.md`; +- `python tools/release_check.py` runs lint, tests, strict docs, distribution + checks, and clean artifact-installation checks. See the [tools README](https://github.com/DeloneCommons/atomref/blob/main/tools/README.md) -for a short description of each script. +for the maintainer-only data snapshot workflow and command details. --- diff --git a/docs/datasets/index.md b/docs/datasets/index.md index 739ee4d..5edeb26 100644 --- a/docs/datasets/index.md +++ b/docs/datasets/index.md @@ -56,4 +56,4 @@ separately from the `atomref` code license. Cite both the - [van der Waals radius](van_der_waals_radius.md) - [Atomic radius](atomic_radius.md) - [X–H bond length](xh_bond_length.md) -- Proatomic density +- [Proatomic density](../guide/proatomic_density.md) diff --git a/docs/datasets/xh_bond_length.md b/docs/datasets/xh_bond_length.md index 28364c5..5f8b8bb 100644 --- a/docs/datasets/xh_bond_length.md +++ b/docs/datasets/xh_bond_length.md @@ -34,6 +34,7 @@ That means the package draws a sharp line between: ## Scope note -This is intentionally a small addendum rather than full X–H support. -Broader X–H datasets, richer policies, and more complete literature treatment -are planned for `0.2.x`. +This is intentionally a small feature rather than a complete literature model +of X–H bonding. `0.2.1` retains the accepted sparse target and explicit fitted +policy; it does not add broader X–H datasets or claim environment-specific bond +lengths. diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index cee495a..1441715 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -119,3 +119,17 @@ The package deliberately avoids a large object graph or a chemistry-specific DSL A quantity wrapper is usually only a thin adapter over the generic policy core. That keeps the internals easy to test and lets other scientific packages reuse `atomref` without bringing in the rest of the Delone Commons stack. + +## Documentation and distribution boundary + +The five files under `docs/notebooks/` are both the maintained Jupyter sources +and their documentation pages. `mkdocs-jupyter` renders their committed state +with execution disabled; `tools/check_notebooks.py` exercises temporary copies +through `nbclient` and discards the results. There is no exporter, generated +notebook Markdown, source-copy step, or output-freshness contract. + +The wheel remains a focused runtime artifact containing package code, typing +metadata, legal notices, and curated data. The source distribution additionally +contains tests, tools, durable documentation, and the single notebook sources. +Base, `notebook`, and `all` installations are validated from the built wheel in +separate temporary environments during release preparation. diff --git a/docs/dev/data_curation.md b/docs/dev/data_curation.md index 37ae4b1..cf1e965 100644 --- a/docs/dev/data_curation.md +++ b/docs/dev/data_curation.md @@ -1,29 +1,63 @@ # Data curation -Packaged scalar tables are stored as CSV files indexed by atomic number. The -radial dataset is a shared-grid CSV payload inside a deterministic ZIP. Dataset -metadata, storage declarations, and provenance live in -`src/atomref/data/registry.json`. +Packaged scalar tables are CSV files indexed by atomic number. The neutral +radial dataset is a shared-grid CSV payload inside a deterministic single-file +ZIP. Dataset metadata, storage declarations, provenance, aliases, coverage, and +attribution live in `src/atomref/data/registry.json`. -Placeholder values are modeled as dataset metadata, not as hard-coded Python -constants. +Generated scientific data must not be hand-edited. -The registry distinguishes several orthogonal concerns: +## Classification + +The registry keeps several concerns separate: - `quantity` — the operational lookup target, such as `covalent_radius` or - `van_der_waals_radius` -- `semantic_class` — what the dataset scientifically represents -- `usage_role` — whether the dataset is intended as a direct target set or as - support data for transfer -- `phase_context` — the physical context of the underlying values + `proatomic_density`; +- `semantic_class` — what the dataset scientifically represents; +- `origin_class` — how its values were obtained; +- `usage_role` — whether it is a direct target or transfer support; +- `phase_context` — the physical context of the values; +- `storage.kind` — the validated packaged payload shape. + +This is why `atomic_radius:rahm2016` remains an isolated-atom isodensity dataset +even when a radii policy uses it as support for a fitted van der Waals value. + +## Neutral proatomic snapshot -This matters for support-only datasets such as `atomic_radius:rahm2016`, which -is packaged as atomic support data and then used by the default van der Waals -policy through linear transfer. +`proatomic_density:pbe0_sfx2c_dyallv4z_h-lr_neutral_v2` is a deterministic +consumer snapshot of `atomref-proatoms` 2.0.0 dataset +`pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`. It selects the neutral H–Lr profiles, +retains the source grid through the first point above 20 bohr so the public +endpoint remains bracketed, and does not interpolate or alter stored density +values during curation. -To check that metadata and every packaged scalar or radial payload stay -synchronized, run: +The registry records the upstream release and dataset identity, PBE0/spherical +fractional-occupation UKS/sf-X2C/dyall-v4z method summary, source and basis +hashes, public domain and interpolation contract, CC BY 4.0 data license, and +the concept and version-specific Zenodo DOIs. Package code remains under its +own LGPL license. + +The maintainer-only builder reads a separately obtained immutable upstream +source tree; `atomref-proatoms` is not a runtime or build dependency: + +```bash +python tools/build_proatomic_density_snapshot.py \ + --source-dir ../atomref-proatoms-reference-v2.0.0/upstream/data/profiles/pbe0_sfx2c_dyallv4z_h-lr_spherical_v2 \ + --check +``` + +Write mode is reserved for deliberate snapshot regeneration after a separately +reviewed data change. It is not part of ordinary documentation or release +builds. + +## Validation + +Run the registry validator after any metadata or payload change: ```bash python tools/check_registry.py ``` + +It cross-checks metadata against every scalar and radial payload. Distribution +validation independently checks required members, attribution markers, and the +exact outer ZIP and inner CSV fingerprints in both source and wheel artifacts. diff --git a/docs/dev/dev_plan.md b/docs/dev/dev_plan.md deleted file mode 100644 index a029466..0000000 --- a/docs/dev/dev_plan.md +++ /dev/null @@ -1,21 +0,0 @@ -# Development plan - -The authoritative implementation plan is the root-level -[`DEV_PLAN.md`](https://github.com/DeloneCommons/atomref/blob/main/DEV_PLAN.md). -This compact page exists only for the current documentation structure and will -be removed during the planned `0.2.1` documentation cleanup. - -## Current `0.2.0` direction - -The accepted density work provides neutral H–Lr spherical proatomic profiles -and scalar log–log evaluation. Stage 4 now defines two explicit pairwise modes: - -- a default stable proatomic boundary based on homonuclear symmetry, equal - proatomic contributions, and a fixed low-density contour fallback; -- an optional, cutoff-bounded and resolution-limited promolecular density - minimum for Bader-oriented comparison. - -The mode-selection evidence and limitations are summarized in -[Pairwise boundary and IAS-proxy method selection](ias_method_selection.md). - -Neither mode is an exact molecular-density QTAIM surface. diff --git a/docs/guide/non_goals.md b/docs/guide/non_goals.md index fe29101..4d46104 100644 --- a/docs/guide/non_goals.md +++ b/docs/guide/non_goals.md @@ -13,9 +13,14 @@ It is **not** trying to be: - an environment-specific chemistry model, - a machine-learning framework for extrapolating unseen chemistry. -The package is about **curated reference data and explicit lookup policies**. -That includes provenance, transfer from broader support datasets, and stable API -surfaces that higher-level scientific code can rely on. +The package is about **curated atomic references and explicit selection or +model semantics**. That includes provenance-aware scalar lookup, transfer from +broader support datasets, frozen neutral spherical proatomic profiles, and the +two documented pairwise reference-atom modes. + +The pairwise helpers are not molecular electron-density calculations or exact +QTAIM surfaces. Radial data are not completed through scalar policy, +correlation, or neighboring-element substitution. Future versions may widen the range of supported *reference-data families*, but the package should still remain a small reference-data layer rather than a full diff --git a/docs/guide/quickstart.md b/docs/guide/quickstart.md index 8db9586..83ea590 100644 --- a/docs/guide/quickstart.md +++ b/docs/guide/quickstart.md @@ -1,9 +1,12 @@ # Quickstart -The two most important user-facing ideas in `atomref` are: +Install the lightweight runtime: -- `get_*` returns only the selected number, -- `lookup_*` returns the number **and** provenance metadata. +```bash +pip install atomref +``` + +Then request the reference values needed by a structure workflow: ```pycon >>> import atomref as ar @@ -13,50 +16,98 @@ The two most important user-facing ideas in `atomref` are: 1.5 >>> ar.get_xh_bond_length("N") 1.015 +``` + +Packaged radii and X–H lengths are in angstrom. `get_*` is the concise path +when an algorithm only needs the selected number. + +## Keep provenance with the result + +Use the matching `lookup_*` helper when a result must record how it was chosen: + +```pycon >>> lookup = ar.lookup_vdw_radius("Pm") >>> lookup.value 2.8972265395148358 >>> lookup.source 'transfer_linear' +>>> lookup.transfer_depth +1 >>> lookup.resolved_from (DatasetRef(quantity='atomic_radius', set_id='rahm2016'),) ``` -Use `get_*` when you only need the value. Use `lookup_*` when you want to know -whether the result came from the preferred dataset, a support dataset, a policy -override, or a fallback. +A `LookupResult` distinguishes direct, overridden, substituted, fitted, +fallback, placeholder, and missing values. See the [policy guide](policies.md) +when you need to configure that resolution process. + +## Evaluate a neutral proatomic density -You can inspect the packaged quantity layer directly: +The density API accepts an element symbol or atomic number and evaluates one +scalar radius at a time: ```pycon ->>> import atomref as ar ->>> ar.list_quantities() -('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') ->>> ar.get_quantity_info("xh_bond_length") -QuantityInfo(quantity='xh_bond_length', domain='element', units='angstrom', description='Element-indexed reference X-H bond lengths keyed by parent element X and intended for hydrogen-position normalisation or related geometry workflows.') ->>> [info.ref.set_id for info in ar.list_radii_set_infos("van_der_waals", usage_role="target")] -['bondi1964', 'rowland_taylor1996', 'alvarez2013', 'chernyshov2020'] +>>> rho = ar.get_proatomic_density( +... "O", +... 0.75, +... radius_unit="angstrom", +... density_unit="electron/bohr^3", +... ) +>>> rho +0.1141799693379811 ``` -And you can load a packaged set object directly: +The packaged neutral H–Lr profiles have a strict 0–20 bohr public radius domain. +Radius and density units are selected independently. Invalid or out-of-range +radii raise `ValueError`; unsupported elements return `None`. + +## Choose a pairwise reference-atom mode + +For a C–O pair at 1.43 Å: ```pycon ->>> import atomref as ar +>>> boundary = ar.estimate_proatomic_boundary("C", "O", 1.43) +>>> boundary.method, boundary.status +('equal_proatom_density', 'ok') +>>> minimum = ar.estimate_promolecular_density_minimum("C", "O", 1.43) +>>> minimum.method, minimum.status +('promolecular_density_minimum', 'ok') +>>> ar.estimate_ias_position("C", "O", 1.43) == boundary +True +``` + +`boundary` is the stable default: it uses homonuclear symmetry, equal neutral- +proatom contributions in the meaningful-overlap region, and a fixed contour- +gap rule at long separation. `minimum` is an optional, cutoff-bounded, +`0.01 bohr`-resolution proxy for a minimum of the summed promolecular line +density. It may return an explicit non-result and never silently switches to +boundary mode. + +Neither result is an exact molecular-density QTAIM surface. Read the +[scientific guide](proatomic_density.md) before using pairwise coordinates in a +scientific interpretation. + +## Inspect or load an exact dataset + +The registry lets an application report and select exact sources: + +```pycon +>>> ar.list_quantities() +('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') +>>> [info.ref.set_id for info in ar.list_radii_set_infos( +... "van_der_waals", usage_role="target" +... )] +['bondi1964', 'rowland_taylor1996', 'alvarez2013', 'chernyshov2020'] >>> vdw = ar.get_radii_set("van_der_waals", "alvarez2013") >>> vdw.get("O") 1.5 ->>> raw = ar.get_builtin_set(ar.DatasetRef("atomic_radius", "rahm2016")) ->>> raw.get("Pm") -2.83 ->>> xh = ar.get_xh_set("csd_legacy_xh_cno") ->>> xh.get("C") -1.089 ``` -For longer, runnable examples see: +## Continue with executable examples -- the [notebook overview](notebooks.md), -- the [quickstart notebook page](../notebooks/01-quickstart.md), -- the [policies notebook page](../notebooks/02-policies-and-assessment.md), -- the [custom sets notebook page](../notebooks/03-custom-sets-and-discovery.md), -- the [executed proatomic density and IAS feature notebook](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb). +- [Notebook overview](notebooks.md) +- [Quickstart notebook](../notebooks/01-quickstart.ipynb) +- [Policies and assessment notebook](../notebooks/02-policies-and-assessment.ipynb) +- [Custom sets and discovery notebook](../notebooks/03-custom-sets-and-discovery.ipynb) +- [IAS method-selection study](../notebooks/04-ias-method-selection-study.ipynb) +- [Proatomic density and IAS workflows](../notebooks/05-proatomic-density-and-ias.ipynb) diff --git a/docs/index.md b/docs/index.md index 2a9bf7a..72b3bef 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,183 +6,196 @@ [![Python Versions](https://img.shields.io/pypi/pyversions/atomref.svg)](https://pypi.org/project/atomref/) [![License](https://img.shields.io/pypi/l/atomref.svg)](https://github.com/DeloneCommons/atomref/blob/main/LICENSE) -`atomref` is a small pure-Python package for **curated atomic reference data**, -**provenance-aware lookup policies**, and neutral-proatom reference workflows -used by geometry and structure-analysis algorithms. - -It is not meant to be yet another periodic-table encyclopedia. The package is -for code that needs stable atomic reference values with explicit provenance, -clear fallback behavior, and honest handling of incomplete preferred datasets. - -What you get in the current release line: - -- stable element metadata, -- curated named radii sets, -- provisional X–H bond-length support for hydrogen-normalisation workflows, -- dataset provenance and coverage metadata, -- deterministic lookup policies, -- substitution and linear transfer from support datasets or policies into target datasets, -- guarded nested policy-backed transfers with explicit transfer depth, - conservative fit/prediction controls, and cycle detection, -- user-defined custom element-indexed scalar sets, -- frozen neutral H–Lr spherical proatomic-density profiles with scalar, - unit-aware evaluation, -- a stable pairwise neutral-proatom boundary and an optional cutoff-bounded, - resolution-limited promolecular-minimum proxy. - -## Core terms - -`atomref` uses a small vocabulary on purpose. - -- **quantity** — the operational property family being requested, such as - `covalent_radius`, `van_der_waals_radius`, `atomic_radius`, or - `xh_bond_length`, or `proatomic_density`. -- **domain** — the key space used to index that quantity. In the current - runtime, the supported domain is `element`, meaning lookups are keyed by an - element symbol. -- **dataset** — one curated named scalar table or radial-profile payload inside - a quantity, such as `cordero2008`, `alvarez2013`, or the neutral H–Lr - proatomic set. -- **policy** — the ordered rule set that decides what scalar value to return - when the preferred dataset is incomplete. - -The metadata layer records `domain` and storage kind explicitly. The current -runtime supports the `element` domain with packaged scalar tables and one -shared-grid radial-profile family. Lookup policies, substitution, and linear -transfer intentionally remain scalar-only; radial profiles are never completed -by correlation or neighboring-element substitution. - -## Why this exists - -Scientific software often wants a complete lookup table, but the best dataset -for the job is rarely complete. `atomref` makes that situation explicit. -Instead of hiding ad hoc defaults inside algorithm code, you choose a target -set, describe how missing values may be restored, and keep provenance on what -was actually returned. - -The built-in default behavior is intentionally simple and practical: - -- **Cordero covalent radii** (`cordero2008`) are the preferred covalent target - set, with missing values substituted from the **legacy CSD covalent radii** - (`csd_legacy_cov`). -- **Alvarez van der Waals radii** (`alvarez2013`) are the preferred vdW target - set, with missing values restored from the **Rahm isodensity atomic radii** - (`rahm2016`) through a fitted linear transfer. -- **CSD/ConQuest hydrogen-normalisation defaults** (`csd_legacy_xh_cno`) are a - provisional sparse X–H target set for `C`, `N`, and `O`, with other parent - elements inferred from **Cordero covalent radii** through a fitted linear - transfer. - -Nested policy predictors are supported too. `LinearTransfer` separates -**fit-time** use of nested predictor values from **prediction-time** use. By default, the fit may use only direct nested -values, while the final requested element may still use one additional -nested completion step. That is a useful compromise for workflows such as -provisional X–H inference from a chosen covalent-radii policy. - -## Quick example +`atomref` provides cited atomic values and frozen spherical free-atom electron +densities through a small Python API for crystallographic, quantum-chemical, +and molecular-structure algorithms. + +Use it when your software needs dependable atomic radii, X–H reference lengths, +neutral proatomic densities, or pairwise reference-atom boundaries without +embedding another project-local table and its fallback rules. The core runtime +is pure Python and has **no required third-party dependencies**. + +## Install and get a useful result + +```bash +pip install atomref +``` + +```python +import atomref as ar + +covalent_c = ar.get_covalent_radius("C") +xh_n = ar.get_xh_bond_length("N") +rho_o = ar.get_proatomic_density("O", 0.75) +boundary = ar.estimate_proatomic_boundary("C", "O", 1.43) +minimum = ar.estimate_promolecular_density_minimum("C", "O", 1.43) +``` + +The scalar results above are in documented units: radii and X–H lengths use +angstrom, while the density call returns electron/bohr³ by default. Pairwise +coordinates are measured from the first atom toward the second. For this C–O +example, `boundary` is the stable equal-proatom divider and `minimum` is the +optional, resolution-limited minimum of the summed promolecular line density. + +[Start with the quickstart](https://delonecommons.github.io/atomref/guide/quickstart/) +or open the [complete API reference](https://delonecommons.github.io/atomref/api/). + +## What it solves + +- **Bond and contact geometry:** select named covalent and van der Waals radii + instead of scattering constants through structure code. +- **Hydrogen normalization:** obtain provisional, provenance-aware X–H target + lengths keyed by the parent element. +- **Incomplete reference tables:** use explicit substitution or fitted transfer + policies and inspect how a value was obtained. +- **Free-atom density sampling:** evaluate immutable neutral H–Lr spherical + profiles with explicit coordinate and density units over a strict 0–20 bohr + domain. +- **Pairwise reference-atom models:** choose a stable proatomic boundary or an + explicitly cutoff-bounded promolecular-minimum proxy without presenting + either as an exact molecular QTAIM surface. + +Most convenience functions come in two forms: ```pycon >>> import atomref as ar ->>> ar.get_covalent_radius("C") -0.76 ->>> ar.get_vdw_radius("O") -1.5 ->>> ar.get_xh_bond_length("N") -1.015 ->>> lookup = ar.lookup_vdw_radius("Pm") ->>> lookup.value +>>> ar.get_vdw_radius("Pm") 2.8972265395148358 ->>> lookup.source -'transfer_linear' ->>> lookup.transfer_depth -1 ->>> lookup.resolved_from +>>> result = ar.lookup_vdw_radius("Pm") +>>> result.source, result.transfer_depth +('transfer_linear', 1) +>>> result.resolved_from (DatasetRef(quantity='atomic_radius', set_id='rahm2016'),) ->>> ar.get_proatomic_density("O", 0.75) -0.1141799693379811 ->>> boundary = ar.estimate_proatomic_boundary("C", "O", 1.5, distance_unit="bohr") ->>> boundary.method, boundary.status -('equal_proatom_density', 'ok') ``` -`get_*` returns only the number. `lookup_*` returns a `LookupResult` that also -records where the value came from, whether a transfer model or policy source was -involved, and how many transfer steps were needed (`transfer_depth`). +`get_*` returns the selected number. `lookup_*` returns a typed `LookupResult` +with the source, supporting datasets, placeholder state, fit information, and +transfer depth. -You can inspect the packaged quantity and dataset catalog directly: +## Why adopt `atomref`? -```pycon ->>> import atomref as ar ->>> ar.list_quantities() -('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') ->>> ar.get_quantity_info("xh_bond_length") -QuantityInfo(quantity='xh_bond_length', domain='element', units='angstrom', description='Element-indexed reference X-H bond lengths keyed by parent element X and intended for hydrogen-position normalisation or related geometry workflows.') ->>> [info.ref.set_id for info in ar.list_dataset_infos("van_der_waals_radius", usage_role="target")] -['bondi1964', 'rowland_taylor1996', 'alvarez2013', 'chernyshov2020'] +A local constants file is easy to start and difficult to maintain. It usually +accumulates uncited values, silent replacements for missing elements, ambiguous +units, and behavior that cannot be reviewed independently of the consuming +algorithm. `atomref` keeps those concerns in one versioned layer: + +- every packaged dataset has a stable identifier, coverage metadata, and + bibliographic provenance; +- lookup rules are explicit and deterministic rather than hidden in callers; +- direct, substituted, fitted, fallback, placeholder, and missing results stay + distinguishable; +- public types, units, valid ranges, and failure behavior are documented; +- packaged data and attribution are checked in both wheel and source + distributions; +- the dependency-free runtime can be embedded in lightweight scientific tools. + +The package does not claim that one table or policy is universally correct. It +makes the selected reference and the assumptions around it visible. + +## Installation choices + +The base install is sufficient for every runtime API: + +```bash +pip install atomref ``` -You can also load a packaged set directly: +Install the notebook extra to run the shipped Jupyter examples and their plots: -```pycon ->>> import atomref as ar ->>> vdw = ar.get_radii_set("van_der_waals", "alvarez2013") ->>> vdw.get("O") -1.5 ->>> xh = ar.get_xh_set("csd_legacy_xh_cno") ->>> xh.get("C") -1.089 ->>> info = ar.get_proatomic_density_set_info() ->>> radial = ar.get_builtin_set(info.ref) ->>> type(radial).__name__ -'ElementRadialSet' +```bash +pip install "atomref[notebook]" +``` + +Install all current user-facing optional features with: + +```bash +pip install "atomref[all]" ``` -The proatomic profiles are isolated neutral spherical references, not molecular -electron densities. Pairwise `boundary` mode is the stable default. Optional -`minimum` mode searches only above the fixed `1e-4 electron/bohr^3` per-atom -tail cutoff and has a practical resolution of `0.01 bohr`; it may return an -explicit diagnostic status without a coordinate. See the -[proatomic-density guide](https://delonecommons.github.io/atomref/guide/proatomic_density/) -for the 20-bohr domain, units, provenance, mode selection, and physical -limitations. +Contributor test, lint, build, upload, and release tools are intentionally not +part of `all`. See the [installation guide](https://delonecommons.github.io/atomref/guide/install/) +for development setup. + +## Data, provenance, and scientific scope + +The scalar catalog includes named covalent, van der Waals, atomic-isodensity, +and provisional X–H datasets. Registry metadata separates the requested +quantity from scientific classification and from whether a dataset is a direct +target or transfer support. + +The neutral proatomic profiles are a deterministic consumer snapshot of +`atomref-proatoms` 2.0.0 dataset +`pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`. They cover H–Lr and record the PBE0, +self-consistent spherical fractional-occupation UKS, spin-free one-electron +X2C, and dyall-v4z definition, source hashes, CC BY 4.0 data license, and both +concept and version-specific DOIs. + +These profiles are isolated, neutral, spherical reference atoms—not molecular +electron densities. Density evaluation is scalar, and the public radius domain +is exactly 0–20 bohr. Pairwise `boundary` mode is the stable default. Optional +`minimum` mode searches only where both proatoms remain above +`1e-4 electron/bohr^3`, has a declared `0.01 bohr` resolution, and may return a +typed diagnostic without a coordinate. + +- [Dataset catalog and provenance](https://delonecommons.github.io/atomref/datasets/) +- [Proatomic-density and pairwise scientific guide](https://delonecommons.github.io/atomref/guide/proatomic_density/) +- [Policy guide](https://delonecommons.github.io/atomref/guide/policies/) +- [Explicit non-goals](https://delonecommons.github.io/atomref/guide/non_goals/) -## Notebook walkthroughs +## Executable notebook documentation -The repository ships example notebooks for the main workflows. The first three -are also available as rendered Markdown pages. The `0.2.0` proatomic notebooks -are saved, executed source artifacts with outputs and plots. +The documentation renders the actual committed `.ipynb` files directly, +including Markdown, code, mathematics, saved text, and saved PNG plots. Site +builds do not execute or rewrite them; a separate temporary Jupyter smoke check +verifies that their code still runs. - [Notebook overview](https://delonecommons.github.io/atomref/guide/notebooks/) - [Quickstart notebook](https://delonecommons.github.io/atomref/notebooks/01-quickstart/) - [Policies and assessment notebook](https://delonecommons.github.io/atomref/notebooks/02-policies-and-assessment/) - [Custom sets and discovery notebook](https://delonecommons.github.io/atomref/notebooks/03-custom-sets-and-discovery/) -- [IAS method-selection study](https://delonecommons.github.io/atomref/dev/ias_method_selection/) -- [Executed proatomic density and IAS feature notebook](https://github.com/DeloneCommons/atomref/blob/main/notebooks/05-proatomic-density-and-ias.ipynb) +- [IAS method-selection study](https://delonecommons.github.io/atomref/notebooks/04-ias-method-selection-study/) +- [Proatomic density and IAS workflows](https://delonecommons.github.io/atomref/notebooks/05-proatomic-density-and-ias/) -## Relationship to Delone Commons +## Dataset and policy discovery + +The lower-level registry is available when an application needs to choose or +report an exact source: + +```pycon +>>> import atomref as ar +>>> ar.list_quantities() +('covalent_radius', 'van_der_waals_radius', 'atomic_radius', 'xh_bond_length', 'proatomic_density') +>>> [info.ref.set_id for info in ar.list_dataset_infos( +... "van_der_waals_radius", usage_role="target" +... )] +['bondi1964', 'rowland_taylor1996', 'alvarez2013', 'chernyshov2020'] +>>> profile_info = ar.get_proatomic_density_set_info() +>>> profile_info.ref.set_id +'pbe0_sfx2c_dyallv4z_h-lr_neutral_v2' +``` -`atomref` is designed as a standalone package, but within Delone Commons it is -primarily intended to support chemistry-aware packages such as: +Custom element-indexed scalar sets can participate in the same policy layer. +Radial profiles deliberately do not: no scalar `ValuePolicy`, neighboring- +element substitution, or fitted correlation is applied to density data. -- `molcryst`, for covalent-bond detection, contact analysis, and hydrogen workflows, -- future `chemvoro`, for chemistry-aware contact and hydrogen workflows. +## Relationship to Delone Commons -By contrast, `pyvoro2` and `pbcgraph` are intentionally general mathematical -packages and are not direct consumers of `atomref`. +`atomref` is a standalone package. Within Delone Commons it is intended for +chemistry-aware consumers such as `molcryst` and future `chemvoro` workflows. +General mathematical packages such as `pyvoro2` and `pbcgraph` remain +independent of atomic-reference policy. -## Data curation and developer tools +## Maintainer checks -The repository also ships small maintenance tools. The most important ones are: +The repository keeps a small set of release tools: -- `python tools/check_registry.py` — validate curated registry metadata against - every packaged scalar and radial payload, -- `python tools/check_notebooks.py` — validate saved release-notebook state and - execute notebook code cells, -- `python tools/export_notebooks.py` — turn notebooks into Markdown pages for - the docs, -- `python tools/gen_readme.py` — regenerate `README.md` from this page, -- `python tools/release_check.py` — run the full release-preparation checklist, - including linting, tests, docs, builds, and artifact validation. +- `python tools/check_registry.py` validates registry metadata against every + packaged scalar and radial payload; +- `python tools/check_notebooks.py` smoke-executes temporary notebook copies + through a standard Jupyter kernel and discards the results; +- `python tools/gen_readme.py` regenerates this README from `docs/index.md`; +- `python tools/release_check.py` runs lint, tests, strict docs, distribution + checks, and clean artifact-installation checks. See the [tools README](https://github.com/DeloneCommons/atomref/blob/main/tools/README.md) -for a short description of each script. +for the maintainer-only data snapshot workflow and command details. diff --git a/mkdocs.yml b/mkdocs.yml index bdab673..c8cf96c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,7 +65,6 @@ nav: - Architecture: dev/architecture.md - Data curation: dev/data_curation.md - IAS method selection: dev/ias_method_selection.md - - Development plan: dev/dev_plan.md - API: - Overview: api/index.md - atomref: api/atomref.md From da350a89beac33680dabc345e648a76137d7a22a Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 00:24:32 +0300 Subject: [PATCH 13/23] chore: add complete user extras --- docs/guide/install.md | 64 ++++++++++++++++++++++----- pyproject.toml | 7 +++ tests/meta/test_packaging_metadata.py | 53 ++++++++++++++++++++++ 3 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 tests/meta/test_packaging_metadata.py diff --git a/docs/guide/install.md b/docs/guide/install.md index 9153eec..e76d6d4 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -1,31 +1,71 @@ # Install -For normal use, install the runtime package: +## Lightweight runtime + +Install the dependency-free runtime for radii, X–H, density, registry, policy, +and pairwise APIs: ```bash pip install atomref ``` -`atomref` is pure Python and has no required runtime dependencies outside the -standard library. +`atomref` supports Python 3.10 and later. Its required runtime dependency set is +empty; every core calculation uses the Python standard library and packaged +data. -For local development, documentation work, and tests, install the editable -package together with the main extras: +Verify the installation with a useful result: ```bash -pip install -e ".[test,notebook,docs,dev]" +python -c "import atomref as ar; print(ar.get_covalent_radius('C'))" ``` -Those extras currently cover: +## Notebook-capable installation + +Install the direct notebook renderer, standard Jupyter execution support, +kernel, and plotting dependency with: + +```bash +pip install "atomref[notebook]" +``` -- `test` — pytest and test-only compatibility helpers, -- `notebook` — Matplotlib for executing the saved plotting notebooks, -- `docs` — MkDocs and API documentation tooling, -- `dev` — flake8, build, and release metadata checks. +This extra is needed to run the shipped `.ipynb` examples locally. It is not +needed to use any `atomref` runtime API or to read the rendered notebooks on the +documentation site. +## All user-facing optional features -For a full local pre-release validation pass after installing those extras, run: +Install the union of current user-facing feature extras with: + +```bash +pip install "atomref[all]" +``` + +In `0.2.1`, notebooks are the only optional user-facing feature, so `all` and +`notebook` intentionally contain the same dependency entries. `all` is the +stable choice for applications that want every optional user capability as new +feature extras are added later. It does not include pytest, lint, build, +upload, or release tools. + +## Contributor environment + +For repository development, install the editable package and the contributor +groups required by the task: + +```bash +pip install -e ".[test,dev,docs,notebook]" +``` + +- `test` provides the test runner. +- `dev` provides lint, build, and distribution-metadata checks. +- `docs` provides MkDocs, Material, and typed API-documentation tooling. +- `notebook` provides direct notebook rendering, Jupyter execution, a Python + kernel, and plotting. + +Run the full local release-candidate validation with: ```bash python tools/release_check.py ``` + +That command builds fresh artifacts and validates clean base, `notebook`, and +`all` installations in temporary virtual environments. diff --git a/pyproject.toml b/pyproject.toml index 8297664..869b257 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,13 @@ notebook = [ "nbclient>=0.10,<0.12", "nbformat>=5.10,<6", ] +all = [ + "ipykernel>=6.29", + "matplotlib>=3.8", + "mkdocs-jupyter>=0.26,<0.27", + "nbclient>=0.10,<0.12", + "nbformat>=5.10,<6", +] docs = [ "mkdocs>=1.6,<2", "mkdocs-material>=9.5", diff --git a/tests/meta/test_packaging_metadata.py b/tests/meta/test_packaging_metadata.py new file mode 100644 index 0000000..0e67fd1 --- /dev/null +++ b/tests/meta/test_packaging_metadata.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from pathlib import Path + +try: + import tomllib +except ModuleNotFoundError: # pragma: no cover - exercised on Python 3.10 + import tomli as tomllib + + +REPO_ROOT = Path(__file__).resolve().parents[2] +PYPROJECT = REPO_ROOT / "pyproject.toml" +EXPECTED_NOTEBOOK_DEPENDENCIES = [ + "ipykernel>=6.29", + "matplotlib>=3.8", + "mkdocs-jupyter>=0.26,<0.27", + "nbclient>=0.10,<0.12", + "nbformat>=5.10,<6", +] + + +def _project_metadata() -> dict[str, object]: + return tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))["project"] + + +def test_runtime_dependencies_remain_empty() -> None: + project = _project_metadata() + + assert project["dependencies"] == [] + + +def test_notebook_and_all_extras_are_identical_and_complete() -> None: + project = _project_metadata() + extras = project["optional-dependencies"] + + assert extras["notebook"] == EXPECTED_NOTEBOOK_DEPENDENCIES + assert extras["all"] == EXPECTED_NOTEBOOK_DEPENDENCIES + + +def test_all_extra_excludes_contributor_tooling() -> None: + project = _project_metadata() + all_requirements = "\n".join(project["optional-dependencies"]["all"]).lower() + + for contributor_package in ("build", "flake8", "pytest", "twine"): + assert contributor_package not in all_requirements + + +def test_docs_extra_contains_only_used_documentation_tooling() -> None: + project = _project_metadata() + docs_requirements = "\n".join(project["optional-dependencies"]["docs"]) + + assert "mkdocs-include-markdown-plugin" not in docs_requirements + assert "tomli" not in docs_requirements From c4396eab3c83d5ecc614f48a29055e488ca21557 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 00:25:54 +0300 Subject: [PATCH 14/23] chore: prepare atomref 0.2.1 --- .github/workflows/ci.yml | 32 +-- CHANGELOG.md | 37 ++++ DEV_PLAN.md | 131 ++++++++---- docs/guide/install.md | 6 +- src/atomref/__about__.py | 2 +- tests/meta/test_release_tools.py | 75 ++++++- tests/test_smoke.py | 2 +- tools/README.md | 4 +- tools/check_dist.py | 348 ++++++++++++++++++++++++++++++- tools/release_check.py | 124 ++++++----- 10 files changed, 611 insertions(+), 150 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f868cf8..030c406 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,34 +88,4 @@ jobs: - name: Validate metadata run: python -m twine check dist/* - name: Check packaged files - run: python tools/check_dist.py dist - - name: Install built wheel and smoke-test it - run: | - python -m pip install --force-reinstall --no-deps dist/*.whl - python - <<'PY' - import atomref as ar - - assert ar.get_covalent_radius('C') == 0.76 - assert ar.get_vdw_radius('C') == 1.77 - assert 'atomic_radius' in ar.list_quantities() - assert 'rahm2016' in ar.list_dataset_ids('atomic_radius', usage_role='support') - ref = ar.DatasetRef( - 'proatomic_density', - 'pbe0_sfx2c_dyallv4z_h-lr_neutral_v2', - ) - dataset = ar.get_builtin_set(ref) - assert dataset.get('O') is not None - rho = ar.get_proatomic_density( - 'O', - 0.75, - radius_unit='angstrom', - density_unit='electron/bohr^3', - ) - assert rho is not None and rho > 0 - boundary = ar.estimate_proatomic_boundary('C', 'O', 1.43) - assert boundary is not None and boundary.position_from_a is not None - minimum = ar.estimate_promolecular_density_minimum('C', 'O', 1.43) - assert minimum is not None - selected = ar.estimate_ias_position('C', 'O', 1.43, mode='boundary') - assert selected == boundary - PY + run: python tools/check_dist.py dist --check-installs diff --git a/CHANGELOG.md b/CHANGELOG.md index bc6cc39..54380b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## 0.2.1 - 2026-07-14 + +### Added + +- Complete structured public API documentation with rendered typed signatures, + parameters, returns, raised errors, attributes, examples, and cross-references. +- An `all` extra that exactly mirrors the complete user-facing `notebook` extra. +- Clean built-wheel installation checks for the base package, `notebook`, and + `all` extras. + +### Changed + +- Repositioned the documentation home page and generated README around rapid + installation, first use, scientific provenance, and adoption by downstream + structure-analysis software. +- Rendered the maintained `.ipynb` notebooks directly in MkDocs with committed + Markdown, code, mathematics, text output, and PNG plots. +- Replaced the bespoke notebook execution/export path with one temporary + standard-Jupyter smoke check that discards its execution results. + +### Packaging + +- Declared the renderer, execution client, notebook format library, kernel, and + plotting library in both `notebook` and `all`, while keeping documentation, + test, lint, build, and release tools out of `all`. +- Updated CI, source-distribution checks, and release preparation for the final + single-source notebook layout and isolated artifact installations. +- Build release artifacts from a clean committed-source extraction and reject + nonstandard executable modes on ordinary wheel and source-distribution files. +- Removed generated notebook Markdown, the custom exporter, export-sync tests, + and the duplicate documentation copy of the development plan. + +### Scientific behavior + +- No density values, cutoff radii, pairwise modes, selected coordinates, + statuses, packaged scientific data, or other numerical behavior changed. + ## 0.2.0 - 2026-07-14 ### Added diff --git a/DEV_PLAN.md b/DEV_PLAN.md index 5edee75..2d4cb63 100644 --- a/DEV_PLAN.md +++ b/DEV_PLAN.md @@ -1,9 +1,9 @@ # atomref 0.2.x development plan -> **Status:** fourth review draft — Stage 4 replanned after numerical review -> **Plan lifecycle state:** `ACTIVE` -> **Primary target:** `atomref 0.2.0` — dataset architecture, neutral proatomic density, two-mode pairwise boundary/IAS-proxy analysis, and executed notebooks -> **Planned follow-up:** `atomref 0.2.1` — notebook/documentation infrastructure, complete API reference, and README positioning +> **Status:** Stage 6 implementation complete — `0.2.1` release candidate awaiting external review +> **Plan lifecycle state:** `COMPLETE_AWAITING_REVIEW` +> **Accepted release:** `atomref 0.2.0` — tagged without rewriting accepted history +> **Review candidate:** `atomref 0.2.1` — direct notebook documentation, complete API reference, adoption-oriented README, packaging, and release hardening > **Source data:** `atomref-proatoms 2.0.0`, dataset `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2` ## 1. Purpose and lifecycle of this file @@ -2824,54 +2824,54 @@ chore: prepare atomref 0.2.1 ### 14.6 `0.2.1` documentation, notebooks, packaging, and release -- [ ] Full typed signatures render. -- [ ] All public parameters, defaults, returns, raises, units, ranges, and +- [x] Full typed signatures render. +- [x] All public parameters, defaults, returns, raises, units, ranges, and attributes render correctly. -- [ ] Public docstrings use one structured style. -- [ ] API-page member selection is complete and excludes irrelevant internals. -- [ ] API cross-references work. -- [ ] README/home page explains the package value immediately. -- [ ] Installation and a compact working example appear before detailed internal +- [x] Public docstrings use one structured style. +- [x] API-page member selection is complete and excludes irrelevant internals. +- [x] API cross-references work. +- [x] README/home page explains the package value immediately. +- [x] Installation and a compact working example appear before detailed internal terminology. -- [ ] Working radii, X–H, density, boundary-mode, and minimum-mode capabilities +- [x] Working radii, X–H, density, boundary-mode, and minimum-mode capabilities are discoverable from the opening documentation path. -- [ ] The README explains honestly why a developer should use `atomref` rather +- [x] The README explains honestly why a developer should use `atomref` rather than maintain project-local constants, datasets, and fallback rules. -- [ ] Detailed policy and registry terminology is moved below first-use content. -- [ ] Provenance and limitations remain explicit without obscuring the initial +- [x] Detailed policy and registry terminology is moved below first-use content. +- [x] Provenance and limitations remain explicit without obscuring the initial adoption path. -- [ ] `mkdocs-jupyter` renders the actual `.ipynb` files directly in the MkDocs +- [x] `mkdocs-jupyter` renders the actual `.ipynb` files directly in the MkDocs site. -- [ ] Each notebook has exactly one maintained source file and no parallel +- [x] Each notebook has exactly one maintained source file and no parallel Markdown representation. -- [ ] Inline `$...$` and display `$$...$$` mathematics render correctly with the +- [x] Inline `$...$` and display `$$...$$` mathematics render correctly with the current Material theme. -- [ ] Saved text output and saved PNG plots render correctly. -- [ ] Ordinary and strict documentation builds do not execute or rewrite +- [x] Saved text output and saved PNG plots render correctly. +- [x] Ordinary and strict documentation builds do not execute or rewrite notebooks. -- [ ] Notebook code is smoke-executed once through standard Jupyter tooling in a +- [x] Notebook code is smoke-executed once through standard Jupyter tooling in a temporary location and execution errors fail the check. -- [ ] Notebook smoke execution does not compare outputs or enforce freshness, +- [x] Notebook smoke execution does not compare outputs or enforce freshness, normalization, or byte-for-byte reproducibility. -- [ ] Generated notebook Markdown files, `tools/export_notebooks.py`, export-sync +- [x] Generated notebook Markdown files, `tools/export_notebooks.py`, export-sync tests, and obsolete CI/release commands are removed. -- [ ] No duplicate notebook tree, symlink, staging directory, or committed +- [x] No duplicate notebook tree, symlink, staging directory, or committed notebook-conversion cache remains. -- [ ] Notebook navigation, links, source links, packaging checks, and sdist paths +- [x] Notebook navigation, links, source links, packaging checks, and sdist paths use the final notebook locations. -- [ ] Every direct notebook dependency is declared in `notebook` and duplicated +- [x] Every direct notebook dependency is declared in `notebook` and duplicated in `all`. -- [ ] Documentation CI installs both `docs` and `notebook` extras. -- [ ] An `all` extra contains every user-facing optional feature dependency. -- [ ] `all` excludes test, lint, packaging, upload, and release-only dependencies. -- [ ] Base, `atomref[notebook]`, and `atomref[all]` installations succeed from +- [x] Documentation CI installs both `docs` and `notebook` extras. +- [x] An `all` extra contains every user-facing optional feature dependency. +- [x] `all` excludes test, lint, packaging, upload, and release-only dependencies. +- [x] Base, `atomref[notebook]`, and `atomref[all]` installations succeed from built artifacts in clean environments. -- [ ] Documented minimal examples run outside the repository checkout. -- [ ] Duplicate development-plan docs are removed. -- [ ] Architecture, data-curation, scientific guides, package metadata, and +- [x] Documented minimal examples run outside the repository checkout. +- [x] Duplicate development-plan docs are removed. +- [x] Architecture, data-curation, scientific guides, package metadata, and changelog match the accepted implementation. -- [ ] Version is `0.2.1`. -- [ ] Strict docs and full release checks pass. +- [x] Version is `0.2.1`. +- [x] Strict docs and full release checks pass. ## 15. Optional architecture review after `0.2.0` @@ -3129,32 +3129,71 @@ Lifecycle state: COMPLETE_AWAITING_REVIEW Completed releases/stages: - - ... + - atomref 0.2.0 was accepted and tagged before this implementation. + - Stage 6 completed the merged atomref 0.2.1 documentation, notebook, + packaging, and release-candidate contract. Final commits or pull requests: - - ... + - docs: render notebooks directly + - docs: complete typed API reference + - docs: reposition atomref documentation + - chore: add complete user extras + - chore: prepare atomref 0.2.1 (this commit) + - local commits only; no pull request, push, tag, or publication action Acceptance checks: - - : + - flake8 src tests tools: passed + - python tools/check_registry.py: passed + - python tools/check_notebooks.py: five notebooks passed in disposable + standard-Jupyter kernels; committed sources were unchanged + - temporary failing-notebook probe: failed with CellExecutionError as required; + the probe source hash was unchanged and the probe was discarded + - python tools/gen_readme.py --check: passed + - python -m pytest -q: 267 passed + - mkdocs build --strict: passed with execute=False for all five notebooks; + source hashes were unchanged and rendered code, inline/display mathematics, + saved text, and saved PNG output were inspected successfully + - rendered API inspection: all 65 top-level exports, typed signatures, + class constructor defaults, structured sections, attributes, examples, and + intended special methods rendered; lookup_value() has a standalone Examples + section and exactly two Raises rows; internal resolver and __post_init__ + members remained excluded + - python tools/release_check.py: passed, including build, twine, distribution + content checks, conventional 0644 regular-file modes, and clean + base/notebook/all artifact installations from a clean committed HEAD export + - runtime AST comparison against Stage 6 baseline after removing docstrings: + unchanged across all nine documentation-edited runtime modules + - git diff --exit-code 3490e348b6a3824bfaa0e60c3cda88f82595106c -- + src/atomref/data: passed + - history audit: the Stage 6 sequence descends from the recorded baseline + 3490e348b6a3824bfaa0e60c3cda88f82595106c without amendment; the v0.2.0 tag + object remained c5b5901cc23ad2914a546c3c61a9feee53d41985 and still peeled to + 1fba665a41d5a7b2ee97d27701347113b277cd4f Binding requirements not implemented: - none - or - - Plan deviations: - - none - or - - + - none; the preferred mkdocs-jupyter path passed the focused mathematics + prototype, so the nbconvert fallback was not used Future-reference items intentionally not touched: - - ... + - ions and additional atomic properties + - vectorized, grid, and molecular-density APIs + - exact-QTAIM functionality and future molecular benchmarks + - optional post-0.2.0 architecture ideas in Section 15 Known limitations remaining within the accepted release: - - ... + - profiles remain neutral, spherical H–Lr references with scalar evaluation + over the documented 0–20 bohr domain + - pairwise modes remain reference-atom proxies rather than exact molecular + QTAIM surfaces, and minimum mode may return a typed non-result + - X–H support remains the documented provisional parent-element dataset scope Implementation agent conclusion: - - + - every binding Stage 6 and Section 14.6 item is implemented and validated; + atomref 0.2.1 is ready for independent diff, science, documentation, and + artifact review as a local commit sequence without being tagged or published External review required: - verify repository diff and history diff --git a/docs/guide/install.md b/docs/guide/install.md index e76d6d4..5a44a6f 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -67,5 +67,7 @@ Run the full local release-candidate validation with: python tools/release_check.py ``` -That command builds fresh artifacts and validates clean base, `notebook`, and -`all` installations in temporary virtual environments. +That command requires a clean worktree, builds fresh artifacts from a temporary +extraction of the committed `HEAD` with conventional file modes, and validates +clean base, `notebook`, and `all` installations in temporary virtual +environments. diff --git a/src/atomref/__about__.py b/src/atomref/__about__.py index d3ec452..3ced358 100644 --- a/src/atomref/__about__.py +++ b/src/atomref/__about__.py @@ -1 +1 @@ -__version__ = "0.2.0" +__version__ = "0.2.1" diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index 425418a..2569e48 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -16,6 +16,16 @@ SNAPSHOT_PATH = REPO_ROOT / "src" / "atomref" / "data" / ( "proatomic_density_neutral.zip" ) +VALID_WHEEL_METADATA = b"""\ +Metadata-Version: 2.4 +Name: atomref +Version: 0.2.1 +Provides-Extra: notebook +Provides-Extra: all +Requires-Dist: ipykernel>=6.29; extra == 'notebook' +Requires-Dist: ipykernel>=6.29; extra == 'all' + +""" spec = importlib.util.spec_from_file_location("check_dist_tool", CHECK_DIST_PATH) assert spec is not None and spec.loader is not None @@ -51,17 +61,79 @@ def test_dist_check_requires_release_tools_and_legacy_data() -> None: assert ".flake8" in check_dist.REQUIRED_SDIST_SUFFIXES assert "tools/check_registry.py" in check_dist.REQUIRED_SDIST_SUFFIXES assert "tools/check_dist.py" in check_dist.REQUIRED_SDIST_SUFFIXES + assert "docs/notebooks/05-proatomic-density-and-ias.ipynb" in ( + check_dist.REQUIRED_SDIST_SUFFIXES + ) + assert "tools/export_notebooks.py" not in check_dist.REQUIRED_SDIST_SUFFIXES + assert "dist-info/licenses/COPYING" in check_dist.REQUIRED_WHEEL_MEMBERS def test_sdist_root_readme_cannot_be_satisfied_by_tools_readme() -> None: with pytest.raises(check_dist.DistCheckError, match="root-level 'README.md'"): check_dist._sdist_root_member( - {"atomref-0.2.0/tools/README.md"}, + {"atomref-9.9.9/tools/README.md"}, "README.md", label="test sdist", ) +def test_sdist_layout_rejects_obsolete_notebook_paths() -> None: + members = { + "atomref-0.2.1/docs/notebooks/01-quickstart.ipynb", + "atomref-0.2.1/notebooks/01-quickstart.ipynb", + } + + with pytest.raises(check_dist.DistCheckError, match="exactly one source"): + check_dist._assert_sdist_layout(members, label="test sdist") + + +def test_sdist_layout_rejects_generated_notebook_markdown() -> None: + members = { + f"atomref-0.2.1/{name}" for name in check_dist.EXPECTED_SDIST_NOTEBOOKS + } + members.add("atomref-0.2.1/docs/notebooks/01-quickstart.md") + + with pytest.raises(check_dist.DistCheckError, match="obsolete members"): + check_dist._assert_sdist_layout(members, label="test sdist") + + +def test_wheel_metadata_accepts_empty_runtime_and_matching_user_extras() -> None: + check_dist._assert_wheel_metadata( + VALID_WHEEL_METADATA, + member="atomref.dist-info/METADATA", + label="test wheel", + ) + + +def test_dist_check_accepts_conventional_regular_file_modes() -> None: + check_dist._assert_regular_file_modes( + [("atomref/module.py", 0o100644), ("atomref/data/table.csv", 0o644)], + label="test artifact", + ) + + +def test_dist_check_rejects_executable_payload_file_modes() -> None: + with pytest.raises(check_dist.DistCheckError, match="module.py=0755"): + check_dist._assert_regular_file_modes( + [("atomref/module.py", 0o100755)], + label="test artifact", + ) + + +def test_wheel_metadata_rejects_runtime_dependencies() -> None: + payload = ( + VALID_WHEEL_METADATA.rstrip() + + b"\nRequires-Dist: numpy>=2\n\n" + ) + + with pytest.raises(check_dist.DistCheckError, match="runtime requirements"): + check_dist._assert_wheel_metadata( + payload, + member="atomref.dist-info/METADATA", + label="test wheel", + ) + + def test_dist_check_rejects_changed_snapshot_archive() -> None: payload = SNAPSHOT_PATH.read_bytes() + b"altered" @@ -103,3 +175,4 @@ def test_release_check_help() -> None: text=True, ) assert "release-preparation checks" in result.stdout + assert "--skip-install-checks" in result.stdout diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 17ae6b6..bcb6912 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -4,7 +4,7 @@ def test_release_version() -> None: - assert ar.__version__ == '0.2.0' + assert ar.__version__ == '0.2.1' def test_basic_smoke_import_and_lookup() -> None: diff --git a/tools/README.md b/tools/README.md index 6f840d8..2bd03a7 100644 --- a/tools/README.md +++ b/tools/README.md @@ -17,7 +17,8 @@ release preparation. packaged scalar and radial payload. - `gen_readme.py` — regenerate `README.md` from `docs/index.md`. - `release_check.py` — run the full release-preparation checklist, - including linting, tests, docs, builds, and artifact validation. + including linting, tests, docs, a clean committed-source build with + conventional archive modes, and artifact validation. ## Typical commands @@ -25,6 +26,7 @@ release preparation. python tools/check_registry.py python tools/check_notebooks.py python tools/gen_readme.py +python tools/check_dist.py dist --check-installs python tools/release_check.py ``` diff --git a/tools/check_dist.py b/tools/check_dist.py index 85c9c68..fdf98c8 100644 --- a/tools/check_dist.py +++ b/tools/check_dist.py @@ -3,10 +3,19 @@ from __future__ import annotations import argparse +from email import policy +from email.parser import BytesParser import hashlib import io +import os from pathlib import Path +import re +import stat +import subprocess +import sys import tarfile +import tempfile +import venv import zipfile @@ -18,9 +27,20 @@ "atomref/data/xh_bond_length.csv", "atomref/data/registry.json", "atomref/py.typed", + "dist-info/METADATA", + "dist-info/licenses/COPYING", + "dist-info/licenses/LICENSE", "dist-info/licenses/NOTICE.md", } +EXPECTED_SDIST_NOTEBOOKS = { + "docs/notebooks/01-quickstart.ipynb", + "docs/notebooks/02-policies-and-assessment.ipynb", + "docs/notebooks/03-custom-sets-and-discovery.ipynb", + "docs/notebooks/04-ias-method-selection-study.ipynb", + "docs/notebooks/05-proatomic-density-and-ias.ipynb", +} + REQUIRED_SDIST_SUFFIXES = { "src/atomref/data/periodic_table.csv", "src/atomref/data/covalent.csv", @@ -32,32 +52,82 @@ "README.md", "CHANGELOG.md", "DEV_PLAN.md", + "COPYING", "LICENSE", "NOTICE.md", "pyproject.toml", ".flake8", - "notebooks/01-quickstart.ipynb", - "notebooks/02-policies-and-assessment.ipynb", - "notebooks/03-custom-sets-and-discovery.ipynb", - "notebooks/04-ias-method-selection-study.ipynb", - "notebooks/05-proatomic-density-and-ias.ipynb", - "docs/notebooks/01-quickstart.md", - "docs/notebooks/02-policies-and-assessment.md", - "docs/notebooks/03-custom-sets-and-discovery.md", + "docs/index.md", + *EXPECTED_SDIST_NOTEBOOKS, "docs/guide/notebooks.md", "docs/guide/proatomic_density.md", + "docs/dev/architecture.md", + "docs/dev/data_curation.md", "docs/dev/ias_method_selection.md", + "docs/api/index.md", "docs/api/proatoms.md", + "docs/assets/ias-method-study/c-o-method-comparison.png", + "docs/assets/ias-method-study/cutoff-radii.png", + "docs/assets/ias-method-study/li-li-symmetry.png", "tools/build_proatomic_density_snapshot.py", "tools/check_notebooks.py", "tools/check_registry.py", "tools/check_dist.py", - "tools/export_notebooks.py", "tools/gen_readme.py", "tools/release_check.py", "tools/README.md", } +FORBIDDEN_SDIST_MEMBERS = { + "docs/dev/dev_plan.md", + "tools/export_notebooks.py", +} + +EXPECTED_VERSION = "0.2.1" +EXPECTED_REGULAR_FILE_MODE = 0o644 +REQUIRED_USER_EXTRAS = {"all", "notebook"} +EXTRA_MARKER = re.compile(r"\bextra\s*==\s*(['\"])([-a-zA-Z0-9_.]+)\1") + +OPTIONAL_IMPORTS = """\ +import ipykernel +import matplotlib +import mkdocs_jupyter +import nbclient +import nbformat +""" + +API_SMOKE = """\ +import atomref as ar + +assert ar.__version__ == "0.2.1" +assert ar.get_covalent_radius("C") == 0.76 +assert ar.get_vdw_radius("C") == 1.77 +assert ar.get_xh_bond_length("N") is not None +assert "atomic_radius" in ar.list_quantities() +assert "rahm2016" in ar.list_dataset_ids( + "atomic_radius", usage_role="support" +) +ref = ar.DatasetRef( + "proatomic_density", + "pbe0_sfx2c_dyallv4z_h-lr_neutral_v2", +) +dataset = ar.get_builtin_set(ref) +assert dataset.get("O") is not None +rho = ar.get_proatomic_density( + "O", + 0.75, + radius_unit="angstrom", + density_unit="electron/bohr^3", +) +assert rho is not None and rho > 0 +boundary = ar.estimate_proatomic_boundary("C", "O", 1.43) +assert boundary.position_from_a is not None +minimum = ar.estimate_promolecular_density_minimum("C", "O", 1.43) +assert minimum.requested_mode == "minimum" +selected = ar.estimate_ias_position("C", "O", 1.43, mode="boundary") +assert selected == boundary +""" + ATTRIBUTION_MARKERS = { "CC BY 4.0", "10.5281/zenodo.21291021", @@ -76,7 +146,29 @@ class DistCheckError(RuntimeError): - """Raised when a built distribution is missing required members.""" + """Raised when a built distribution violates the release contract.""" + + +def _assert_regular_file_modes( + members: list[tuple[str, int]], + *, + label: str, +) -> None: + """Require conventional non-executable modes for regular payload files.""" + + unexpected = [ + (name, stat.S_IMODE(mode)) + for name, mode in members + if stat.S_IMODE(mode) != EXPECTED_REGULAR_FILE_MODE + ] + if unexpected: + details = ", ".join( + f"{name}={mode:04o}" for name, mode in unexpected + ) + raise DistCheckError( + f"{label} regular files must use mode " + f"{EXPECTED_REGULAR_FILE_MODE:04o}: {details}" + ) def _assert_members_present( @@ -103,6 +195,52 @@ def _members_matching_suffixes(actual: set[str], suffixes: set[str]) -> set[str] return matched +def _sdist_relative_members(actual: set[str], *, label: str) -> set[str]: + """Remove the generated source-distribution root from member names.""" + + roots = {name.split("/", 1)[0] for name in actual if name} + if len(roots) != 1: + raise DistCheckError( + f"{label} must contain exactly one generated root directory" + ) + root = next(iter(roots)) + prefix = f"{root}/" + return {name[len(prefix) :] for name in actual if name.startswith(prefix)} + + +def _assert_sdist_layout(actual: set[str], *, label: str) -> None: + """Reject duplicate notebooks and obsolete generated documentation paths.""" + + relative = _sdist_relative_members(actual, label=label) + obsolete = { + name + for name in relative + if name in FORBIDDEN_SDIST_MEMBERS + or ( + name.startswith("docs/notebooks/") + and name.endswith(".md") + ) + or ".ipynb_checkpoints/" in name + } + if obsolete: + joined = ", ".join(sorted(obsolete)) + raise DistCheckError(f"{label} contains obsolete members: {joined}") + + notebooks = {name for name in relative if name.endswith(".ipynb")} + if notebooks != EXPECTED_SDIST_NOTEBOOKS: + missing = sorted(EXPECTED_SDIST_NOTEBOOKS - notebooks) + unexpected = sorted(notebooks - EXPECTED_SDIST_NOTEBOOKS) + details: list[str] = [] + if missing: + details.append(f"missing: {', '.join(missing)}") + if unexpected: + details.append(f"unexpected: {', '.join(unexpected)}") + raise DistCheckError( + f"{label} must contain exactly one source for each notebook " + f"({'; '.join(details)})" + ) + + def _member_matching_suffix( actual: set[str], suffix: str, @@ -156,6 +294,59 @@ def _decode_utf8(payload: bytes, *, member: str, label: str) -> str: ) from exc +def _assert_wheel_metadata(payload: bytes, *, member: str, label: str) -> None: + """Validate release version, empty core requirements, and user extras.""" + + try: + metadata = BytesParser(policy=policy.default).parsebytes(payload) + except (TypeError, ValueError) as exc: + raise DistCheckError( + f"{label} member {member!r} is not valid package metadata" + ) from exc + + if metadata.get("Name") != "atomref": + raise DistCheckError(f"{label} has unexpected project metadata name") + if metadata.get("Version") != EXPECTED_VERSION: + raise DistCheckError( + f"{label} has unexpected version {metadata.get('Version')!r}; " + f"expected {EXPECTED_VERSION!r}" + ) + + provided_extras = set(metadata.get_all("Provides-Extra", [])) + missing_extras = REQUIRED_USER_EXTRAS - provided_extras + if missing_extras: + joined = ", ".join(sorted(missing_extras)) + raise DistCheckError(f"{label} is missing user extras: {joined}") + + requirements = metadata.get_all("Requires-Dist", []) + unconditional = [ + requirement + for requirement in requirements + if not EXTRA_MARKER.findall(requirement) + ] + if unconditional: + joined = ", ".join(unconditional) + raise DistCheckError( + f"{label} must not declare runtime requirements: {joined}" + ) + + by_extra: dict[str, set[str]] = { + extra: set() for extra in REQUIRED_USER_EXTRAS + } + for requirement in requirements: + requirement_text = requirement.split(";", 1)[0].strip() + extras = {match[1] for match in EXTRA_MARKER.findall(requirement)} + for extra in REQUIRED_USER_EXTRAS & extras: + by_extra[extra].add(requirement_text) + + if not by_extra["notebook"]: + raise DistCheckError(f"{label} notebook extra must not be empty") + if by_extra["all"] != by_extra["notebook"]: + raise DistCheckError( + f"{label} all and notebook extras must contain identical requirements" + ) + + def _assert_proatomic_snapshot(payload: bytes, *, member: str, label: str) -> None: """Require the exact pinned consumer ZIP and scientific CSV fingerprints.""" @@ -191,6 +382,14 @@ def check_wheel(path: Path) -> None: """Validate the contents of one built wheel.""" with zipfile.ZipFile(path) as zf: + _assert_regular_file_modes( + [ + (member.filename, member.external_attr >> 16) + for member in zf.infolist() + if not member.is_dir() + ], + label=path.name, + ) names = set(zf.namelist()) matched = _members_matching_suffixes(names, REQUIRED_WHEEL_MEMBERS) _assert_members_present(matched, REQUIRED_WHEEL_MEMBERS, label=path.name) @@ -206,6 +405,17 @@ def check_wheel(path: Path) -> None: label=path.name, ) + metadata_member = _member_matching_suffix( + names, + "dist-info/METADATA", + label=path.name, + ) + _assert_wheel_metadata( + zf.read(metadata_member), + member=metadata_member, + label=path.name, + ) + for suffix in ( "atomref/data/registry.json", "dist-info/licenses/NOTICE.md", @@ -219,9 +429,19 @@ def check_sdist(path: Path) -> None: """Validate the contents of one built source distribution.""" with tarfile.open(path, "r:gz") as tf: - names = {member.name for member in tf.getmembers()} + members = tf.getmembers() + _assert_regular_file_modes( + [ + (member.name, member.mode) + for member in members + if member.isreg() + ], + label=path.name, + ) + names = {member.name for member in members} matched = _members_matching_suffixes(names, REQUIRED_SDIST_SUFFIXES) _assert_members_present(matched, REQUIRED_SDIST_SUFFIXES, label=path.name) + _assert_sdist_layout(names, label=path.name) readme_member = _sdist_root_member(names, "README.md", label=path.name) readme_file = tf.extractfile(readme_member) @@ -266,11 +486,110 @@ def check_sdist(path: Path) -> None: _assert_attribution(text, member=member, label=path.name) +def _venv_python(env_dir: Path) -> Path: + """Return the Python executable created in ``env_dir``.""" + + bindir = "Scripts" if sys.platform.startswith("win") else "bin" + return env_dir / bindir / "python" + + +def _run_checked( + args: list[str], + *, + cwd: Path, + env: dict[str, str], + label: str, +) -> None: + """Run one clean-install subprocess with a focused failure message.""" + + display_args = list(args) + if "-c" in display_args: + code_index = display_args.index("-c") + 1 + if code_index < len(display_args): + display_args[code_index] = "" + print("+", " ".join(display_args)) + try: + subprocess.run(args, cwd=cwd, env=env, check=True) + except (OSError, subprocess.CalledProcessError) as exc: + raise DistCheckError(f"{label} clean-install check failed") from exc + + +def check_clean_installations(wheel: Path) -> None: + """Install base, notebook, and all variants in separate clean environments.""" + + wheel = wheel.resolve() + if not wheel.is_file(): + raise DistCheckError(f"wheel does not exist: {wheel}") + + clean_env = os.environ.copy() + clean_env.pop("PYTHONHOME", None) + clean_env.pop("PYTHONPATH", None) + + with tempfile.TemporaryDirectory(prefix="atomref-artifact-installs-") as tmp: + root = Path(tmp) + outside_checkout = root / "outside-checkout" + outside_checkout.mkdir() + + for extra in (None, "notebook", "all"): + label = "base" if extra is None else extra + env_dir = root / f"venv-{label}" + venv.EnvBuilder(with_pip=True).create(env_dir) + python = _venv_python(env_dir) + + if extra is None: + install_target = str(wheel) + else: + install_target = f"atomref[{extra}] @ {wheel.as_uri()}" + + install = [ + str(python), + "-m", + "pip", + "install", + "--disable-pip-version-check", + ] + if extra is None: + install.append("--no-deps") + install.append(install_target) + _run_checked( + install, + cwd=outside_checkout, + env=clean_env, + label=label, + ) + _run_checked( + [str(python), "-m", "pip", "check"], + cwd=outside_checkout, + env=clean_env, + label=label, + ) + + smoke = API_SMOKE + if extra is not None: + smoke = f"{OPTIONAL_IMPORTS}\n{smoke}" + _run_checked( + [str(python), "-c", smoke], + cwd=outside_checkout, + env=clean_env, + label=label, + ) + + print(f"Validated clean {label} installation from {wheel.name}.") + + def main() -> None: """Validate wheel and sdist artifacts found in a distribution directory.""" parser = argparse.ArgumentParser() parser.add_argument("dist_dir", type=Path, nargs="?", default=Path("dist")) + parser.add_argument( + "--check-installs", + action="store_true", + help=( + "install the built wheel as base, notebook, and all variants in " + "separate clean virtual environments" + ), + ) args = parser.parse_args() dist_dir = args.dist_dir @@ -285,6 +604,13 @@ def main() -> None: check_wheel(wheel) for sdist in sdists: check_sdist(sdist) + if args.check_installs: + if len(wheels) != 1: + raise DistCheckError( + "clean-install checks require exactly one wheel in the " + "distribution directory" + ) + check_clean_installations(wheels[0]) if __name__ == "__main__": diff --git a/tools/release_check.py b/tools/release_check.py index 5aa2716..ea45d36 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -4,7 +4,7 @@ This helper is intended for local release preparation. It runs the same checks that are exercised separately in CI, then builds source and wheel artifacts, validates them, and smoke-tests the built wheel in an isolated virtual -environment. +environment for each supported user installation. """ from __future__ import annotations @@ -14,8 +14,8 @@ import shutil import subprocess import sys +import tarfile import tempfile -import venv REPO_ROOT = Path(__file__).resolve().parents[1] @@ -23,11 +23,11 @@ BUILD_DIR = REPO_ROOT / "build" -def _run(*args: str, env: dict[str, str] | None = None) -> None: - """Run one subprocess command in the repository root.""" +def _run(*args: str, cwd: Path = REPO_ROOT) -> None: + """Run one subprocess command in the selected working directory.""" - print("+", " ".join(args)) - subprocess.run(args, cwd=REPO_ROOT, check=True, env=env) + print(f"+ [cwd={cwd}]", " ".join(args)) + subprocess.run(args, cwd=cwd, check=True) def _fresh_build_dirs() -> None: @@ -37,57 +37,66 @@ def _fresh_build_dirs() -> None: shutil.rmtree(BUILD_DIR, ignore_errors=True) -def _smoke_test_wheel() -> None: - """Install the built wheel into a temporary virtualenv and import it.""" +def _assert_clean_worktree() -> None: + """Require artifacts to be built from the exact committed source tree.""" - wheels = sorted(DIST_DIR.glob("*.whl")) - if not wheels: - raise RuntimeError("no wheel found in dist/") - wheel = wheels[-1] + status = subprocess.run( + ["git", "status", "--porcelain=v1", "--untracked-files=all"], + cwd=REPO_ROOT, + check=True, + capture_output=True, + text=True, + ).stdout + if status: + raise RuntimeError( + "release artifacts must be built from a clean committed worktree" + ) + + +def _normalize_source_modes(source_root: Path) -> None: + """Set conventional modes in a disposable source-archive extraction.""" + + source_root.chmod(0o755) + for path in source_root.rglob("*"): + if path.is_symlink(): + continue + if path.is_dir(): + path.chmod(0o755) + elif path.is_file(): + path.chmod(0o644) - with tempfile.TemporaryDirectory(prefix="atomref-release-check-") as tmp: - env_dir = Path(tmp) / "venv" - builder = venv.EnvBuilder(with_pip=True) - builder.create(env_dir) - bindir = "Scripts" if sys.platform.startswith("win") else "bin" - python = env_dir / bindir / "python" - _run(str(python), "-m", "pip", "install", "--no-deps", str(wheel)) + +def _build_from_committed_head() -> None: + """Build artifacts from a normalized temporary extraction of ``HEAD``.""" + + _assert_clean_worktree() + with tempfile.TemporaryDirectory(prefix="atomref-release-source-") as tmp: + temporary_root = Path(tmp) + archive = temporary_root / "atomref-head.tar" + source_root = temporary_root / "source" + source_root.mkdir() + _run( + "git", + "archive", + "--format=tar", + f"--output={archive}", + "HEAD", + ) + with tarfile.open(archive, mode="r:") as source_archive: + source_archive.extractall(source_root) + _normalize_source_modes(source_root) _run( - str(python), - "-c", - ( - "import atomref as ar; " - "assert ar.get_covalent_radius('C') == 0.76; " - "assert ar.get_vdw_radius('C') == 1.77; " - "assert 'atomic_radius' in ar.list_quantities(); " - "assert 'rahm2016' in ar.list_dataset_ids(" - "'atomic_radius', usage_role='support'); " - "ref = ar.DatasetRef(" - "'proatomic_density', " - "'pbe0_sfx2c_dyallv4z_h-lr_neutral_v2'); " - "dataset = ar.get_builtin_set(ref); " - "assert dataset.get('O') is not None; " - "rho = ar.get_proatomic_density(" - "'O', 0.75, radius_unit='angstrom', " - "density_unit='electron/bohr^3'); " - "assert rho is not None and rho > 0; " - "boundary = ar.estimate_proatomic_boundary(" - "'C', 'O', 1.43); " - "assert boundary is not None; " - "assert boundary.position_from_a is not None; " - "minimum = ar.estimate_promolecular_density_minimum(" - "'C', 'O', 1.43); " - "assert minimum is not None; " - "assert minimum.requested_mode == 'minimum'; " - "selected = ar.estimate_ias_position(" - "'C', 'O', 1.43, mode='boundary'); " - "assert selected == boundary" - ), + sys.executable, + "-m", + "build", + "--outdir", + str(DIST_DIR), + cwd=source_root, ) def main() -> int: - """Run lint, tests, docs, build, metadata, and wheel smoke checks.""" + """Run lint, tests, docs, build, metadata, and artifact checks.""" parser = argparse.ArgumentParser( description="Run the full release-preparation checks for the repository.", @@ -98,12 +107,13 @@ def main() -> int: help="skip the strict MkDocs build step", ) parser.add_argument( - "--skip-smoke-test", + "--skip-install-checks", action="store_true", - help="skip the temporary-virtualenv wheel import smoke test", + help="skip clean base, notebook, and all wheel installation checks", ) args = parser.parse_args() + _assert_clean_worktree() _run("flake8", "src", "tests", "tools") _run(sys.executable, "tools/check_registry.py") _run(sys.executable, "tools/check_notebooks.py") @@ -113,11 +123,13 @@ def main() -> int: _run("mkdocs", "build", "--strict") _fresh_build_dirs() - _run(sys.executable, "-m", "build") + _build_from_committed_head() _run(sys.executable, "-m", "twine", "check", "dist/*") - _run(sys.executable, "tools/check_dist.py", "dist") - if not args.skip_smoke_test: - _smoke_test_wheel() + dist_check = [sys.executable, "tools/check_dist.py", "dist"] + if not args.skip_install_checks: + dist_check.append("--check-installs") + _run(*dist_check) + _assert_clean_worktree() return 0 From 5c25a8cfc98fb1e54ba416f5a1bcdf37af281eaf Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 02:38:23 +0300 Subject: [PATCH 15/23] docs: update dev plan --- DEV_PLAN.md | 3398 +++------------------------------------------------ 1 file changed, 138 insertions(+), 3260 deletions(-) diff --git a/DEV_PLAN.md b/DEV_PLAN.md index 2d4cb63..9210ea3 100644 --- a/DEV_PLAN.md +++ b/DEV_PLAN.md @@ -1,3260 +1,138 @@ -# atomref 0.2.x development plan - -> **Status:** Stage 6 implementation complete — `0.2.1` release candidate awaiting external review -> **Plan lifecycle state:** `COMPLETE_AWAITING_REVIEW` -> **Accepted release:** `atomref 0.2.0` — tagged without rewriting accepted history -> **Review candidate:** `atomref 0.2.1` — direct notebook documentation, complete API reference, adoption-oriented README, packaging, and release hardening -> **Source data:** `atomref-proatoms 2.0.0`, dataset `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2` - -## 1. Purpose and lifecycle of this file - -This root-level `DEV_PLAN.md` is the authoritative implementation plan for the -`0.2.x` development cycle. It is written for a coding agent working in the -repository, but it should remain understandable to a human reviewer. - -The plan has three scopes: - -1. **`0.2.0` binding implementation work** — generic packaged-dataset loading, - neutral radial density data, interpolation, two-mode pairwise - boundary/IAS-proxy analysis, tests, and executed explanatory notebooks. -2. **`0.2.1` planned documentation work** — direct rendering of saved executed - notebooks, richer notebook explanations, complete docstring-based API - reference, and a marketing-oriented README rewrite. -3. **Future reference only** — ions, additional properties, vectorized - evaluation, and three-dimensional grid-density generation. These items are - recorded to protect future compatibility but MUST NOT be implemented during - `0.2.0` or `0.2.1` unless the repository owner explicitly changes the scope. - -Use this file to: - -- understand the scientific and product contract before changing code; -- implement the work in the stated order; -- record completed stages and justified deviations; -- avoid adding abstractions, files, or process machinery that the project does - not need; -- preserve a clear distinction between current work and future ideas; -- verify that each release is complete. - -Do **not** copy this whole file into the public documentation. During `0.2.1`, -move only durable user-facing or architectural information into the existing -documentation. The complete execution plan should remain at the repository root -until the planned cycle is finished. - -The current duplicate page at `docs/dev/dev_plan.md` should be removed in -`0.2.1`, together with its MkDocs navigation entry. There should ultimately be -only one development plan. - -After `0.2.1` is released, this file may be: - -- replaced by the next small roadmap; -- shortened to unresolved future work; -- or removed after the changelog and release history preserve completed work. - -### 1.1 Plan authority, terminal states, and external review - -This file is the central control document for LLM-driven implementation of the -work described here. The implementation agent may update checkboxes, stage -results, deviations, and closeout records, but it MUST NOT silently extend the -scope or turn future-reference items into active work. - -The plan has four lifecycle states: - -```text -ACTIVE -COMPLETE_AWAITING_REVIEW -REPLAN_REQUIRED -CLOSED -``` - -Their meanings are: - -- **`ACTIVE`** — the accepted plan authorizes implementation of the current - stage. -- **`COMPLETE_AWAITING_REVIEW`** — the implementation agent believes every - binding requirement and acceptance criterion has been completed. It has - filled the closeout record and stopped further feature development pending an - independent review. -- **`REPLAN_REQUIRED`** — implementation cannot safely continue under the - current plan because of an unexpected scientific, architectural, data, - licensing, tooling, or repository constraint. The agent has documented the - blocker and stopped rather than inventing a replacement plan. -- **`CLOSED`** — an external reviewer has checked the implementation or the - blocker report and formally closed this plan as completed, superseded, or - abandoned. - -An **external reviewer** means the repository owner, another human reviewer, or -a separate planning/review agent that is not simply the implementation agent -continuing its own task without an independent review step. - -When all planned work appears complete, the implementation agent MUST: - -1. run every applicable release and acceptance check; -2. complete the closeout record in Section 20; -3. change the lifecycle state to `COMPLETE_AWAITING_REVIEW`; -4. state exactly what was implemented, tested, deferred, or intentionally - omitted; -5. stop adding functionality until an external reviewer approves or requests - corrections. - -When the plan becomes unexecutable, the implementation agent MUST: - -1. stop at the smallest safe repository state; -2. preserve or clearly identify all partial changes; -3. record what happened, why it blocks the plan, evidence gathered, approaches - attempted, and which decisions require replanning; -4. state whether the partial branch should be retained, reverted, or used as a - basis for a new plan; -5. change the lifecycle state to `REPLAN_REQUIRED`; -6. stop implementation until an external reviewer accepts a revised plan. - -After the state is `COMPLETE_AWAITING_REVIEW`, `REPLAN_REQUIRED`, or `CLOSED`, -an implementation agent asked to “continue development” MUST explain that the -current plan no longer authorizes additional coding. A new or revised plan must -be prepared and externally reviewed first. The agent MAY help draft that plan in -a separate planning task when asked, but MUST NOT resume implementation until -the new plan is accepted and marked `ACTIVE`. - -## 2. Normative language and implementation freedom - -The words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** express -the intended strength of a requirement. - -This plan separates decisions into three classes. - -### 2.1 Binding contract - -A binding contract defines scientific meaning, public behavior, provenance, -units, supported range, compatibility, or release acceptance. It MUST be -preserved unless the repository owner explicitly changes the plan. - -Examples: - -- neutral H–Lr coverage; -- the 20-bohr public radius limit; -- no density correlation or element substitution; -- no runtime dependency on `atomref-proatoms`; -- reproducible source verification; -- explicit units and failures outside the supported domain; -- keeping the stable proatomic-boundary mode distinct from the optional - promolecular-minimum mode, with the cutoff and practical resolution explicit. - -### 2.2 Preferred first design - -A preferred design is the expected starting point for implementation. The agent -MAY replace it with a materially clearer, smaller, safer, or faster design when -all binding behavior and tests remain intact. - -Any deviation MUST be reported with: - -1. the original proposed design; -2. the observed problem; -3. the replacement; -4. why the replacement is better; -5. which tests protect the same contract. - -Examples: - -- exact class and function names; -- whether two closely related result classes are combined; -- precise internal CSV column names; -- exact practical valley-refinement implementation; -- whether density and IAS implementation share one module or use two modules. - -### 2.3 Open decision - -An open decision is intentionally left for focused design work during the -relevant stage. It MUST be resolved explicitly before implementation of the -dependent behavior. - -The earlier open decision about exhaustive IAS local-minimum enumeration is -resolved by Sections 10 and 11. `0.2.0` uses two explicit modes: a stable -proatomic-boundary mode and a resolution-limited promolecular-minimum mode. -Final public spelling may still be adjusted during Stage 4B, but the scientific -meaning, cutoff policy, default mode, and invariants are binding. - -## 3. Agent operating rules - -### 3.1 Keep the repository small - -Before adding any new file, abstraction, schema, public class, or tool, ask: - -> Considering the probable maximum future scope—ionic variants of some -> properties, a small number of additional atomic properties, and optional -> spatial sampling of radial densities—does the project genuinely need this -> object or file? - -Prefer: - -- one generic packaged-dataset dispatcher over separate unrelated loaders; -- the existing registry over a second metadata registry; -- one feature module over several one-purpose internal modules; -- one maintained notebook execution/checking tool over parallel exporters and - validators; -- direct notebook rendering over generated duplicate Markdown copies; -- existing release and validation tools extended in place; -- straightforward dataclasses, protocols, unions, and functions over a - framework. - -Do not add: - -- `AGENTS.md`; -- architecture-decision-record directories; -- a second development-plan copy; -- a plugin system; -- a general multidimensional scientific-data framework; -- a generic atomic-state selection engine; -- a new runtime dependency merely to simplify a small numerical routine; -- separate documentation pages when one existing page or one combined new page - is clearer; -- generated copies of notebooks when MkDocs can render the actual notebook. - -A maintainer-only script in `tools/` is justified when it makes a committed -scientific artifact reproducible or verifiable. Such a tool is not a user-facing -package feature and MUST NOT be presented as one. - -### 3.2 Protect the working scalar policy core - -The existing scalar registry metadata, policy, transfer, radii, and X–H behavior -is working and well tested. - -The implementation MUST NOT: - -- apply `ValuePolicy` to radial values; -- apply `SubstitutionTransfer` or `LinearTransfer` to radial profiles; -- change existing default radii or X–H results; -- break public `0.1.x` APIs; -- perform speculative cleanup outside the affected code. - -However, the packaged-dataset loader itself MUST be generalized so that -`get_builtin_set()` and the dataset-centered discovery machinery work for both -scalar and radial packaged sets. This is a focused architecture change required -by the new feature, not an unrelated policy rewrite. - -Policy code may receive small type-narrowing or helper changes so that it -explicitly requests scalar datasets from the now-generic loader. Its scientific -behavior must remain unchanged. - -### 3.3 Preserve project conventions - -- Runtime code remains pure Python with no required third-party dependencies. -- Optional notebook/documentation dependencies do not become runtime - dependencies. -- Runtime access performs no network requests. -- `README.md` remains generated from `docs/index.md`; do not edit the generated - README as the source of truth. -- Generated profile data MUST NOT be hand-edited. -- Public objects remain typed and compatible with the shipped `py.typed`. -- Package data MUST be validated in both wheel and source distribution. -- Existing commands and CI checks remain operational until deliberately - replaced by a smaller equivalent in `0.2.1`. -- New public names MUST be added deliberately to `atomref.__all__` and the - public-API test. -- New behavior MUST have tests before the stage is considered complete. -- Notebooks committed as release examples MUST contain saved execution counts - and outputs. - -### 3.4 Work stage by stage - -Complete, test, and report each stage before proceeding. - -At the end of each stage, provide: - -- a concise change summary; -- files added, removed, or substantially changed; -- commands run and their results; -- any deviation from this plan; -- unresolved questions; -- the suggested conventional commit message; -- the next stage. - -Do not combine unrelated stages into one unreviewable change. - -Do not begin the `0.2.1` documentation overhaul until the repository owner has -accepted the `0.2.0` functionality and release state. - -## 4. Product goal - -`atomref 0.2.0` should establish the package as a small source of **reference -atoms for structure algorithms**, not only a radii lookup package. - -The release should support these workflows: - -```python -import atomref as ar - -rho = ar.get_proatomic_density("O", 0.75) - -boundary = ar.estimate_proatomic_boundary("C", "O", 1.43) -minimum = ar.estimate_promolecular_density_minimum("C", "O", 1.43) - -# One convenience entry point with an explicit policy choice. -selected = ar.estimate_ias_position("C", "O", 1.43, mode="boundary") -``` - -The density call evaluates a frozen neutral spherical proatomic density. - -The pairwise helpers expose two related but scientifically different -approximations: - -- **`boundary` mode** returns a stable neutral-proatom divider. It uses exact - homonuclear symmetry, equal proatomic contributions in the meaningful-overlap - region, and the midpoint between fixed low-density contours when the contours - no longer overlap. -- **`minimum` mode** searches for one practically resolved minimum of the summed - promolecular line density inside the meaningful-overlap region. This is the - more Bader-oriented proxy, but it is optional, resolution-limited, and may - report ambiguity or no resolved minimum. - -The release is successful when a quantum-chemistry, crystallographic, or -molecular-software developer can immediately understand: - -- what the supplied profiles represent and where they came from; -- how to evaluate them; -- the difference between the two pairwise modes; -- why `boundary` is the default; -- where the fixed tail cutoff enters; -- what numerical domain is supported; -- what each result does and does not claim physically. - -## 5. Release scope - -### 5.1 `0.2.0` included work - -`0.2.0` includes: - -- a generic `get_builtin_set()` dispatcher that loads every packaged dataset - through the same dataset-centered machinery; -- one packaged neutral H–Lr spherical proatomic-density dataset; -- reproducible construction from the immutable `atomref-proatoms 2.0.0` - release; -- profile access by element symbol or atomic number through existing element - normalization; -- scalar radial-density evaluation; -- independent radius-coordinate and density-output unit selection; -- a strict public radius domain of `0 <= r <= 20 bohr`; -- positive-region log–log interpolation; -- a cached fixed neutral-proatom tail radius for every packaged element at - `1e-4 electron/bohr^3`; -- a stable pairwise proatomic-boundary estimator; -- an optional, cutoff-bounded and resolution-limited promolecular-density - minimum estimator; -- a convenience dispatcher with an explicit `mode` and default - `mode="boundary"`; -- exact homonuclear midpoint behavior in both modes; -- explicit low-density-gap, one-atom-dominance, boundary-dominated, - no-resolved-minimum, and competing-minimum diagnostics; -- pair-reversal and unit-invariance guarantees; -- explicit provenance, method, cutoff, units, limitations, and data licensing; -- an executed supporting notebook explaining the Stage 4 method decision; -- one executed feature notebook for proatomic density and pairwise workflows, - including explanatory Markdown cells and plots; -- minimum accurate user/API documentation needed to release the functionality; -- tests, packaging checks, and release metadata. - -### 5.2 `0.2.1` planned work - -`0.2.1` is a documentation and discoverability release. It includes: - -- replacing transient notebook execution with reproducible execution that saves - and verifies notebook outputs; -- executing and saving all shipped notebooks; -- adding short explanatory Markdown before each logical code section; -- directly rendering `.ipynb` files in MkDocs, including plots and rich output; -- removing generated notebook Markdown copies and their exporter when direct - rendering is accepted; -- configuring `mkdocstrings` to show complete typed signatures and structured - Parameters, Returns, Raises, Attributes, and Examples sections; -- upgrading all public docstrings to a consistent structured style; -- reviewing API-page member selection so every intended public type and - parameter is documented; -- substantially improving the README/home page as a product introduction; -- removing the duplicate documentation copy of the development plan; -- simplifying notebook/documentation CI after the new workflow is stable. - -`0.2.1` MUST NOT change density, cutoff radii, pairwise mode semantics, or -selected coordinates except to fix a confirmed defect with an explicit -changelog entry and regression test. - -### 5.3 Excluded from `0.2.0` and `0.2.1` - -These releases do not include: - -- ionic profile selection; -- fractional-charge interpolation; -- atom typing or environment-dependent profiles; -- correlations between profile datasets; -- element substitution for missing profiles; -- reconstruction of one basis-set profile from another; -- user-defined radial profile datasets; -- vectorized NumPy density APIs; -- three-dimensional density-grid generation; -- periodic image summation; -- arbitrary interpolation modes; -- far-tail extrapolation; -- profile evaluation beyond 20 bohr; -- molecular electron densities; -- exact QTAIM or other rigorous molecular-density boundaries; -- an automatic Hirshfeld or Hirshfeld-I cycle; -- a generalized arbitrary-rank data registry; -- new required runtime dependencies. - -### 5.4 Versions - -The primary feature release is **`0.2.0`**, not `0.1.5`. - -It adds: - -- a new scientific data family; -- a new radial payload shape; -- generalized packaged-dataset dispatch; -- new numerical semantics; -- new public APIs; -- a derived pairwise analysis. - -The documentation and discoverability work is **`0.2.1`** because it follows -and documents a stabilized `0.2.0` API rather than forcing the scientific -implementation and the complete presentation overhaul into one review. - -A later grid-density or ion/state release should receive its own version after -separate design review. Do not assign that version in advance. - -## 6. Scientific data contract - -### 6.1 Upstream source - -The packaged profiles MUST be derived from: - -```text -project: - atomref-proatoms - -release: - 2.0.0 - -version-specific DOI: - 10.5281/zenodo.21291022 - -concept DOI: - 10.5281/zenodo.21291021 - -upstream dataset_id: - pbe0_sfx2c_dyallv4z_h-lr_spherical_v2 -``` - -Pinned source files: - -```text -profiles.csv -sha256: - b5520ab009542d52098dd6dbb920966d8d13377a4a5004f584a7bd15cd41c299 - -metadata.json -sha256: - 32c833ca69fa0f7eb9ed32841aafc638123ff872861e636156610e417fc4c514 - -basis_id: - dyall-v4z - -basis_sha256: - 0ee543855f8b1e7fbe9868d4abb844d8e8cc8b8c2694067b2b40de014bb4be94 -``` - -The snapshot builder MUST reject a source whose pinned hashes, dataset identity, data -version, basis identity, or basis checksum do not match. - -### 6.2 Scientific identity - -The consumer metadata MUST preserve at least: - -```text -profile data version: - 2.0.0 - -electronic method: - PBE0 - -SCF model: - self-consistent spherical fractional-occupation UKS - -relativity: - spin-free one-electron X2C - -basis: - dyall-v4z - -native radius unit: - bohr - -native density unit: - electron / bohr^3 - -charge scope: - neutral atoms only - -element coverage: - H through Lr, Z = 1..103 -``` - -The public documentation MUST explain that these are method-, basis-, state-, -and sphericalization-defined reference densities. They are not unique -basis-independent atomic observables. - -### 6.3 Neutral-state selection - -The snapshot builder MUST use upstream metadata, not column-name guessing alone, to -select profiles. - -Required selection: - -```text -charge == 0 -exactly one selected state for every Z from 1 through 103 -``` - -The snapshot builder MUST fail when: - -- an element is missing; -- more than one neutral state is selected for an element; -- a selected column is missing from the CSV; -- the selected metadata and column metadata disagree. - -The consumer data does not need to duplicate configuration, multiplicity, -occupation policy, state labels, SCF artifact paths, or the full upstream -metadata. - -The package metadata should state that detailed state and generation metadata -remain available in the exact `atomref-proatoms 2.0.0` archive. - -### 6.4 Radius truncation and bracketing - -The public validity domain is: - -```text -0 <= r <= 20 bohr -``` - -The upstream logarithmic grid does not contain exactly 20 bohr. The snapshot -SHOULD retain source rows through the first source point above 20 bohr so the -public endpoint remains bracketed by original data. - -For the pinned source, the expected facts are: - -```text -last source point below 20 bohr: - 19.865456344881434 bohr - -first source point above 20 bohr: - 20.1644204667093 bohr - -number of retained rows including the bracketing point: - 1127 -``` - -The point above 20 bohr is an internal interpolation bracket. It does not expand -the public validity domain. - -The snapshot validation MUST verify that all selected densities are finite and -strictly positive throughout the retained range. Thus the consumer package -never encounters the upstream far-tail zeros during valid use. - -### 6.5 Why the public limit is 20 bohr - -The 20-bohr boundary is a product decision, not a statement that the upstream -60-bohr data are invalid. - -The intended use is empirical or semi-empirical structure analysis, often on -experimental structures. At very remote distances: - -- absolute densities are extremely small; -- tail behavior is increasingly sensitive to computational details; -- small structural or numerical changes can dominate the value; -- the data no longer add useful information for the intended IAS helper. - -The limit also provides a simple numerical contract: - -- no tail extrapolation; -- no numerical underflow zeros in the packaged domain; -- no silent zero-fill behavior; -- no ambiguity about whether an out-of-range value is physical or artificial. - -### 6.6 Packaged representation - -The consumer representation is one deterministic single-member ZIP archive in -the existing package data directory: - -```text -src/atomref/data/proatomic_density_neutral.zip -``` - -The archive MUST contain exactly one regular file named: - -```text -proatomic_density_neutral.csv -``` - -It remains a container for a plain wide CSV, not a binary scientific-data -format. - -Preferred logical structure: - -```text -r_bohr,z001,z002,...,z103 -``` - -or another equally simple deterministic Z-keyed header. - -Binding requirements: - -- one shared radius column; -- one density column per Z from 1 through 103; -- columns ordered by increasing Z; -- exact source decimal values retained where practical; -- deterministic CSV bytes, row ordering, column ordering, and LF line endings; -- exactly one archive member named `proatomic_density_neutral.csv`; -- no directory entries or additional archive members; -- fixed member timestamp `(1980, 1, 1, 0, 0, 0)`; -- normalized Unix platform metadata (`create_system = 3`) and regular-file - mode metadata (`external_attr = 0o100644 << 16`, `internal_attr = 0`); -- empty archive and member comments; -- no uncontrolled ZIP extra fields; -- fixed `ZIP_DEFLATED` compression with explicit compression level 9; -- no duplicated symbol columns; -- no ionic or configuration data; -- no second large metadata file. - -The element symbol mapping MUST come from the existing element registry. - -All interpretive metadata SHOULD live in the existing `registry.json`, with -license/citation text also added to `NOTICE.md` and public documentation. - -This owner-approved ZIP representation is binding for the `0.2.x` consumer -snapshot. Do not substitute another container or introduce a binary scientific -data format. - -### 6.7 Licensing and attribution - -The `atomref` code remains under its existing LGPL licensing. - -The imported `atomref-proatoms` data are CC BY 4.0. The release MUST: - -- identify the data license in registry metadata; -- update `NOTICE.md`; -- cite the concept and version-specific DOI; -- name the exact upstream dataset; -- state that the data are a neutral, truncated consumer snapshot; -- ensure the attribution is present in built distributions through packaged - metadata and/or configured license/notice files; -- test that the required attribution metadata is shipped. - -Do not silently present the data as covered only by the code license. - -## 7. Registry and architecture contract - -### 7.1 All packaged datasets use the same machinery - -Every packaged dataset MUST participate in the existing dataset-centered -operations: - -```python -list_quantities() -get_quantity_info(...) -list_dataset_ids(...) -list_dataset_infos(...) -get_dataset_info(...) -get_builtin_set(...) -``` - -`get_builtin_set(DatasetRef(...))` MUST load both existing scalar datasets and -the new radial dataset. - -A separate public loader that bypasses `get_builtin_set()` is not an acceptable -final architecture for `0.2.0`. - -The current scalar-only implementation is not a fundamental blocker. It -currently assumes: - -```text -element-domain + dense-by-Z CSV + one scalar column -``` - -The focused refactor should instead dispatch from explicit storage metadata. - -Preferred registry storage identifiers: - -```text -element_scalar_csv -element_radial_csv_zip -``` - -or an equivalent pair of clear stable identifiers. - -The registry remains the single source of truth for: - -- quantity and set identity; -- aliases; -- coverage; -- scientific metadata; -- storage kind, filename, and archive member where applicable; -- provenance and references; -- loading dispatch. - -### 7.2 Preferred loaded-set model - -Preferred public/internal types: - -```python -ElementScalarSet -ElementRadialSet -BuiltinSet = ElementScalarSet | ElementRadialSet -``` - -`ElementRadialSet` may be named `ProatomicDensitySet` if a truly generic radial -type would add abstraction without present value. The important contract is -that the object returned by `get_builtin_set()` is the actual loaded packaged -set and carries: - -- `ref`; -- `info`; -- element coverage; -- access to an immutable profile by symbol/Z; -- shared grid and profile data; -- no policy behavior. - -The exact union/type-alias placement is a preferred design. - -### 7.3 Scalar policy type narrowing - -The scalar policy core must continue to accept scalar datasets only. - -The current internal names: - -```python -DatasetLike -resolve_dataset_like(...) -``` - -are scalar in behavior despite generic names. During the focused refactor, -prefer clearer names such as: - -```python -ScalarDatasetLike -resolve_scalar_dataset_like(...) -``` - -These are internal and can be renamed without public breakage. - -Alternative: retain the old internal name but add an explicit type guard that -raises a clear `DatasetError` when a radial set is supplied. The clearer rename -is preferred. - -`get_radii_set()` and `get_xh_set()` should continue to return -`ElementScalarSet` and must validate/narrow the generic `get_builtin_set()` -result. - -The policy engine, transfer fitting, fallback, and placeholder behavior must not -be generalized to functions. - -### 7.4 Failure contingency - -If implementation reveals a genuine blocker to generic `get_builtin_set()` -dispatch: - -1. stop before adding a bypass as permanent architecture; -2. document the concrete blocker and affected call graph; -3. implement the smallest reviewed temporary adapter only with owner approval; -4. add a named follow-up item to `0.2.1` or the next patch; -5. protect the temporary behavior with tests; -6. do not claim that dataset machinery is unified until it actually is. - -The expected outcome is still to complete the generic dispatcher in `0.2.0`. - -### 7.5 Minimal implementation structure - -Preferred new files for `0.2.0`: - -```text -src/atomref/proatoms.py -src/atomref/data/proatomic_density_neutral.zip -tools/build_proatomic_density_snapshot.py -tests/proatoms/test_dataset.py -tests/proatoms/test_density.py -tests/proatoms/test_ias.py -notebooks/04-ias-method-selection-study.ipynb -notebooks/05-proatomic-density-and-ias.ipynb -docs/dev/ias_method_selection.md -``` - -The exact test split is flexible. - -The generic radial set and storage dispatch may live in `registry.py`; density -evaluation and IAS analysis should initially live in `proatoms.py`. - -Do not add a separate: - -- `_density_data.py`; -- `ias.py`; -- profile-manifest JSON; -- schema directory; -- numerical-contract document; -- data-specific documentation directory; - -unless actual implementation evidence shows that the combined design is -unmanageable. - -### 7.6 Existing metadata classes - -`CoverageInfo` should be generalized in wording from “element-indexed scalar -dataset” to “element-indexed dataset.” Its fields are sufficient for H–Lr -coverage. - -`DatasetInfo.storage` is already a generic metadata mapping and should carry: - -- storage kind; -- filename; -- archive member name where applicable; -- radius column; -- density-column convention; -- native coordinate unit; -- native density unit; -- public maximum radius; -- retained bracketing radius; -- interpolation contract identifier; -- source project, release, dataset ID, hashes, and license. - -Avoid adding fields to public metadata dataclasses unless the information cannot -be represented clearly in the existing structure. - -### 7.7 Caching - -`get_builtin_set()` is currently cached by `DatasetRef`. Preserve equivalent -caching for radial sets. - -The generic dispatcher may delegate to format-specific cached loaders, but -repeated calls for the same canonical dataset should return the same immutable -loaded-set object or equivalent shared cached data. - -## 8. Density API contract - -### 8.1 Public concept - -The API should use “proatomic density,” not the ambiguous abbreviation `ED`. - -Preferred public names: - -```python -DEFAULT_PROATOMIC_DENSITY_SET -ProatomicDensitySet -ProatomicDensityProfile - -list_proatomic_density_sets() -list_proatomic_density_set_infos() -get_proatomic_density_set() -get_proatomic_density_set_info() -get_proatomic_density_profile() -get_proatomic_density() -``` - -This list is a preferred first design. It MAY be reduced when an existing generic -registry helper already provides the same operation clearly. - -At minimum, the release MUST provide: - -```python -get_proatomic_density_profile(...) -get_proatomic_density(...) -``` - -and a clear way to inspect the exact dataset metadata. - -### 8.2 Preferred usage - -```python -import atomref as ar - -rho_o = ar.get_proatomic_density( - "O", - 0.75, - radius_unit="angstrom", - density_unit="electron/bohr^3", -) - -profile = ar.get_proatomic_density_profile("O") -rho_o_bohr = profile( - 1.5, - radius_unit="bohr", - density_unit="electron/bohr^3", -) -``` - -The profile object SHOULD be callable or expose one obvious evaluation method. - -### 8.3 Element handling - -The API MUST follow existing element behavior: - -- ordinary symbols are canonicalized; -- `D` and `T` use the hydrogen electronic profile; -- invalid symbols return `None`; -- elements beyond Lr return `None`; -- no neighboring-element or correlated fallback occurs. - -The canonical dataset map is keyed internally by atomic number. - -### 8.4 Scalar scope - -The initial public API evaluates one scalar radius at a time. - -It MUST NOT require NumPy and SHOULD NOT accept arbitrary iterables in `0.2.0`. - -Repeated evaluation should be supported efficiently through the cached profile -object. - -A future vectorized API may be added separately if real downstream profiling -shows it is useful. - -### 8.5 Units - -Radius coordinates and density values use independent unit parameters. - -Canonical radius units: - -```text -angstrom -bohr -``` - -Canonical density units: - -```text -electron/bohr^3 -electron/angstrom^3 -``` - -Preferred defaults: - -```text -radius_unit = "angstrom" -density_unit = "electron/bohr^3" -``` - -This reflects common structural input conventions while preserving the familiar -atomic-density scale used for values such as approximately `0.003` or `0.001` -electron/bohr³. - -Preferred density call: - -```python -rho = get_proatomic_density( - "O", - 0.75, - radius_unit="angstrom", - density_unit="electron/bohr^3", -) -``` - -The profile object should use the same independent arguments. - -Short aliases such as `e/bohr^3` MAY be accepted, but documentation and result -metadata should emit one canonical form. - -The implementation MUST use named, tested conversion constants. Do not scatter -conversion literals through the code. - -Changing `density_unit` must change only reported density values. It must not -change: - -- selected interpolation interval; -- cutoff radii or contour-overlap geometry; -- selected pairwise mode or method; -- returned coordinate; -- resolved-minimum identity; -- ambiguity, domination, or low-density-gap status. - -### 8.6 Error and missing-data behavior - -Binding behavior: - -| Situation | Result | -|---|---| -| Invalid or unsupported element | `None` | -| Missing packaged dataset | `DatasetError` | -| Unknown radius or density unit | `ValueError` | -| Negative radius | `ValueError` | -| Non-finite radius | `ValueError` | -| Radius above 20 bohr after unit conversion | `ValueError` | -| Valid radius and supported element | finite positive `float` | - -This follows the package distinction between: - -- absent scientific data: return `None`; -- malformed or out-of-contract request: raise. - -### 8.7 Lazy loading and caching - -The compressed data MUST load lazily. - -Repeated profile retrieval or evaluation MUST NOT repeatedly parse the file. - -The implementation SHOULD cache: - -- the loaded shared radius grid; -- the Z-indexed profile table or immutable profile views; -- dataset metadata resolution. - -The public API MUST not expose mutable references to cached shared data. - -## 9. Interpolation contract - -### 9.1 Supported numerical region - -The packaged retained interval contains strictly positive density values for all -103 profiles. - -Therefore, valid public interpolation does not need a zero-density branch. - -If a future imported snapshot contains a nonpositive value within the retained -bracketing region, import or load validation MUST fail rather than silently -changing interpolation behavior. - -### 9.2 Contract identifier - -Registry metadata and result provenance SHOULD use a stable identifier such as: - -```text -loglog_positive_bracketed_v1 -``` - -The exact string may change before implementation, but it must be stable within -the release and documented. - -### 9.3 Evaluation rules - -For one profile with source knots `(r_i, rho_i)`: - -1. Reject non-finite and negative query radii. -2. For `0 <= r <= r_min`, return `rho(r_min)`. -3. At an exact stored radius, return the exact stored density. -4. Between two positive neighboring knots, interpolate linearly in - `log(r)` and `log(rho)`. -5. Allow evaluation at exactly 20 bohr using the retained bracketing point. -6. Reject values above 20 bohr. -7. Perform no tail extrapolation and no zero fill. -8. Return a finite positive scalar in the selected output units. - -The origin rule is a finite-grid convention. Documentation must not describe it -as an exact nuclear-density evaluation. - -### 9.4 Implementation preference - -Use only the standard library, for example: - -- `bisect` for interval location; -- `math.log` and `math.exp`; -- immutable tuples or similarly simple storage. - -The implementation SHOULD avoid rebuilding logarithm arrays on every call. -Precomputed log-radius and, if useful, log-density values may be cached. - -### 9.5 Relationship to the upstream interpolation issue - -An issue should be opened in `atomref-proatoms` to replace its global -linear-fallback behavior with interval-local handling for profiles that contain -far-tail zeros. - -`atomref 0.2.0` does not need to wait for an upstream release because: - -- it imports pinned v2.0.0 values; -- it restricts its domain to 20 bohr; -- all retained values are strictly positive; -- it implements its own small dependency-free consumer interpolation. - -The two projects should still document compatible positive-positive log–log -behavior. - -## 10. Pairwise proatomic boundary and IAS-proxy scientific contract - -### 10.1 Two related quantities, not one hidden policy - -For atoms A and B separated by distance `R`, define `x` from A toward B: - -```text -A ---- x ----> B -0 R -``` - -The component and summed neutral-promolecular line densities are: - -\[ -\rho_A(x),\qquad \rho_B(R-x),\qquad -f(x;R)=\rho_A(x)+\rho_B(R-x) -\] - -for `0 <= x <= R`. - -`0.2.0` deliberately exposes two different pairwise estimates: - -1. **Proatomic boundary** — a stable geometric divider based on equal neutral - proatomic contributions, with a fixed low-density contour fallback. -2. **Promolecular density minimum** — one practically resolved minimum of - `f(x;R)` inside a region where both proatomic contributions remain above the - fixed tail cutoff. - -They MUST NOT be presented as interchangeable definitions. The first is the -production default because it is unique, fast, and stable. The second is -available because a line-density minimum is closer in spirit to a -bond-critical-point coordinate and is useful for Bader-oriented calibration, -but it is a neutral-promolecular proxy rather than a molecular QTAIM result. - -### 10.2 Interpretation and naming - -Neither mode returns: - -- a QTAIM zero-flux surface; -- a molecular-density topological critical point; -- an environment-relaxed atomic boundary; -- an ionic, charged, or self-consistent stockholder boundary; -- proof of a bond or interaction. - -User-facing documentation SHOULD use: - -- “neutral-proatom boundary estimate” for `boundary` mode; -- “promolecular line-density minimum” or “promolecular IAS proxy” for - `minimum` mode; -- “pairwise IAS-position proxy” only for the mode-selecting convenience API. - -The phrase “most probable position” MUST NOT be used because no probability -model is defined. - -Scientific reference context: - -- molecular QTAIM basin boundaries are zero-flux surfaces of the molecular - density: R. F. W. Bader, *Chemical Reviews* **91** (1991), 893–928, - `doi:10.1021/cr00005a013`; -- the equal-proatom rule is a pairwise stockholder balance based on free-atom - reference densities: F. L. Hirshfeld, *Theoretica Chimica Acta* **44** - (1977), 129–138, `doi:10.1007/BF00549096`. - -These references motivate the interpretation; they do not turn either neutral- -proatom mode into a molecular QTAIM calculation. - -### 10.3 Public functions and default policy - -Preferred public API: - -```python -estimate_proatomic_boundary( - atom_a, - atom_b, - distance, - *, - distance_unit="angstrom", - density_unit="electron/bohr^3", - set_id=DEFAULT_PROATOMIC_DENSITY_SET, -) - -estimate_promolecular_density_minimum( - atom_a, - atom_b, - distance, - *, - distance_unit="angstrom", - density_unit="electron/bohr^3", - set_id=DEFAULT_PROATOMIC_DENSITY_SET, -) - -estimate_ias_position( - atom_a, - atom_b, - distance, - *, - mode="boundary", - distance_unit="angstrom", - density_unit="electron/bohr^3", - set_id=DEFAULT_PROATOMIC_DENSITY_SET, -) -``` - -Binding behavior: - -- `mode` accepts only `"boundary"` and `"minimum"` in `0.2.0`; -- the default is `"boundary"`; -- the dispatcher calls the same internal implementation as the corresponding - direct function; -- `minimum` mode MUST NOT silently fall back to `boundary` mode; -- callers can compare the two direct results without hidden policy changes. - -The exact public names remain a preferred first design. Any rename MUST preserve -these three roles and the explicit default. - -### 10.4 Input domain and units - -Binding input rules: - -- both elements must have profiles; -- distance must be finite and strictly positive; -- distance must not exceed 20 bohr after conversion; -- the result coordinate is measured from atom A toward atom B; -- all reported positions use `distance_unit`; -- component and summed densities use `density_unit`; -- D/T follow hydrogen behavior; -- missing element profiles return `None`; -- invalid distance, mode, or unit raises `ValueError`. - -All scientific decisions MUST be performed in bohr and electron/bohr³. Output -conversion occurs only after method selection, status classification, and -coordinate calculation. Changing `density_unit` MUST NOT change geometry, -method, status, or ambiguity. - -No universal lower distance such as `0.5 angstrom` is imposed. Extremely short -or dominated pairs are handled by explicit statuses instead of an arbitrary -ban. - -### 10.5 Fixed neutral-proatom tail cutoff - -The `0.2.0` numerical contract uses the fixed per-atom cutoff: - -\[ -\rho_c = 10^{-4}\ \mathrm{electron/bohr^3}. -\] - -For every profile, define the unique cutoff radius: - -\[ -\rho_A(r_A^c)=\rho_c, -\qquad -\rho_B(r_B^c)=\rho_c. -\] - -This is a model policy for ignoring weak neutral-proatom tails. It MUST NOT be -described as a universal lower bound for a chemically meaningful QTAIM bond -critical point. - -The cutoff is applied to each component separately. Therefore a gap where both -components are below the cutoff can have a summed density below -`2 * rho_c`, not necessarily below `rho_c`. - -The cutoff is fixed rather than user-configurable in `0.2.0`. It MUST be exposed -in result metadata and included in the pairwise numerical-contract identifier. - -### 10.6 Shared result information - -Use one small immutable result type for both direct functions and the dispatcher -unless implementation evidence strongly favors two types. - -Preferred first design: - -```python -@dataclass(frozen=True, slots=True) -class IASPositionResult: - atom_a: str - atom_b: str - distance: float - distance_unit: str - density_unit: str - requested_mode: str - method: str - status: str - position_from_a: float | None - position_from_b: float | None - fraction_from_a: float | None - rho_a: float | None - rho_b: float | None - rho_sum: float | None - cutoff_density: float - cutoff_radius_a: float - cutoff_radius_b: float - contour_separation: float - alternative_position_from_a: float | None - alternative_rho_sum: float | None - relative_depth_gap: float | None - search_resolution: float | None - search_converged: bool | None - dataset_id: str - interpolation_contract: str - pairwise_contract: str -``` - -The exact field list is preferred, not binding. The result MUST still expose: - -1. the returned coordinate or an explicit absence; -2. requested mode and actual method; -3. status; -4. component and summed density at a returned coordinate; -5. cutoff, cutoff radii, and signed contour separation; -6. enough minimum-search diagnostics to interpret ambiguity when `mode` is - `"minimum"`; -7. dataset, interpolation, and pairwise-contract provenance; -8. coordinate orientation. - -Do not return one unexplained float. - -### 10.7 Proatomic-boundary mode - -Let the signed contour separation be: - -\[ -d_c=R-r_A^c-r_B^c. -\] - -For a homonuclear pair, return exactly: - -\[ -x=R/2. -\] - -Use status `low_density_gap` when its two cutoff contours are separated; -otherwise use the ordinary successful status. The coordinate remains `R/2` in -either regime. - -For an unlike pair: - -- if `d_c > 0`, the cutoff contours do not overlap. Return the midpoint of the - two contour points: - - \[ - x=\frac{r_A^c+(R-r_B^c)}{2} - =\frac{R+r_A^c-r_B^c}{2}; - \] - - use method `cutoff_gap_midpoint` and status `low_density_gap`; -- if the contours overlap or touch, solve the unique monotone equation: - - \[ - \rho_A(x)=\rho_B(R-x); - \] - - use method `equal_proatom_density`; -- if an unlike atom dominates the entire interval and the equal-contribution - equation has no interior solution, return no coordinate with status - `one_atom_dominates`. - -At `R=r_A^c+r_B^c`, the contour midpoint and equal-contribution definitions meet -at the same coordinate. The implementation SHOULD classify this as -`cutoff_contact` or otherwise make contact visible without changing the -coordinate. - -### 10.8 Promolecular-minimum mode - -This mode searches only the meaningful-overlap interval where both neutral -proatomic contributions are at least the cutoff: - -\[ -I_c= -\left[ -\max(0,R-r_B^c), -\min(R,r_A^c) -\right]. -\] - -Binding behavior: - -- for an unlike pair, if `I_c` is empty, return no minimum coordinate with - status `low_density_gap`; -- for a homonuclear pair, return exactly `R/2` by symmetry, even if `I_c` is - empty or the raw interpolant contains slightly lower symmetric off-center - shell minima; retain `low_density_gap` as the status when applicable; -- for unlike pairs, find one **practically resolved** interior minimum of - `f(x;R)` under Section 11’s fixed spatial-resolution contract; -- do not enumerate or expose every mathematical microminimum; -- retain at most the selected resolved valley and one materially competitive - alternative; -- if no interior valley is resolved, return no coordinate with status - `no_resolved_interior_minimum`; -- if a boundary value is lower than the selected interior valley, retain the - candidate as a diagnostic but use status `boundary_dominated`; -- if two separated resolved valleys are practically competitive, return the - deeper one and expose the runner-up and relative depth gap with status - `ambiguous_competing_minima`; -- if the adaptive passes do not stabilize, return the finest-pass candidate - with status `search_unstable` and `search_converged=False`. - -The result MUST identify a homonuclear return as `homonuclear_midpoint`, not -mislabel it as a numerically discovered minimum when the midpoint fails the -resolved-valley check. - -The following concepts from the superseded all-minima design are intentionally -absent from `0.2.0`: - -- exhaustive local-minimum lists; -- exact global-tie enumeration; -- basin widths and watershed clipping; -- weighted averages of separate minima; -- high-precision reconstruction of sub-ULP event topology. - -### 10.9 Why both modes are retained - -`boundary` mode is the recommended production choice for geometry and weighted -Voronoi/Laguerre workflows because it is unique, continuous at cutoff contact, -symmetry-preserving, and inexpensive. - -`minimum` mode is retained for Bader-oriented research and calibration because a -line-density minimum is a more direct promolecular analogue of an internuclear -critical-point coordinate. Its optional status and diagnostics make the model -limitations visible instead of hiding them behind an averaged coordinate. - -Documentation SHOULD demonstrate both modes on the same pairs and MUST explain -that disagreement between them is a scientific-model difference, not -necessarily a numerical error. - -### 10.10 Pair reversal and homonuclear symmetry - -For a result on `(A, B, R)` and the reversed result `(B, A, R)`: - -- a returned position `x` corresponds to `R-x`; -- method and status remain equivalent after A/B relabeling; -- component densities swap; -- summed density, cutoff regime, search convergence, relative depth gap, and - practical ambiguity remain unchanged; -- an alternative minimum, when present, also transforms to `R-x_alt`; -- `one_atom_dominates` identifies the correspondingly relabeled atom. - -For every homonuclear pair and every valid distance, both public modes return -exactly `R/2` after output-unit conversion within the ordinary conversion -rounding. - -Canonical internal orientation MAY be used to make reversal invariance -structural. - -### 10.11 Status and failure semantics - -The exact vocabulary may be adjusted, but the result model MUST distinguish at -least: - -```text -ok -low_density_gap -one_atom_dominates -no_resolved_interior_minimum -boundary_dominated -ambiguous_competing_minima -search_unstable -``` - -The method field MUST distinguish at least: - -```text -homonuclear_midpoint -equal_proatom_density -cutoff_gap_midpoint -promolecular_density_minimum -none -``` - -Missing profiles return `None`. A scientifically non-applicable pair with valid -profiles returns a typed result with `position_from_a=None`; it is not treated -as missing data or an exception. - -## 11. Pairwise numerical implementation decision - -The earlier exact all-minima design is retained only as supporting numerical -research. Production Stage 4 uses the smaller contract below. - -### 11.1 Shared profile preparation and cutoff radii - -For every loaded profile, cache: - -- logarithmic radii; -- logarithmic densities; -- ordinary log–log segment coefficients; -- the fixed `1e-4 electron/bohr^3` cutoff radius. - -Because each packaged profile is strictly decreasing over its stored positive -segments, the cutoff radius is unique. Locate the bracketing stored values and -invert the accepted log–log segment analytically. Do not run a general root -finder for a cached one-dimensional inverse. - -Dataset validation MUST confirm for every packaged H–Lr profile that: - -- all positive segments are strictly decreasing; -- the first stored value is above the cutoff; -- the profile falls below the cutoff before the 20-bohr public limit; -- substituting the cached radius reproduces the cutoff within a documented - floating-point tolerance. - -### 11.2 Boundary-mode algorithm - -1. Validate and convert `R` to bohr. -2. Apply canonical pair orientation if used. -3. Read the two cached cutoff radii. -4. For identical atoms, return `R/2` immediately. -5. If the cutoff contours have a positive gap, return their midpoint. -6. Otherwise solve - - \[ - g(x)=\log\rho_A(x)-\log\rho_B(R-x)=0. - \] - - The function is monotone non-increasing under the packaged profile contract, - so bracketed bisection is sufficient. -7. If the endpoint signs do not bracket a root, return - `one_atom_dominates`. -8. Stop bisection when the coordinate bracket is no wider than `1e-10 bohr` or - binary64 adjacency is reached. -9. Evaluate and report component densities using the public Stage 3 profile - convention at the selected coordinate. -10. Transform orientation and units only after the native result is complete. - -Use the continuous log–log segment representation for the equality comparison. -One-ULP exact-knot value round trips MUST NOT create or remove an equality -crossing. - -### 11.3 Practical minimum-mode algorithm - -The search is intentionally resolution-limited. Its public spatial resolution -is: - -```text -IAS_MINIMUM_RESOLUTION_BOHR = 0.01 -``` - -This is approximately `0.0053 angstrom`, far below the intended chemical-geometry -scale while large enough to coalesce the sub-resolution shell and interpolation -splittings that made exhaustive Stage 4A disproportionately complex. - -For unlike atoms with a nonempty significant-overlap interval `I_c`: - -1. Evaluate `f(x;R)` on a deterministic uniform grid over `I_c` with step no - larger than `0.02 bohr`. Include both interval endpoints, the interval - midpoint, and the equal-proatom-density coordinate when it exists. -2. A sample-resolved valley is an interior sample no higher than its immediate - neighbors. -3. Refine each sampled valley only inside its neighboring sample bracket with a - small dependency-free bounded minimizer. Golden-section search is acceptable - because the bracket is already local and the public contract coalesces - smaller internal structure. -4. Repeat with step no larger than `0.01 bohr`. -5. Combine candidates from both passes. Candidates closer than `0.01 bohr` - belong to one resolved valley; retain the lowest sampled/refined value as - that valley’s representative. -6. If the selected resolved valley changes by more than `0.01 bohr`, or its - density changes by more than `1e-4` relative, run one fallback pass with step - no larger than `0.005 bohr`. -7. After the fallback, return the finest supported representative and set - `search_converged` according to the same practical criteria. Do not invoke - Decimal arithmetic merely to classify smaller structure. -8. Sort resolved valleys by density. Retain the selected valley and at most one - runner-up separated by at least `0.01 bohr`. -9. Mark `ambiguous_competing_minima` when the runner-up is within `1e-4` - relative density of the selected valley. This is a declared practical - equivalence policy, not a binary64 equality tolerance. -10. Compare `f(0)` and `f(R)` with the selected interior value. If either is - lower by more than the ordinary floating-point comparison envelope, use - `boundary_dominated`. - -For a homonuclear pair, return `R/2` without running this search. The -implementation MAY evaluate nearby samples to report whether the midpoint also -behaves as a resolved minimum, but this diagnostic MUST NOT move the returned -coordinate. - -If no interior valley is found on the finest pass, return -`no_resolved_interior_minimum`. Do not clamp a minimum to a cutoff endpoint or -nucleus. - -### 11.4 Tolerance categories - -Keep these concepts separate: - -- **tail cutoff:** `1e-4 electron/bohr^3`; a fixed model policy; -- **minimum spatial resolution:** `0.01 bohr`; a fixed practical-resolution - policy; -- **competitive depth gap:** `1e-4` relative; a user-visible ambiguity policy; -- **equality bisection tolerance:** `1e-10 bohr`; a numerical convergence - tolerance; -- **ordinary floating-point envelope:** only for comparisons whose outcome is - not already defined by the practical policies above. - -Do not reuse one large `isclose` tolerance for cutoff contact, equality, -minimum competition, and binary64 roundoff. Do not tune tolerances only to make -individual examples pass. - -### 11.5 No exact-knot or all-minima machinery in production - -The production algorithm MUST NOT add: - -- an exact Decimal event sweep; -- exhaustive one-sided derivative topology; -- sub-ULP cell reconstruction; -- proximity merging of mathematical basins; -- global-tie lists; -- watershed-width construction; -- weighted averaging across minima. - -Exact profile knots are ordinary samples under the Stage 3 evaluator. A -sub-resolution feature that changes only under exact-event reconstruction is -outside the declared `minimum`-mode resolution and cannot affect -`boundary` mode. - -The archived Stage 4A design and review cases remain useful as a conservative -development reference and as supporting information explaining this decision. - -### 11.6 Performance guardrail - -The intended downstream use includes repeated pair evaluation. - -Record local, non-CI timings for: - -- first call including data load; -- cached cutoff-radius access; -- one `boundary` pair; -- one ordinary `minimum` pair; -- one fallback-pass `minimum` pair; -- a deterministic 1,000-pair batch in each mode. - -Targets on the recorded development machine are: - -```text -cached boundary mode: comfortably below 0.2 ms/pair -cached minimum mode: comfortably below 1.0 ms/pair in the ordinary case -``` - -These are design targets, not portable CI thresholds. No benchmarking or -numerical runtime dependency may be added. - -### 11.7 Validation reference and supporting study - -Stage 4 validation MUST use two references for different purposes: - -1. a slower fine-grid-and-refinement search inside `I_c` to validate the - practical minimum contract at and above `0.01 bohr` resolution; -2. the archived exact-event Stage 4A cases to confirm that intentionally omitted - microstructure cannot change boundary mode and is either coalesced or - reported as unresolved/ambiguous in minimum mode. - -The executed notebook: - -```text -notebooks/04-ias-method-selection-study.ipynb -``` - -and its compact documentation page MUST record: - -- cutoff-radius coverage for H–Lr; -- representative boundary/minimum comparisons; -- homonuclear Li–Li behavior; -- low-density-gap behavior; -- practical-reference agreement and timings; -- the numerical pathologies that motivated abandoning exhaustive all-minima - production semantics; -- the final two-mode decision and limitations. - -Agreement with actual molecular-density QTAIM coordinates is scientifically -valuable and SHOULD be added when a curated benchmark is available. It is not a -claim made by `0.2.0`, and absence of such a benchmark must remain explicit in -the documentation. - -## 12. Documentation and notebook strategy - -### 12.1 Split feature documentation from the later overhaul - -`0.2.0` must contain enough documentation to use the new API safely: - -- accurate public docstrings for new objects; -- one concise density/pairwise-boundary guide or equivalent section; -- exact provenance, units, range, cutoff, mode semantics, and limitations; -- one executed Stage 4 method-selection notebook; -- one executed user-facing feature notebook with plots; -- a changelog entry and working README mention. - -The broad documentation redesign belongs to `0.2.1` after the API is accepted. - -Do not move the README marketing rewrite, complete existing-notebook rewrite, -or all-module docstring conversion into the critical path of `0.2.0`. - -### 12.2 `0.2.0` Stage 4 supporting notebook is mandatory - -Add and execute: - -```text -notebooks/04-ias-method-selection-study.ipynb -``` - -This notebook is supporting information for the numerical and scientific -decision. It MUST: - -- clearly label all helper functions as research prototypes rather than public - API; -- explain the fixed per-atom cutoff; -- compare `boundary` and `minimum` definitions; -- reproduce representative cases and practical timings; -- summarize the archived exact all-minima failure modes; -- explain why exhaustive all-minima enumeration is not the production default; -- state that neither mode is an exact QTAIM surface; -- be linked from a compact page at `docs/dev/ias_method_selection.md`. - -Saved plots are encouraged. Matplotlib may be an optional -notebook/documentation dependency and MUST NOT become a runtime dependency. - -### 12.3 `0.2.0` feature notebook is mandatory - -Add: - -```text -notebooks/05-proatomic-density-and-ias.ipynb -``` - -The committed feature notebook MUST: - -- be executed; -- contain saved execution counts; -- contain saved text and plot outputs; -- execute from top to bottom in a clean environment; -- use short Markdown explanations before every logical code section; -- explain what the reader should notice rather than merely restating the code; -- avoid hidden state and manual edits to outputs; -- use deterministic examples where practical. - -Required content: - -1. package and dataset discovery through `get_builtin_set()` and registry - metadata; -2. profile retrieval and scalar evaluation with independent radius/density - units; -3. one or two radial-density plots, preferably with a logarithmic density axis; -4. one ordinary heteronuclear `boundary` example; -5. the same pair in `minimum` mode; -6. a plot of `rho_A(x)`, `rho_B(R-x)`, and their sum; -7. a homonuclear midpoint example; -8. a low-density-gap example; -9. one no-resolved-minimum or one-atom-dominance example; -10. a concise limitations and mode-selection section. - -For `0.2.0`, the notebooks may be linked from the docs/GitHub even if the -current Markdown exporter cannot faithfully embed rich plot output. The saved -`.ipynb` files remain the source artifacts. - -### 12.4 Current notebook behavior to replace in `0.2.1` - -The current repository tools: - -- execute code cells in a plain Python namespace; -- suppress or separately capture stdout; -- do not write execution counts or outputs into `.ipynb`; -- do not verify that committed outputs are current; -- export duplicate Markdown pages; -- do not preserve rich Jupyter output such as Matplotlib figures. - -They do not “unexecute” notebooks; they simply ignore the stored execution -state and run code transiently. - -`0.2.1` should replace this with actual Jupyter-kernel execution. - -Preferred compact workflow: - -1. one maintainer tool, preferably the existing `tools/check_notebooks.py`, - gains: - - `--write` to execute notebooks and save normalized outputs; - - `--check` to execute into temporary files and compare against committed - normalized notebooks; -2. use `nbclient`/`nbconvert` and `nbformat`, not `exec()` of extracted code - strings; -3. normalize nondeterministic execution metadata before comparison; -4. fail on cell errors; -5. keep outputs committed; -6. CI verifies that outputs are fresh. - -Avoid adding separate “execute,” “export,” and “check” tools when one tool with -two modes is sufficient. - -### 12.5 Direct notebook rendering in `0.2.1` - -Preferred documentation approach: - -- render actual `.ipynb` files directly with `mkdocs-jupyter`; -- use committed saved outputs; -- set documentation-build execution off, because freshness is checked - separately; -- include notebook source/download links; -- remove `docs/notebooks/*.md`; -- remove `tools/export_notebooks.py`; -- remove export-sync tests and replace them with notebook execution-state tests. - -This reduces duplicated files and allows plots and rich outputs to appear -naturally. - -A different direct-rendering solution may be used if it is materially better, -but do not keep both direct rendering and generated Markdown copies. - -### 12.6 Explanatory notebook writing standard - -Every shipped notebook should contain: - -- a title and a concise goal; -- prerequisites or expected knowledge; -- Markdown before each meaningful code section; -- explanation of important output after or before the cell; -- section headings that form a useful table of contents; -- a final “what this demonstrated” and limitations section. - -Not every one-line import needs its own essay. The standard is that a reader -should understand why each logical block exists and what to inspect in its -output. - -### 12.7 Full API reference with MkDocs - -Do not switch away from MkDocs solely to obtain Sphinx-like API pages. - -The existing `mkdocstrings[python]` stack can render: - -- complete function and method signatures; -- type annotations; -- parameter names, types, descriptions, and defaults; -- return types and descriptions; -- raised exceptions; -- class attributes; -- examples and cross-references. - -The `0.2.1` task is to configure and feed it correctly. - -Preferred `mkdocs.yml` options include: - -```yaml -plugins: - - mkdocstrings: - handlers: - python: - paths: [src] - options: - docstring_style: google - show_root_heading: true - show_source: false - show_signature: true - separate_signature: true - show_signature_annotations: true - signature_crossrefs: true - annotations_path: brief - show_docstring_parameters: true - show_docstring_returns: true - show_docstring_raises: true - show_docstring_attributes: true - show_docstring_examples: true -``` - -Exact presentation options may be tuned after inspecting the rendered site. - -All intended public functions, classes, methods, and dataclass attributes should -receive structured Google-style docstrings with, as applicable: - -```text -Args: -Returns: -Raises: -Attributes: -Examples: -Notes: -``` - -Type annotations remain the source of type information; docstrings explain -meaning, units, behavior, and constraints. - -### 12.8 README/home-page redesign in `0.2.1` - -`docs/index.md` remains the source of `README.md`. - -The home page should immediately answer: - -1. What is `atomref`? -2. Which scientific/software problems does it solve? -3. Why use it instead of embedding constants or ad hoc tables? -4. What can a user do in the first five lines of Python? -5. Which data and provenance guarantees are provided? - -The opening should communicate an identity close to: - -> `atomref` provides cited atomic values and frozen spherical free-atom -> electron densities through a small Python API for crystallographic, -> quantum-chemical, and molecular-structure algorithms. - -Show working radii, profile, boundary-mode, and minimum-mode examples near the top. Move detailed policy -terminology and nested-transfer mechanics lower. - -Avoid leading with negative positioning such as “not another periodic-table -encyclopedia.” - -### 12.9 Keep the durable docs compact - -The preferred durable structure after `0.2.1` is still small: - -```text -docs/index.md -docs/guide/proatomic_density.md -docs/dev/architecture.md -docs/api/*.md -notebooks/*.ipynb -``` - -Remove the duplicated public development-plan page. Do not create separate pages -for interpolation, pairwise method semantics, provenance, licensing, and every result -field unless one combined guide becomes genuinely difficult to use. - -## 13. Staged implementation plan - -## `0.2.0` implementation - -### Stage 0 — Baseline verification - -#### Goal - -Confirm the supplied repository is healthy before modifying it. - -#### Actions - -Inspect at minimum: - -- `src/atomref/registry.py`; -- `src/atomref/policy.py`; -- `src/atomref/__init__.py`; -- `src/atomref/data/registry.json`; -- package-data configuration; -- registry, public-API, package-data, and release tests; -- existing notebooks and notebook tools; -- `mkdocs.yml`. - -Record: - -- current version; -- baseline tests; -- current public API; -- current built-in dataset return type; -- current notebook execution/output state. - -Run: - -```bash -python -m pytest -q -python tools/check_registry.py -python tools/check_notebooks.py -python tools/export_notebooks.py --check -python tools/gen_readme.py --check -``` - -When dependencies are available: - -```bash -flake8 src tests tools -mkdocs build --strict -``` - -#### Constraints - -- Do not change code in Stage 0. -- Report baseline failures before attributing later failures to new work. - -#### Completion criteria - -- baseline recorded; -- relevant call graph understood; -- no unexplained existing failures. - -#### Suggested commit - -No commit. - ---- - -### Stage 1 — Generalize packaged-dataset loading - -#### Goal - -Make `get_builtin_set()` the unified entry point for scalar and radial packaged -datasets before adding the new data. - -#### Required changes - -1. Add an explicit storage-kind discriminator to existing registry entries. -2. Add the radial storage kind to registry validation. -3. Introduce the minimal loaded radial-set type. -4. Change `get_builtin_set()` to dispatch by storage kind. -5. Preserve caching and alias resolution. -6. Narrow scalar policy and convenience code explicitly to - `ElementScalarSet`. -7. Update type annotations, public exports, tests, and architecture comments. -8. Generalize `CoverageInfo` wording. - -#### Required tests - -- every existing scalar dataset still loads through `get_builtin_set()`; -- the returned scalar type remains `ElementScalarSet`; -- aliases still resolve before caching/loading; -- an unknown storage kind raises `DatasetError`; -- scalar policy code rejects a radial test fixture clearly; -- generic discovery/metadata functions do not care about payload kind; -- a small synthetic radial fixture can be dispatched and queried; -- existing scalar policy results are unchanged. - -#### Do not do - -- do not add radial policy behavior; -- do not generalize transfer fitting; -- do not build a registry framework beyond the two actual storage kinds; -- do not bypass `get_builtin_set()` from the later proatomic API. - -#### Completion criteria - -- generic built-in dataset dispatch is working; -- scalar behavior is unchanged; -- radial sets can participate in the same discovery and loading path. - -#### Suggested commit - -```text -refactor: generalize built-in dataset loading -``` - ---- - -### Stage 2 — Reproducible neutral profile snapshot - -#### Goal - -Add the exact minimal neutral H–Lr data product without runtime dependence on -the generator project. - -#### Required changes - -1. Add `tools/build_proatomic_density_snapshot.py`. -2. Add one compressed package-data file. -3. Add the `proatomic_density` quantity and dataset metadata to `registry.json`. -4. Update `NOTICE.md` with CC BY attribution and exact DOIs. -5. Update package build inclusion for `.zip`. -6. Extend registry, package-data, and distribution checks. -7. Add focused source/data-integrity tests. -8. Document the maintainer tool briefly in `tools/README.md`. - -#### Why the builder belongs in `tools/` - -This script is not for normal package users. It is a maintainer tool that makes -the committed scientific snapshot auditable and reproducible. - -Without it, future maintainers and agents would have to trust an opaque -hand-edited compressed table and manually repeat: - -- hash verification; -- neutral-state selection; -- H–Lr coverage checks; -- 20-bohr truncation/bracketing; -- column ordering; -- deterministic compression. - -The script should therefore be named as a snapshot builder, not as a public -“import” feature. - -#### Snapshot-builder requirements - -The builder MUST: - -- accept a local upstream dataset directory or explicit CSV/metadata paths; -- perform no network access; -- verify source hashes and identities; -- select exactly one neutral profile per Z=1..103 from metadata; -- preserve source radius and density decimal values; -- retain rows through the first source point above 20 bohr; -- verify expected retained row count and bracketing values; -- verify finite, strictly increasing positive radii; -- verify finite, strictly positive densities in the retained table; -- verify every profile is monotonically non-increasing within a documented - numerical tolerance; -- write columns in increasing Z order; -- create the deterministic single-member ZIP defined in Section 6.6; -- support a check mode and a write mode; -- print a concise source/output summary. - -Preferred commands: - -```bash -python tools/build_proatomic_density_snapshot.py \ - --source-dir ../atomref-proatoms/data/profiles/pbe0_sfx2c_dyallv4z_h-lr_spherical_v2 \ - --write - -python tools/build_proatomic_density_snapshot.py \ - --source-dir ../atomref-proatoms/data/profiles/pbe0_sfx2c_dyallv4z_h-lr_spherical_v2 \ - --check -``` - -The CLI may be simplified. - -#### Registry requirements - -Add one quantity: - -```text -proatomic_density -``` - -Add one atomref-side set ID clearly identifying: - -- neutral scope; -- H–Lr coverage; -- PBE0/sf-X2C/dyall-v4z; -- upstream v2 lineage. - -The exact ID remains a review point. - -Its storage metadata MUST declare `element_radial_csv_zip`, filename -`proatomic_density_neutral.zip`, and member -`proatomic_density_neutral.csv`. - -The new dataset MUST load through: - -```python -get_builtin_set(DatasetRef("proatomic_density", set_id)) -``` - -and return the radial loaded-set type. - -#### Data tests - -Tests MUST verify: - -- the compressed file is packaged; -- exactly 103 profile columns exist; -- columns map to Z=1..103 without gaps; -- row count is 1127; -- radii are strictly increasing; -- the retained bracketing point is above 20 bohr; -- all densities are finite and positive; -- all profiles are monotone non-increasing within tolerance; -- source and license metadata are present; -- generic `get_builtin_set()` loads the radial set; -- wheel and sdist contain the data and attribution metadata. - -Source-dependent byte regeneration may be a maintainer check when the upstream -archive is not present in CI. CI must validate the committed snapshot -internally. - -#### Do not do - -- do not package all 501 states; -- do not package the full upstream metadata; -- do not package configurations or multiplicities; -- do not add the upstream generator as a dependency; -- do not download data at runtime; -- do not add a second registry. - -#### Completion criteria - -- reproducible neutral snapshot committed; -- generic dataset loader returns it; -- provenance and licensing recorded; -- distribution tests pass. - -#### Suggested commit - -```text -feat(data): add neutral proatomic density snapshot -``` - ---- - -### Stage 3 — Proatomic density evaluation - -#### Goal - -Expose a small typed dependency-free profile API with stable interpolation and -independent units. - -#### Required changes - -1. Add `src/atomref/proatoms.py`. -2. Add immutable profile access/evaluation objects or an equally clear API. -3. Use the radial object returned by `get_builtin_set()`. -4. Implement radius and density unit conversion independently. -5. Implement the interpolation contract. -6. Add lazy/shared cached numerical data as needed. -7. Add public exports and public-API tests. -8. Add density tests. -9. Add minimum accurate API docstrings and feature documentation. - -#### Density acceptance tests - -##### Dataset and profile access - -- H, C, O, Fe, La, U, and Lr profiles load. -- Rf and Og return `None`. -- D and T produce the H profile. -- invalid symbols return `None`. -- aliases/set IDs resolve through registry conventions. -- repeated calls reuse cached set/profile data. -- the public profile/set objects cannot mutate shared package data. - -##### Radius behavior - -- `r=0` equals the first stored value. -- `r=r_min` equals the exact stored value. -- selected interior knots reproduce exact source values. -- `r=20 bohr` evaluates successfully. -- values above 20 bohr raise. -- negative, `NaN`, and infinite radii raise. -- unknown radius units raise. - -##### Interpolation - -- a synthetic power-law profile is reproduced by log–log interpolation; -- interpolation is continuous at stored knots within tolerance; -- interpolated densities remain positive; -- representative profiles remain non-increasing; -- no linear fallback or zero-fill path exists in the supported domain. - -##### Units - -- equivalent bohr/ångström coordinates evaluate the same physical point; -- electron/bohr³ and electron/ų outputs convert correctly; -- default radius unit is ångström; -- default density unit is electron/bohr³; -- changing density unit does not change interval selection or provenance; -- unknown density units raise. - -##### Compatibility - -- all existing scalar tests pass unchanged; -- `get_radii_set()` and `get_xh_set()` retain scalar return types; -- policy code rejects radial sets explicitly; -- all packaged sets remain accessible through `get_builtin_set()`. - -#### Completion criteria - -- density evaluation works for neutral H–Lr; -- unit, range, and interpolation behavior is tested; -- API is typed and documented; -- runtime remains dependency-free. - -#### Suggested commit - -```text -feat: add proatomic density evaluation -``` - ---- - -### Stage 4 — Pairwise proatomic boundary and practical IAS proxy - -#### Goal - -Implement the two-mode pairwise contract from Sections 10 and 11 without the -superseded exhaustive all-minima machinery. - -#### Stage 4A — finalize and preserve the numerical decision - -Before public implementation: - -1. accept the revised Sections 10 and 11; -2. retain the archived exact-event Stage 4A reports as development evidence, - not as the production contract; -3. add and execute `notebooks/04-ias-method-selection-study.ipynb`; -4. add `docs/dev/ias_method_selection.md` and link the notebook; -5. verify all H–Lr cutoff radii and the monotonicity assumptions; -6. record representative agreement and timing for the practical minimum search; -7. resolve final public function/result names without changing the two-mode - semantics. - -Stage 4A MUST NOT modify production pairwise code beyond documentation/test -fixtures needed to preserve the decision record. - -Representative development pairs should include: - -```text -H-H -Li-Li -C-O -Fe-O -H-U -H-heavy-element -one low-density-gap pair -one one-atom-dominance pair -one boundary-dominated minimum case -one pair with competing resolved minima -one archived sub-resolution exact-event case -``` - -#### Stage 4B — implement shared result, direct functions, and dispatcher - -Implement: - -- the fixed cached cutoff radius; -- the shared immutable result type; -- `estimate_proatomic_boundary()`; -- `estimate_promolecular_density_minimum()`; -- `estimate_ias_position(..., mode="boundary" | "minimum")`; -- exact homonuclear midpoint handling; -- equal-contribution bisection; -- cutoff-gap midpoint behavior; -- the resolution-limited minimum search; -- explicit statuses and provenance; -- canonical reversal handling and unit conversion. - -Do not implement: - -- exhaustive local-minimum enumeration; -- exact global-tie machinery; -- widths or watershed clipping; -- weighted centers or spreads; -- Decimal event topology; -- a universal minimum-distance ban; -- hidden fallback between modes. - -#### Stage 4C — validation - -Required tests cover: - -- all H–Lr cutoff radii are unique, finite, and reproduce the cutoff; -- exact homonuclear `R/2` in both modes; -- equal component densities in ordinary boundary-overlap cases; -- cutoff-gap midpoint geometry; -- continuous agreement of the two boundary branches at cutoff contact; -- explicit one-atom dominance; -- minimum search limited to the significant-overlap interval; -- practical-reference agreement at the declared `0.01 bohr` resolution; -- no-resolved-minimum, boundary-dominated, competing-minimum, and unstable-search - statuses; -- pair reversal for both primary and alternative coordinates; -- distance-unit and density-unit invariance; -- invalid mode, distance, and units; -- missing elements; -- representative light, transition, lanthanide, and actinide pairs; -- practical cached performance for both modes; -- regression cases from the Stage 4A supporting study. - -#### Completion criteria - -- the default returns a stable, interpretable proatomic divider; -- the optional mode returns one cutoff-bounded, practically resolved - promolecular minimum or an explicit non-result/diagnostic status; -- identical atoms always return `R/2`; -- no arbitrary short-distance ban is present; -- pair reversal, units, cutoff, provenance, and failures are tested; -- no exact molecular-QTAIM claim is made; -- no all-minima production machinery remains in scope. - -#### Suggested commit - -```text -feat: add two-mode proatomic IAS position estimates -``` - -### Stage 5 — Executed feature notebook and `0.2.0` release - -#### Goal - -Demonstrate the accepted functionality, complete minimum documentation, and -release `0.2.0` without performing the full documentation overhaul. - -#### Required notebook work - -Add and execute: - -```text -notebooks/05-proatomic-density-and-ias.ipynb -``` - -It MUST satisfy Section 12.3. - -For `0.2.0`, extend existing notebook validation minimally so CI verifies: - -- the notebook exists; -- all nonempty code cells have execution counts; -- expected output-bearing cells contain saved outputs; -- the code executes cleanly; -- required Markdown sections are present. - -Do not build a second large notebook pipeline during this stage. - -#### Minimum documentation - -- add the new notebook to the notebook overview; -- link the saved notebook from docs; -- add accurate density, cutoff, mode-selection, and IAS-proxy guide material; -- add the new module API page; -- mention the feature on the home page without the full marketing rewrite; -- document exact provenance, units, 20-bohr domain, and limitations; -- update architecture only where needed for generic datasets; -- update changelog and release metadata. - -#### Project metadata - -Update: - -- version to `0.2.0`; -- package keywords for proatomic density/electron density/IAS; -- build inclusion for `.zip`; -- installed-wheel smoke tests. - -#### Installed-wheel smoke test - -At minimum: - -```python -import atomref as ar - -ref = ar.DatasetRef("proatomic_density", "") -dataset = ar.get_builtin_set(ref) - -rho = ar.get_proatomic_density( - "O", - 0.75, - radius_unit="angstrom", - density_unit="electron/bohr^3", -) -assert rho is not None and rho > 0 - -boundary = ar.estimate_proatomic_boundary("C", "O", 1.43) -assert boundary is not None and boundary.position_from_a is not None - -minimum = ar.estimate_promolecular_density_minimum("C", "O", 1.43) -assert minimum is not None - -result = ar.estimate_ias_position("C", "O", 1.43, mode="boundary") -assert result == boundary -``` - -#### Final commands - -Run all current checks, including: - -```bash -python tools/check_registry.py -python tools/check_notebooks.py -python tools/export_notebooks.py --check -python tools/gen_readme.py --check -flake8 src tests tools -python -m pytest -q -mkdocs build --strict -python tools/release_check.py -``` - -If the feature notebook is not compatible with the current Markdown exporter -because it contains plots, the exporter may omit that notebook for `0.2.0` while -the docs link directly to the saved `.ipynb`. Record this temporary state for -Stage 6 rather than implementing a plot-specific Markdown exporter. - -#### Completion criteria - -- all `0.2.0` acceptance criteria pass; -- feature notebook is executed and saved; -- wheel/sdist work; -- minimal docs are accurate; -- no broad docs rewrite has obscured scientific review. - -#### Suggested commits - -```text -docs: add IAS method study and proatomic density feature notebook -chore: prepare atomref 0.2.0 -``` - ---- - -## `0.2.1` documentation and discoverability work - -Do not start these stages until `0.2.0` is accepted. - -### Stage 6 — Complete API docs, adoption-oriented README, notebook documentation, packaging, and `0.2.1` release - -#### Goal - -Make the package immediately understandable and attractive to both new users and -developers while preserving accurate scientific claims and the lightweight -runtime. - -This stage is a documentation, packaging, and release-hardening stage. It MUST -NOT change density values, cutoff radii, pairwise mode semantics, selected -coordinates, or other accepted scientific behavior except to fix a confirmed -defect with an explicit changelog entry and regression test. - -#### Documentation and adoption principle - -The README and documentation home page are the main adoption surface for a -developer deciding whether to implement the required atomic-reference logic -independently or use `atomref`. - -They must be technically accurate, but accuracy alone is not sufficient. A new -visitor should be able to determine quickly: - -- what practical problems `atomref` solves; -- which capabilities are available now; -- why the package is preferable to embedding ad hoc constants, datasets, and - fallback logic in another project; -- that the core runtime remains small and dependency-free; -- how to install it; -- how to perform one useful operation immediately; -- where to find detailed provenance, scientific assumptions, limitations, and - API documentation. - -Use an adoption-oriented or marketability-aware presentation without hype, -unsupported comparison, or concealment of limitations. Lead with user problems, -working examples, and concrete benefits. Move internal terminology, complete -dataset inventories, nested-policy mechanics, and detailed caveats below the -first-use path or into dedicated documentation. - -#### Notebook policy and direct rendering - -Notebooks are narrative examples, how-to material, and occasionally -supporting-information-style analyses. They are not the canonical -implementation or a release-defining exact-reproducibility layer. - -Replace the current generated-Markdown notebook path with direct rendering of -the actual `.ipynb` files through `mkdocs-jupyter`. - -The direct-rendering design MUST: - -- render committed notebook Markdown, code, saved text output, and saved plots - inside the MkDocs site; -- render ordinary Jupyter inline and display mathematics, including `$...$` and - `$$...$$`, under the current Material theme; -- use the committed outputs during documentation builds rather than executing - notebooks as part of `mkdocs build`; -- keep only one copy of each notebook and one documentation representation; -- add no staging, cache, generated-output, or parallel notebook directory to the - repository; -- remove generated `docs/notebooks/*.md` copies and the custom Markdown exporter; -- preserve direct links to the source notebook where useful; -- avoid an output-freshness, normalized-output, or byte-for-byte reproducibility - contract. - -Because MkDocs pages normally belong under the configured documentation source, -the preferred file layout is to move the actual notebooks from the root -`notebooks/` directory into the existing `docs/notebooks/` directory, replacing -the generated Markdown files there. Do not retain duplicate root-level notebook -copies, symlinks, or a copy step. If a simpler supported configuration renders -the existing root-level notebooks directly without duplication or staging, that -configuration MAY be used instead. - -Configure `mkdocs-jupyter` with notebook execution disabled for ordinary and -strict documentation builds. The built site must therefore reflect the outputs -committed in the notebook files and must not silently rewrite them. - -Notebook code should still receive one separate smoke-execution check using -standard Jupyter tooling such as `nbclient` or `nbconvert`, not the current -custom JSON-plus-`exec()` approximation. Execute temporary copies or write the -results only to a temporary output directory, fail on notebook exceptions, and -discard the produced notebooks after the check. Do not compare generated -outputs with committed outputs and do not modify the committed notebooks. - -The smoke check should run once on the primary CI Python version, not through -every Python version in the ordinary test matrix. A small wrapper in `tools/` -MAY remain when it makes the command portable and keeps `release_check.py` -simple, but it should delegate execution to standard Jupyter tooling rather -than implement notebook semantics itself. - -All direct notebook dependencies MUST be declared in the existing `notebook` -optional-dependency group, including `mkdocs-jupyter`, the execution tool used by -the smoke check, `matplotlib`, and any kernel package explicitly required by the -repository. The same dependency entries MUST be duplicated in `all`. They MUST -NOT become core runtime dependencies. The documentation workflow should install -both `docs` and `notebook` extras. - -If a focused local prototype demonstrates that `mkdocs-jupyter` cannot render -inline and display mathematics correctly with the current Material theme, stop -that implementation and use standard `nbconvert` output as the fallback. Do not -maintain both rendering systems. The fallback must still remove the bespoke -exporter, use one standard conversion path, and avoid exact output-freshness -requirements. - -Notebook prose or stored outputs should be changed only when needed to fix a -broken, stale, or misleading user-facing example discovered during the -transition or documentation audit. - -#### Required changes - -1. Configure `mkdocstrings` for complete typed signatures and structured - documentation sections. -2. Rewrite all intended public docstrings in one consistent style. -3. Verify every public parameter, return value, raised error, unit, valid range, - and important attribute appears correctly in rendered API documentation. -4. Review API-page member selection so every intended public function, class, - method, and dataclass is documented without exposing irrelevant internals. -5. Rewrite `docs/index.md` as a clear product introduction and regenerate - `README.md` from it. -6. Improve the quickstart path so installation and a useful first result appear - before detailed architecture or policy terminology. -7. Replace generated notebook Markdown with direct `.ipynb` rendering through - `mkdocs-jupyter`, following the notebook policy above. -8. Move the actual notebooks into the existing MkDocs source tree when required - for direct rendering, while retaining only one copy of each notebook. -9. Remove `tools/export_notebooks.py`, generated notebook Markdown files, - export-sync tests, and the corresponding CI/release-check commands. -10. Replace the custom `exec()`-based notebook check with one temporary - Jupyter-based smoke-execution path, or reduce the existing wrapper to a thin - delegation layer. -11. Verify that inline mathematics, display mathematics, saved text output, and - saved PNG plots render correctly in the strict MkDocs build. -12. Keep notebook execution disabled in `mkdocs-jupyter`; documentation builds - render committed outputs and do not rewrite source notebooks. -13. Add an `all` optional-dependency group containing the union of all - user-facing optional feature dependencies. Do not include test, lint, - packaging, upload, or release-only tooling in `all`. -14. Keep every notebook-related dependency in `notebook` and duplicate the same - dependency entries in `all`. Keep ordinary documentation dependencies in - `docs`, and install `.[docs,notebook]` for documentation builds. -15. Document at least the lightweight base installation, notebook-capable - installation, and complete user-facing installation: - - ```bash - pip install atomref - pip install "atomref[notebook]" - pip install "atomref[all]" - ``` - -16. Remove `docs/dev/dev_plan.md` and its navigation item so the root - `DEV_PLAN.md` remains the only development plan. -17. Ensure architecture, data-curation, notebook-overview, and scientific guide - pages match the final code, public names, dataset identity, units, cutoff, - mode semantics, and limitations. -18. Review package description, keywords, project links, and other public - metadata for consistency with the complete `0.2.x` feature set. -19. Update the changelog and version to `0.2.1`. -20. Validate the base installation, `atomref[notebook]`, and `atomref[all]` from - built artifacts in clean environments. - -#### API documentation acceptance - -For every public function/class added or retained: - -- signature is visible; -- annotations are visible; -- parameters include type, meaning, and default; -- return value is described; -- raised errors are described; -- units and valid ranges are explicit where relevant; -- dataclass attributes are visible; -- examples are present for non-obvious APIs; -- cross-references work. - -#### README acceptance - -A new visitor should discover within the initial screen or immediately following -it: - -- a concise statement of what `atomref` does; -- the concrete developer problems it solves; -- radii, X–H, proatomic-density, boundary, and IAS-proxy capabilities; -- the dependency-free core runtime; -- installation and a compact working example; -- explicit provenance as a central value; -- a concise explanation of why using `atomref` is preferable to maintaining - project-local constants and fallback rules; -- links to the quickstart, datasets/provenance, notebooks, scientific guidance, - and API reference. - -The opening MUST NOT require a newcomer to understand the internal registry, -policy, transfer, or dataset taxonomy before seeing the package's value and a -working example. Limitations must remain honest and visible, but should be -placed near the affected feature rather than overwhelming the first-use path. - -#### Notebook documentation acceptance - -- each notebook has exactly one maintained `.ipynb` source file; -- the actual notebooks appear directly in MkDocs navigation; -- inline and display mathematics render correctly; -- saved text outputs and PNG plots render correctly; -- documentation builds do not execute or modify notebooks; -- a separate temporary Jupyter smoke check fails on execution errors; -- the smoke check does not compare outputs or enforce freshness; -- no generated notebook Markdown, custom Markdown exporter, duplicate notebook - tree, or committed conversion cache remains; -- notebook links, source links, packaging checks, and the notebook overview use - the final notebook paths. - -#### Packaging acceptance - -- required runtime dependencies remain empty; -- existing individual user-facing extras remain usable; -- every direct notebook dependency is present in `notebook` and duplicated in - `all`; -- `all` is the union of user-facing optional feature dependencies; -- `all` excludes contributor-only test, lint, build, upload, and release tools; -- base, `notebook`, and `all` installations succeed from built artifacts in clean - environments; -- installed-package examples use only documented dependencies; -- wheel and sdist retain all required datasets, attribution, documentation, and - notebook artifacts. - -#### Final checks - -Run the updated project checks, including: - -```bash -python tools/check_registry.py -# Run the final temporary Jupyter notebook smoke-execution command or thin wrapper. -python tools/gen_readme.py --check -flake8 src tests tools -python -m pytest -q -mkdocs build --strict -python tools/release_check.py -``` - -The obsolete custom Markdown-export command MUST NOT remain. Inspect the strict -site build and verify at least one inline formula, one display formula, one saved -text output, and one saved PNG plot from the notebooks. - -Also verify the base package, `atomref[notebook]`, and `atomref[all]` by -installing the built artifacts in clean environments and running the documented -minimal examples outside the repository checkout. - -#### Completion criteria - -- complete typed API documentation renders correctly; -- the README/home page communicates value and first use immediately without - overstating the package; -- a developer considering a local reimplementation can understand the concrete - advantages of adopting `atomref`; -- the actual `.ipynb` notebooks render directly in MkDocs with formulas and - saved rich output; -- notebook smoke execution uses standard Jupyter tooling without introducing an - output-freshness contract; -- generated notebook Markdown, the bespoke exporter, and duplicate notebook - representations are removed; -- base, `notebook`, and `all` optional-dependency installations are coherent, - documented, and validated from built artifacts; -- duplicate development-plan documentation is removed; -- architecture, scientific guides, package metadata, and changelog match the - accepted implementation; -- strict documentation and full release checks pass; -- version `0.2.1` is ready for external review and release. - -#### Suggested commits - -```text -docs: render notebooks directly -docs: complete typed API reference -docs: reposition atomref documentation -chore: add complete user extras -chore: prepare atomref 0.2.1 -``` - -## 14. Release acceptance checklists - -### 14.1 `0.2.0` scope and compatibility - -- [ ] Version is `0.2.0`. -- [x] Existing `0.1.x` public APIs remain available. -- [x] Existing scalar values and default policies are unchanged. -- [x] Runtime dependencies remain empty. -- [x] Runtime performs no network access. -- [x] No ionic or profile-correlation behavior is exposed. -- [x] `get_builtin_set()` loads every packaged scalar and radial dataset. -- [x] Scalar policy code rejects radial sets clearly. -- [x] Registry discovery and metadata work identically across payload kinds. - -### 14.2 `0.2.0` data - -- [x] Source profiles hash matches the pinned `profiles.csv`. -- [x] Source metadata hash matches the pinned `metadata.json`. -- [x] Upstream dataset ID and version match. -- [x] Basis ID and basis hash match. -- [x] Exactly 103 neutral profiles are selected. -- [x] Coverage is exactly Z=1..103. -- [x] Retained row count is 1127. -- [x] Public limit is exactly 20 bohr. -- [x] First retained point above 20 bohr brackets the endpoint. -- [x] All retained densities are finite and positive. -- [x] All retained profiles are monotone non-increasing within tolerance. -- [x] ZIP output satisfies the deterministic single-member contract in Section - 6.6. -- [x] Snapshot is built by the maintainer tool, not hand-edited. -- [x] Wheel and sdist contain `proatomic_density_neutral.zip`. -- [x] Registry and notice contain CC BY attribution and exact DOIs. - -### 14.3 `0.2.0` density API - -- [ ] Profile retrieval works for H–Lr. -- [ ] D/T map to H. -- [ ] Unsupported elements return `None`. -- [ ] Radius units and density units are independent. -- [ ] Default radius unit is ångström. -- [ ] Default density unit is electron/bohr³. -- [ ] Electron/ų output is available explicitly. -- [ ] `r=0` uses the first-grid value. -- [ ] Exact knots reproduce stored values. -- [ ] Positive-segment log–log interpolation is tested. -- [ ] `r=20 bohr` is valid. -- [ ] `r>20 bohr` raises. -- [ ] Negative and non-finite radii raise. -- [ ] Loading is lazy/cached through generic dataset machinery. -- [ ] Shared package data cannot be mutated through public objects. - -### 14.4 `0.2.0` pairwise boundary and IAS-proxy API - -- [ ] Distance is measured from A toward B and documented. -- [ ] Distance must be `0 < R <= 20 bohr`. -- [ ] Distance and density units are independent. -- [ ] The fixed per-atom cutoff is `1e-4 electron/bohr^3` and is reported. -- [ ] Every packaged H–Lr profile has one validated cutoff radius. -- [ ] `estimate_proatomic_boundary()` is available. -- [ ] `estimate_promolecular_density_minimum()` is available. -- [ ] `estimate_ias_position()` dispatches explicitly by mode. -- [ ] The default mode is `boundary`. -- [ ] `minimum` mode never silently falls back to `boundary`. -- [ ] Identical atoms return exactly `R/2` in both modes. -- [ ] Overlapping unlike atoms can return the equal-proatom-density divider. -- [ ] Separated cutoff contours return their geometric midpoint in boundary mode. -- [ ] Cutoff contact is continuous. -- [ ] One-atom dominance is explicit. -- [ ] Minimum search is confined to the significant-overlap interval. -- [ ] Minimum search uses the declared `0.01 bohr` practical resolution. -- [ ] At most one selected minimum and one competitive alternative are exposed. -- [ ] Low-density-gap, no-resolved-minimum, boundary-dominated, competing, and - unstable cases are explicit. -- [ ] Density-unit conversion does not change geometry, method, or status. -- [ ] Pair reversal transforms primary and alternative positions correctly. -- [ ] Missing profiles return `None`. -- [ ] Invalid distances, modes, and units raise. -- [ ] Dataset, interpolation, cutoff, and pairwise-contract provenance is present. -- [ ] Practical minimum results agree with an independent slower search at the - declared resolution. -- [ ] The API does not claim an exact QTAIM surface or critical point. -- [ ] Exhaustive local-minimum, tie, width, and weighted-center machinery is not - part of the production API. - -### 14.5 `0.2.0` notebook and packaging - -- [ ] Stage 4 method-selection supporting notebook exists. -- [ ] Supporting notebook is executed and saved with outputs. -- [ ] Supporting notebook is linked from `docs/dev/ias_method_selection.md`. -- [ ] Proatomic density/pairwise feature notebook exists. -- [ ] Feature notebook is executed and saved with outputs. -- [ ] Both notebooks contain explanatory Markdown before logical code sections. -- [ ] The notebooks contain informative plots where appropriate. -- [ ] The feature notebook demonstrates both modes, homonuclear symmetry, a - low-density gap, and an explicit non-ordinary case. -- [ ] Notebook execution is deterministic and validated. -- [ ] Minimal public docs describe source, units, range, cutoff, mode selection, - and limitations. -- [ ] README is regenerated and mentions the feature accurately. -- [ ] Installed wheel loads the radial set, evaluates density, and performs both - pairwise modes. - -### 14.6 `0.2.1` documentation, notebooks, packaging, and release - -- [x] Full typed signatures render. -- [x] All public parameters, defaults, returns, raises, units, ranges, and - attributes render correctly. -- [x] Public docstrings use one structured style. -- [x] API-page member selection is complete and excludes irrelevant internals. -- [x] API cross-references work. -- [x] README/home page explains the package value immediately. -- [x] Installation and a compact working example appear before detailed internal - terminology. -- [x] Working radii, X–H, density, boundary-mode, and minimum-mode capabilities - are discoverable from the opening documentation path. -- [x] The README explains honestly why a developer should use `atomref` rather - than maintain project-local constants, datasets, and fallback rules. -- [x] Detailed policy and registry terminology is moved below first-use content. -- [x] Provenance and limitations remain explicit without obscuring the initial - adoption path. -- [x] `mkdocs-jupyter` renders the actual `.ipynb` files directly in the MkDocs - site. -- [x] Each notebook has exactly one maintained source file and no parallel - Markdown representation. -- [x] Inline `$...$` and display `$$...$$` mathematics render correctly with the - current Material theme. -- [x] Saved text output and saved PNG plots render correctly. -- [x] Ordinary and strict documentation builds do not execute or rewrite - notebooks. -- [x] Notebook code is smoke-executed once through standard Jupyter tooling in a - temporary location and execution errors fail the check. -- [x] Notebook smoke execution does not compare outputs or enforce freshness, - normalization, or byte-for-byte reproducibility. -- [x] Generated notebook Markdown files, `tools/export_notebooks.py`, export-sync - tests, and obsolete CI/release commands are removed. -- [x] No duplicate notebook tree, symlink, staging directory, or committed - notebook-conversion cache remains. -- [x] Notebook navigation, links, source links, packaging checks, and sdist paths - use the final notebook locations. -- [x] Every direct notebook dependency is declared in `notebook` and duplicated - in `all`. -- [x] Documentation CI installs both `docs` and `notebook` extras. -- [x] An `all` extra contains every user-facing optional feature dependency. -- [x] `all` excludes test, lint, packaging, upload, and release-only dependencies. -- [x] Base, `atomref[notebook]`, and `atomref[all]` installations succeed from - built artifacts in clean environments. -- [x] Documented minimal examples run outside the repository checkout. -- [x] Duplicate development-plan docs are removed. -- [x] Architecture, data-curation, scientific guides, package metadata, and - changelog match the accepted implementation. -- [x] Version is `0.2.1`. -- [x] Strict docs and full release checks pass. - -## 15. Optional architecture review after `0.2.0` - -Do not implement this section automatically during the feature release. - -After the density and IAS core is accepted, review actual friction encountered. - -Possible small follow-up work only when justified by evidence: - -- replace the hard-coded `[None] * 119` scalar allocation with a size derived - from the element table; -- improve storage-kind validation if Stage 1 reveals weak points; -- add a static type-checking CI job; -- optimize repeated pairwise boundary/minimum evaluation if profiling shows a bottleneck; -- add scalar batch evaluation if downstream use proves it necessary; -- improve mixed-license artifact checks. - -If generic `get_builtin_set()` dispatch could not be completed in `0.2.0`, that -becomes the highest-priority architecture task before expanding the profile API. -A temporary bypass must not silently become permanent. - -Do not schedule a refactor release merely because it was anticipated. If the -feature integrates cleanly, complete `0.2.1` docs and stop. - -## 16. Future compatibility and non-actionable roadmap - -Everything in this section is for future reference. An agent implementing -`0.2.0` or `0.2.1` MUST NOT begin this work without an explicit scope change. - -### 16.1 Ions and additional atomic properties - -The initial radial dataset is neutral only. - -To avoid blocking future ionic datasets: - -- keep dataset identity explicit; -- state charge scope in metadata; -- do not imply that one profile per element is universal; -- do not encode state selection into the element parser; -- allow a later keyword-only dataset/state selector without breaking current - calls; -- preserve the ability for a radial set to have a richer key model in a future - release. - -Do not add now: - -- a `charge` argument that only accepts zero; -- an unused `AtomicStateKey` class; -- ionic configuration metadata copied from upstream; -- provisional automatic ion-selection rules. - -Future ions require an evidence-aware state-selection design, especially for -anions and heavy elements. They should be introduced as explicitly identified -datasets rather than guessed through scalar transfer policies. - -Additional scalar or radial properties should continue to use the same registry -and `get_builtin_set()` machinery whenever their key space and payload can be -represented clearly. - -### 16.2 Future three-dimensional grid-density generation - -A future release may add functions that sample radial proatomic profiles onto a -three-dimensional grid. - -Conceptual workflows: - -```python -single = proatomic_density_grid( - element, - position, - grid=..., - ... -) - -total = promolecular_density_grid( - atoms, - grid=..., - ... -) -``` - -The exact API is intentionally unresolved. - -Potential supported nonperiodic grid description: - -- origin vector; -- integer shape `(nx, ny, nz)`; -- three grid-step vectors, allowing non-orthogonal grids; -- coordinate unit; -- density output unit; -- radial cutoff not exceeding the profile domain. - -Potential periodic extension: - -- periodic cell vectors; -- atomic Cartesian or fractional coordinates; -- explicit periodic axes; -- summation over every periodic image intersecting the radial cutoff; -- correct handling of skew cells and grids; -- no assumption that wrapping an atom once is equivalent to image summation. - -Important design questions: - -1. Should the API require NumPy through an optional extra? -2. Should it accept/output an array protocol rather than nested Python lists? -3. How should memory allocation and chunking work for large crystallographic - grids? -4. Should the single-atom function be public if the multi-atom implementation - needs a more efficient shared kernel? -5. How are cell, grid, and atomic coordinate units represented? -6. How are periodic images enumerated robustly for triclinic cells? -7. Is the 20-bohr radial cutoff always sufficient for the intended grid use? -8. Should atomic contributions be returned separately or only summed? -9. How are spin-resolved or ionic profiles represented later? - -Recommended direction: - -- stabilize and validate scalar radial evaluation and the two pairwise modes first; -- gather requirements from actual crystallographic grid formats; -- design nonperiodic and periodic behavior together; -- use an optional NumPy-based extra rather than making NumPy a core runtime - dependency; -- implement one optimized multi-atom kernel and let any single-atom convenience - wrapper call the same core where efficient; -- add independent tests against direct pointwise profile sums. - -This is a plausible and valuable `atomref` capability because it converts the -package’s reference radial functions into spatial promolecular densities. -Nevertheless, premature implementation would risk choosing the wrong array, -periodicity, and grid conventions. - -### 16.3 Other possible future work - -Future candidates, only after demonstrated need: - -- vectorized one-dimensional profile evaluation; -- user-supplied radial sets with the same interface; -- ionic profile datasets and state selectors; -- additional reference functions; -- direct helpers for stockholder initialization; -- optional integration utilities for common crystallographic grid formats. - -Do not turn this list into active tasks during `0.2.0` or `0.2.1`. - -## 17. Suggested commit sequence - -Preferred linear history for `0.2.0`: - -```text -refactor: generalize built-in dataset loading -feat(data): add neutral proatomic density snapshot -feat: add proatomic density evaluation -feat: add two-mode proatomic IAS position estimates -docs: add IAS method study and proatomic density feature notebook -chore: prepare atomref 0.2.0 -``` - -Preferred linear history for `0.2.1`: - -```text -docs: make notebooks executable documentation -docs: complete typed API reference -docs: reposition atomref documentation -chore: prepare atomref 0.2.1 -``` - -Commits may be combined when changes are inseparable, but do not combine an -entire release into one opaque commit. - -Do not rewrite accepted history. - -## 18. Agent completion report template - -Use this template after each stage: - -```text -Stage: - - -Implemented: - - ... - -Files added: - - ... - -Files removed: - - ... - -Files changed: - - ... - -Validation: - - : - -Plan deviations: - - none - or - - proposed design: - - issue found: - - replacement: - - justification: - - protecting tests: - -Open questions: - - ... - -Suggested commit: - - -Next step: - -``` - -## 19. Open review points for this draft - -Review before the corresponding implementation stage: - -1. Final atomref-side density dataset ID. -2. Final radial loaded-set class name: - - `ElementRadialSet`; - - or `ProatomicDensitySet`. -3. Exact generic `get_builtin_set()` return annotation and scalar narrowing - helper names. -4. Final public spelling of the three pairwise functions and result type. -5. Final status and method vocabulary, without changing their required - distinctions. -6. Whether `IAS_MINIMUM_RESOLUTION_BOHR` and the practical competitive-depth - threshold should be public constants or provenance-only fields. -7. Whether the minimum result should expose one explicit runner-up object or - flattened optional fields. -8. Whether the supporting notebook remains a permanent documentation artifact - after `0.2.1` adopts direct notebook rendering. -9. Final `0.2.1` notebook renderer and normalization details. -10. Final MkDocs/mkdocstrings visual settings after a local rendered preview. -11. The future grid-density requirements once actual target formats are - collected. -12. A future curated molecular-QTAIM benchmark for comparing midpoint, - equal-contribution, and promolecular-minimum coordinates. - -These are review points, not permission to change the fixed cutoff, default -mode, homonuclear midpoint, explicit mode separation, provenance, range, -dataset-unification, unit, or compatibility contracts. - -## 20. Plan closeout and replanning record - -This section is intentionally left as a template while the plan is `ACTIVE`. -The implementation agent MUST complete the appropriate subsection before -changing the lifecycle state to `COMPLETE_AWAITING_REVIEW` or -`REPLAN_REQUIRED`. - -### 20.1 Completion record - -```text -Lifecycle state: - COMPLETE_AWAITING_REVIEW - -Completed releases/stages: - - atomref 0.2.0 was accepted and tagged before this implementation. - - Stage 6 completed the merged atomref 0.2.1 documentation, notebook, - packaging, and release-candidate contract. - -Final commits or pull requests: - - docs: render notebooks directly - - docs: complete typed API reference - - docs: reposition atomref documentation - - chore: add complete user extras - - chore: prepare atomref 0.2.1 (this commit) - - local commits only; no pull request, push, tag, or publication action - -Acceptance checks: - - flake8 src tests tools: passed - - python tools/check_registry.py: passed - - python tools/check_notebooks.py: five notebooks passed in disposable - standard-Jupyter kernels; committed sources were unchanged - - temporary failing-notebook probe: failed with CellExecutionError as required; - the probe source hash was unchanged and the probe was discarded - - python tools/gen_readme.py --check: passed - - python -m pytest -q: 267 passed - - mkdocs build --strict: passed with execute=False for all five notebooks; - source hashes were unchanged and rendered code, inline/display mathematics, - saved text, and saved PNG output were inspected successfully - - rendered API inspection: all 65 top-level exports, typed signatures, - class constructor defaults, structured sections, attributes, examples, and - intended special methods rendered; lookup_value() has a standalone Examples - section and exactly two Raises rows; internal resolver and __post_init__ - members remained excluded - - python tools/release_check.py: passed, including build, twine, distribution - content checks, conventional 0644 regular-file modes, and clean - base/notebook/all artifact installations from a clean committed HEAD export - - runtime AST comparison against Stage 6 baseline after removing docstrings: - unchanged across all nine documentation-edited runtime modules - - git diff --exit-code 3490e348b6a3824bfaa0e60c3cda88f82595106c -- - src/atomref/data: passed - - history audit: the Stage 6 sequence descends from the recorded baseline - 3490e348b6a3824bfaa0e60c3cda88f82595106c without amendment; the v0.2.0 tag - object remained c5b5901cc23ad2914a546c3c61a9feee53d41985 and still peeled to - 1fba665a41d5a7b2ee97d27701347113b277cd4f - -Binding requirements not implemented: - - none - -Plan deviations: - - none; the preferred mkdocs-jupyter path passed the focused mathematics - prototype, so the nbconvert fallback was not used - -Future-reference items intentionally not touched: - - ions and additional atomic properties - - vectorized, grid, and molecular-density APIs - - exact-QTAIM functionality and future molecular benchmarks - - optional post-0.2.0 architecture ideas in Section 15 - -Known limitations remaining within the accepted release: - - profiles remain neutral, spherical H–Lr references with scalar evaluation - over the documented 0–20 bohr domain - - pairwise modes remain reference-atom proxies rather than exact molecular - QTAIM surfaces, and minimum mode may return a typed non-result - - X–H support remains the documented provisional parent-element dataset scope - -Implementation agent conclusion: - - every binding Stage 6 and Section 14.6 item is implemented and validated; - atomref 0.2.1 is ready for independent diff, science, documentation, and - artifact review as a local commit sequence without being tagged or published - -External review required: - - verify repository diff and history - - verify acceptance checklist - - verify scientific and unit contracts - - verify documentation and packaged artifacts - - either set state to CLOSED or return the plan to ACTIVE with corrections -``` - -### 20.2 Replanning-required record - -```text -Lifecycle state: - REPLAN_REQUIRED - -Blocked stage: - - ... - -Observed problem: - - ... - -Why the current plan cannot safely continue: - - ... - -Evidence and reproduction: - - ... - -Approaches attempted: - - ... - -Partial changes present: - - ... - -Repository/branch safety state: - - clean / partial changes retained / revert recommended / other - -Decisions requiring a new plan: - - ... - -Suggested replanning questions: - - ... - -Implementation agent conclusion: - - stop implementation and request external review/replanning -``` - -### 20.3 External reviewer disposition - -```text -Reviewer: - - ... - -Review date: - - ... - -Disposition: - - CLOSED — completed - - CLOSED — superseded/abandoned - - ACTIVE — corrections required under this plan - - REPLAN_REQUIRED — a replacement plan must be prepared - -Review notes: - - ... -``` +# atomref development plan + +> **Status:** No active near-term development plan +> **Plan lifecycle state:** `CLOSED` +> **Current completed development line:** `atomref 0.2.1` +> **Scientific data source:** `atomref-proatoms 2.0.0`, dataset `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2` + +## 1. Current state + +The `0.2.x` development cycle is complete. Its scientific implementation, +documentation, notebooks, packaging, and release preparation have been +independently reviewed and accepted. + +`atomref` is now a small, dependency-free-core package providing cited atomic +reference data and frozen spherical free-atom electron densities for +crystallographic, quantum-chemical, and molecular-structure algorithms. + +The completed package scope includes: + +- scalar radii and X–H reference data with explicit provenance and policy + behavior; +- generic discovery and loading of packaged scalar and radial datasets; +- neutral spherical proatomic-density profiles for H–Lr; +- scalar density evaluation over the documented `0–20 bohr` domain; +- a stable neutral-proatom boundary estimate; +- an optional resolution-limited promolecular line-density minimum; +- complete public API documentation, adoption-oriented user documentation, + directly rendered notebooks, and validated release artifacts. + +There is currently **no active implementation stage, scheduled feature release, +or close-term roadmap**. Routine maintenance may address confirmed defects, +compatibility problems, documentation corrections, packaging issues, or +security concerns. Any substantial feature requires a new, separately reviewed +development plan. + +Completed `0.2.x` implementation history belongs in the changelog, Git history, +release records, documentation, and tests rather than in this file. + +## 2. Durable project constraints + +Future work should preserve the following principles unless the repository owner +explicitly approves a revised contract: + +- Keep the core runtime pure Python, without required third-party dependencies + or runtime network access. +- Preserve explicit dataset identity, provenance, licensing, units, supported + ranges, and charge/state scope. +- Do not silently substitute elements, correlate missing profiles, infer ionic + states, or apply scalar fallback/transfer policies to radial profiles. +- Keep packaged datasets integrated through the existing registry and + `get_builtin_set()` machinery rather than adding unrelated loading paths. +- Preserve the distinction between the stable proatomic-boundary mode and the + optional promolecular-minimum mode. Neither should be represented as an exact + molecular QTAIM boundary or critical point. +- Avoid speculative frameworks and abstractions. Add dependencies, public + objects, files, or architectural layers only when required by demonstrated + use cases. +- Preserve scientific and numerical behavior across documentation, packaging, + and maintenance-only releases unless a confirmed defect is documented and + protected by regression tests. +- Keep `docs/index.md` as the source of `README.md`, maintain one source for each + notebook, and continue validating installed wheel and source-distribution + contents. + +## 3. Current known scope limits + +The present implementation is intentionally limited: + +- proatomic profiles are neutral, spherical reference densities for H–Lr; +- scalar profile evaluation is supported only within `0–20 bohr`; +- no ionic, fractional-charge, environment-dependent, or self-consistent + stockholder model is provided; +- pairwise boundary and minimum results are reference-atom proxies, not + molecular-density QTAIM results; +- no vectorized array API, three-dimensional density-grid generator, periodic + image summation, or molecular-density construction is included; +- X–H support retains its documented dataset and parent-element limitations. + +These are accepted scope boundaries, not active defects. + +## 4. Possible future directions + +The following ideas are non-binding. They do not authorize implementation and +have no assigned order, milestone, or version. + +### Atomic data and states + +- Add ionic proatomic datasets with an evidence-based, explicit state-selection + design. +- Add further scalar or radial atomic reference properties when a concrete + downstream use case and suitable source data exist. +- Support user-supplied radial datasets while preserving the same metadata, + validation, and evaluation contracts. + +### Numerical and spatial APIs + +- Add optional NumPy-based vectorized profile evaluation if profiling and real + workloads justify it. +- Generate single-atom and promolecular densities on three-dimensional grids. +- Design nonperiodic and periodic grid support together, including triclinic + cells, explicit units, memory/chunking policy, and correct periodic-image + enumeration. +- Add lightweight stockholder-initialization or common crystallographic-grid + helpers where they provide clear interoperability value. + +### Scientific validation + +- Build a curated molecular-density/QTAIM benchmark comparing midpoint, + equal-proatom boundary, and promolecular-minimum coordinates. +- Reassess cutoff and practical-resolution policies only from benchmark + evidence, without silently changing established semantics. +- Optimize repeated pair evaluation only after downstream profiling identifies + a meaningful bottleneck. + +### Maintenance and engineering + +- Introduce static type checking or small internal cleanups when they provide + clear maintenance value. +- Strengthen mixed-license and artifact validation as additional datasets are + added. +- Revisit registry or storage abstractions only when a new real data family + cannot be represented cleanly by the current design. + +## 5. Starting future development + +Before implementing any direction above: + +1. Select one concrete problem and document the user or scientific need. +2. Define the data source, provenance, scientific meaning, supported domain, + public API, compatibility constraints, dependencies, and explicit + exclusions. +3. Prepare a small staged plan with acceptance tests and release criteria. +4. Review and approve that plan before coding. +5. Mark the new plan `ACTIVE`; until then, this file remains a closed, + non-actionable roadmap. + +Do not interpret the possible directions in this file as committed work or as +permission to begin implementation. From 51249041fbdbbf07af2a1dce6d1c4b764ac0b73a Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 02:52:12 +0300 Subject: [PATCH 16/23] docs: silence MkDocs 2 migration warning --- .github/workflows/ci.yml | 2 + .github/workflows/docs.yml | 2 + CHANGELOG.md | 2 + pyproject.toml | 2 + tests/meta/test_packaging_metadata.py | 9 ++++ tests/meta/test_release_tools.py | 61 +++++++++++++++++++++++++++ tools/release_check.py | 22 ++++++++-- 7 files changed, 97 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 030c406..e473d49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,8 @@ jobs: - name: Check README sync run: python tools/gen_readme.py --check - name: Build docs + env: + NO_MKDOCS_2_WARNING: "true" run: mkdocs build --strict - name: Verify docs did not rewrite notebooks run: git diff --exit-code -- docs/notebooks diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 586b767..33d0894 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -28,6 +28,8 @@ jobs: - name: Check README sync run: python tools/gen_readme.py --check - name: Build docs + env: + NO_MKDOCS_2_WARNING: "true" run: mkdocs build --strict - name: Verify docs did not rewrite notebooks run: git diff --exit-code -- docs/notebooks diff --git a/CHANGELOG.md b/CHANGELOG.md index 54380b4..b308928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ test, lint, build, and release tools out of `all`. - Updated CI, source-distribution checks, and release preparation for the final single-source notebook layout and isolated artifact installations. +- Kept MkDocs below version 2 across documentation and notebook extras, and + suppressed Material's MkDocs 2 migration banner in automated strict builds. - Build release artifacts from a clean committed-source extraction and reject nonstandard executable modes on ordinary wheel and source-distribution files. - Removed generated notebook Markdown, the custom exporter, export-sync tests, diff --git a/pyproject.toml b/pyproject.toml index 869b257..000741d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ test = [ notebook = [ "ipykernel>=6.29", "matplotlib>=3.8", + "mkdocs>=1.6,<2", "mkdocs-jupyter>=0.26,<0.27", "nbclient>=0.10,<0.12", "nbformat>=5.10,<6", @@ -62,6 +63,7 @@ notebook = [ all = [ "ipykernel>=6.29", "matplotlib>=3.8", + "mkdocs>=1.6,<2", "mkdocs-jupyter>=0.26,<0.27", "nbclient>=0.10,<0.12", "nbformat>=5.10,<6", diff --git a/tests/meta/test_packaging_metadata.py b/tests/meta/test_packaging_metadata.py index 0e67fd1..94e21e7 100644 --- a/tests/meta/test_packaging_metadata.py +++ b/tests/meta/test_packaging_metadata.py @@ -13,6 +13,7 @@ EXPECTED_NOTEBOOK_DEPENDENCIES = [ "ipykernel>=6.29", "matplotlib>=3.8", + "mkdocs>=1.6,<2", "mkdocs-jupyter>=0.26,<0.27", "nbclient>=0.10,<0.12", "nbformat>=5.10,<6", @@ -51,3 +52,11 @@ def test_docs_extra_contains_only_used_documentation_tooling() -> None: assert "mkdocs-include-markdown-plugin" not in docs_requirements assert "tomli" not in docs_requirements + + +def test_mkdocs_stays_on_the_supported_1_x_line() -> None: + project = _project_metadata() + extras = project["optional-dependencies"] + + for extra in ("docs", "notebook", "all"): + assert extras[extra].count("mkdocs>=1.6,<2") == 1 diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index 2569e48..4aa2186 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -13,6 +13,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2] CHECK_DIST_PATH = REPO_ROOT / "tools" / "check_dist.py" +RELEASE_CHECK_PATH = REPO_ROOT / "tools" / "release_check.py" SNAPSHOT_PATH = REPO_ROOT / "src" / "atomref" / "data" / ( "proatomic_density_neutral.zip" ) @@ -33,6 +34,14 @@ sys.modules[spec.name] = check_dist spec.loader.exec_module(check_dist) +release_spec = importlib.util.spec_from_file_location( + "release_check_tool", RELEASE_CHECK_PATH +) +assert release_spec is not None and release_spec.loader is not None +release_check = importlib.util.module_from_spec(release_spec) +sys.modules[release_spec.name] = release_check +release_spec.loader.exec_module(release_check) + def _snapshot_with_modified_csv(payload: bytes) -> bytes: with zipfile.ZipFile(io.BytesIO(payload), mode="r") as archive: @@ -176,3 +185,55 @@ def test_release_check_help() -> None: ) assert "release-preparation checks" in result.stdout assert "--skip-install-checks" in result.stdout + + +def test_release_docs_build_suppresses_material_banner( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls: list[tuple[tuple[str, ...], dict[str, object]]] = [] + + def record(*args: str, **kwargs: object) -> None: + calls.append((args, kwargs)) + + monkeypatch.setattr(release_check, "_run", record) + release_check._build_docs() + + assert calls == [ + ( + ("mkdocs", "build", "--strict"), + {"extra_env": {"NO_MKDOCS_2_WARNING": "true"}}, + ) + ] + + +def test_release_command_merges_extra_environment( + monkeypatch: pytest.MonkeyPatch, +) -> None: + captured: dict[str, object] = {} + + def record(args: tuple[str, ...], **kwargs: object) -> None: + captured["args"] = args + captured.update(kwargs) + + monkeypatch.setenv("ATOMREF_PARENT_ENV", "preserved") + monkeypatch.setattr(release_check.subprocess, "run", record) + release_check._run( + "mkdocs", + "build", + extra_env={"NO_MKDOCS_2_WARNING": "true"}, + ) + + environment = captured["env"] + assert isinstance(environment, dict) + assert environment["ATOMREF_PARENT_ENV"] == "preserved" + assert environment["NO_MKDOCS_2_WARNING"] == "true" + + +@pytest.mark.parametrize( + "workflow", + [REPO_ROOT / ".github/workflows/ci.yml", REPO_ROOT / ".github/workflows/docs.yml"], +) +def test_docs_workflows_suppress_material_banner(workflow: Path) -> None: + text = workflow.read_text(encoding="utf-8") + + assert 'NO_MKDOCS_2_WARNING: "true"' in text diff --git a/tools/release_check.py b/tools/release_check.py index ea45d36..2ca255a 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -10,6 +10,7 @@ from __future__ import annotations import argparse +import os from pathlib import Path import shutil import subprocess @@ -21,13 +22,28 @@ REPO_ROOT = Path(__file__).resolve().parents[1] DIST_DIR = REPO_ROOT / "dist" BUILD_DIR = REPO_ROOT / "build" +MKDOCS_ENV = {"NO_MKDOCS_2_WARNING": "true"} -def _run(*args: str, cwd: Path = REPO_ROOT) -> None: +def _run( + *args: str, + cwd: Path = REPO_ROOT, + extra_env: dict[str, str] | None = None, +) -> None: """Run one subprocess command in the selected working directory.""" print(f"+ [cwd={cwd}]", " ".join(args)) - subprocess.run(args, cwd=cwd, check=True) + environment = None + if extra_env is not None: + environment = os.environ.copy() + environment.update(extra_env) + subprocess.run(args, cwd=cwd, env=environment, check=True) + + +def _build_docs() -> None: + """Build strict docs without Material's inapplicable MkDocs 2 banner.""" + + _run("mkdocs", "build", "--strict", extra_env=MKDOCS_ENV) def _fresh_build_dirs() -> None: @@ -120,7 +136,7 @@ def main() -> int: _run(sys.executable, "tools/gen_readme.py", "--check") _run(sys.executable, "-m", "pytest", "-q") if not args.skip_docs: - _run("mkdocs", "build", "--strict") + _build_docs() _fresh_build_dirs() _build_from_committed_head() From baf93d88d09726e6e85fcef8eb406b1faa90319c Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 03:07:38 +0300 Subject: [PATCH 17/23] docs: remove internal development wording --- NOTICE.md | 6 +- README.md | 18 +++--- docs/api/xh.md | 3 +- docs/datasets/covalent_radius.md | 2 +- docs/datasets/index.md | 4 +- docs/datasets/van_der_waals_radius.md | 6 +- docs/datasets/xh_bond_length.md | 9 ++- docs/dev/architecture.md | 2 +- docs/dev/data_curation.md | 2 +- docs/dev/ias_method_selection.md | 23 ++++--- docs/guide/install.md | 2 +- docs/guide/proatomic_density.md | 4 +- docs/index.md | 18 +++--- docs/notebooks/01-quickstart.ipynb | 3 +- .../04-ias-method-selection-study.ipynb | 61 ++++++++++--------- .../05-proatomic-density-and-ias.ipynb | 8 +-- 16 files changed, 84 insertions(+), 87 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index f1a9b1e..b4b77e4 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -8,13 +8,13 @@ License: LGPL-3.0-or-later (see LICENSE and COPYING) ## Third-party material -The initial scaffold reuses and adapts data tables and design ideas from the -Delone Commons `molcryst` repository, also authored by Ivan Chernyshov. +Some data tables and design ideas were adapted from earlier chemistry and +structure-analysis software authored by Ivan Chernyshov. ### atomref-proatoms data `proatomic_density_neutral.zip` is a neutral H–Lr (Z=1–103), 20-bohr-truncated -consumer snapshot derived from: +packaged snapshot derived from: Ivan Yu. Chernyshov, *atomref-proatoms: spherical atomic and ionic reference densities*, release 2.0.0, dataset diff --git a/README.md b/README.md index 2d80602..e012061 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ Documentation: https://delonecommons.github.io/atomref -`atomref` provides cited atomic values and frozen spherical free-atom electron -densities through a small Python API for crystallographic, quantum-chemical, -and molecular-structure algorithms. +`atomref` provides cited atomic properties and frozen spherical free-atom +electron densities through a small Python API for crystallographic, +quantum-chemical, and molecular-structure algorithms. Use it when your software needs dependable atomic radii, X–H reference lengths, neutral proatomic densities, or pairwise reference-atom boundaries without @@ -125,7 +125,7 @@ and provisional X–H datasets. Registry metadata separates the requested quantity from scientific classification and from whether a dataset is a direct target or transfer support. -The neutral proatomic profiles are a deterministic consumer snapshot of +The neutral proatomic profiles are a deterministic packaged snapshot of the `atomref-proatoms` 2.0.0 dataset `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`. They cover H–Lr and record the PBE0, self-consistent spherical fractional-occupation UKS, spin-free one-electron @@ -180,12 +180,12 @@ Custom element-indexed scalar sets can participate in the same policy layer. Radial profiles deliberately do not: no scalar `ValuePolicy`, neighboring- element substitution, or fitted correlation is applied to density data. -## Relationship to Delone Commons +## Use in scientific software -`atomref` is a standalone package. Within Delone Commons it is intended for -chemistry-aware consumers such as `molcryst` and future `chemvoro` workflows. -General mathematical packages such as `pyvoro2` and `pbcgraph` remain -independent of atomic-reference policy. +`atomref` is a standalone package for physicochemical and structural-analysis +software that needs curated atomic properties, proatomic densities, or explicit +reference-data policy. Purely mathematical packages can remain independent of +those choices until a consuming application needs atomic context. ## Maintainer checks diff --git a/docs/api/xh.md b/docs/api/xh.md index 8d78357..cf21a6b 100644 --- a/docs/api/xh.md +++ b/docs/api/xh.md @@ -1,7 +1,6 @@ # atomref.xh -This module provides the provisional X–H bond-length helpers available in the -current release line. +This module provides focused X–H bond-length helpers. It is intentionally narrow: diff --git a/docs/datasets/covalent_radius.md b/docs/datasets/covalent_radius.md index 5e022fd..96fee51 100644 --- a/docs/datasets/covalent_radius.md +++ b/docs/datasets/covalent_radius.md @@ -6,7 +6,7 @@ legacy support dataset. ## Cordero covalent radii (`cordero2008`) -This is the main covalent-radius target set in the current release line. +This is the default built-in covalent-radius target set. - **What it is:** a broad covalent-radius compilation based mainly on crystallographic bond distances. diff --git a/docs/datasets/index.md b/docs/datasets/index.md index 5edeb26..d48d4ed 100644 --- a/docs/datasets/index.md +++ b/docs/datasets/index.md @@ -33,8 +33,8 @@ If you want the packaged values themselves, use `get_builtin_set(...)`, ## Neutral proatomic-density snapshot The `proatomic_density` quantity currently contains the dataset -`pbe0_sfx2c_dyallv4z_h-lr_neutral_v2`: a neutral H–Lr (Z = 1–103), truncated -consumer snapshot built from `atomref-proatoms` 2.0.0 dataset +`pbe0_sfx2c_dyallv4z_h-lr_neutral_v2`: a truncated neutral H–Lr (Z = 1–103) +packaged snapshot of the `atomref-proatoms` 2.0.0 dataset `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`. Its public radial domain is 0–20 bohr; one original source point above 20 bohr is retained only to bracket that limit. The native density unit is electron/bohr³. diff --git a/docs/datasets/van_der_waals_radius.md b/docs/datasets/van_der_waals_radius.md index 3013d57..112626f 100644 --- a/docs/datasets/van_der_waals_radius.md +++ b/docs/datasets/van_der_waals_radius.md @@ -27,7 +27,7 @@ contacts. ## Alvarez van der Waals radii (`alvarez2013`) -This is the main van der Waals target set in the current release line. +This is the default built-in van der Waals target set. - **What it is:** a broad structural vdW set derived from statistical analysis of many interatomic distances in the Cambridge Structural Database. @@ -53,5 +53,5 @@ A compatibility-oriented table used historically in CSD tools. - **What it is:** an older practical vdW table with placeholder conventions. - **Coverage:** broad practical coverage, but not a modern scientific target set. -- **How `atomref` uses it:** support-only data for legacy compatibility and - future migration work. +- **How `atomref` uses it:** support-only data for explicit compatibility with + legacy workflows; it is not the default scientific target set. diff --git a/docs/datasets/xh_bond_length.md b/docs/datasets/xh_bond_length.md index 5f8b8bb..fd38fc8 100644 --- a/docs/datasets/xh_bond_length.md +++ b/docs/datasets/xh_bond_length.md @@ -1,7 +1,7 @@ # X–H bond length -The `xh_bond_length` quantity is a small provisional addition in the current -release line. +The `xh_bond_length` quantity intentionally covers a narrow set of reference +data. Its purpose is not to claim a complete literature survey of X–H bond lengths. Instead, it provides a stable, provenance-aware starting point for @@ -35,6 +35,5 @@ That means the package draws a sharp line between: ## Scope note This is intentionally a small feature rather than a complete literature model -of X–H bonding. `0.2.1` retains the accepted sparse target and explicit fitted -policy; it does not add broader X–H datasets or claim environment-specific bond -lengths. +of X–H bonding. The documented sparse target and explicit fitted policy do not +claim broader X–H coverage or environment-specific bond lengths. diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 1441715..d2cd1e0 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -118,7 +118,7 @@ placeholder status itself. Instead, its provenance is carried by The package deliberately avoids a large object graph or a chemistry-specific DSL. A quantity wrapper is usually only a thin adapter over the generic policy core. That keeps the internals easy to test and lets other scientific packages reuse -`atomref` without bringing in the rest of the Delone Commons stack. +`atomref` without requiring a larger application stack. ## Documentation and distribution boundary diff --git a/docs/dev/data_curation.md b/docs/dev/data_curation.md index cf1e965..c3c3979 100644 --- a/docs/dev/data_curation.md +++ b/docs/dev/data_curation.md @@ -25,7 +25,7 @@ even when a radii policy uses it as support for a fitted van der Waals value. ## Neutral proatomic snapshot `proatomic_density:pbe0_sfx2c_dyallv4z_h-lr_neutral_v2` is a deterministic -consumer snapshot of `atomref-proatoms` 2.0.0 dataset +packaged snapshot of the `atomref-proatoms` 2.0.0 dataset `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`. It selects the neutral H–Lr profiles, retains the source grid through the first point above 20 bohr so the public endpoint remains bracketed, and does not interpolate or alter stored density diff --git a/docs/dev/ias_method_selection.md b/docs/dev/ias_method_selection.md index 35f9a8d..a854859 100644 --- a/docs/dev/ias_method_selection.md +++ b/docs/dev/ias_method_selection.md @@ -1,14 +1,13 @@ # Pairwise boundary and IAS-proxy method selection -Stage 4 originally considered a numerically exhaustive search for every local -minimum of the neutral-promolecular line density between two atoms. The review -work showed that this contract is possible, but it makes interpolation-scale -features, one-ULP knot behavior, sub-ULP event ordering, and exact tie handling -part of the public science. +An exhaustive numerical study considered returning every local minimum of the +neutral-promolecular line density between two atoms. Although this contract is +possible, it makes interpolation-scale features, one-ULP knot behavior, sub-ULP +event ordering, and exact tie handling part of the public science. -That is more precision than the intended geometry workflow needs. The revised -Stage 4 keeps two explicit methods instead of hiding one policy behind a single -number. +That is more precision than the intended geometry workflow needs. `atomref` +therefore exposes two explicit methods instead of hiding one policy behind a +single number. ## Recommended default: proatomic boundary @@ -59,7 +58,7 @@ That disagreement is expected rather than a solver defect. The executed study compared the practical minimum search with a slower `0.001 bohr` reference over 300 deterministic H–Lr cases and seven adversarial -cases from the Stage 4A review. +cases selected from the exhaustive all-minima analysis. - All 298 cases where both methods returned a minimum agreed within `0.01 bohr`. @@ -67,7 +66,7 @@ cases from the Stage 4A review. - Two extremely short-distance reference minima were intentionally reported as unresolved because they were narrower than the public practical resolution and lay immediately beside a nucleus. -- On the recorded review machine, the research prototype took roughly +- On the machine used for the study, the standalone implementation took roughly `0.05–0.06 ms` per cached boundary estimate and about `0.8 ms` per cached practical minimum estimate. @@ -75,7 +74,7 @@ These figures are evidence, not portable performance guarantees. ## Why exhaustive all-minima search is not the default -The exact Stage 4A investigation found real edge cases: +The exhaustive investigation found real edge cases: - a minimum only 27 binary64 coordinates from a profile knot; - one-ULP inversions at exact knots; @@ -85,7 +84,7 @@ The exact Stage 4A investigation found real edge cases: - value errors large enough to complicate exact global-tie classification. Those facts matter under an “expose every local minimum” contract. They do not -materially improve a stable pairwise divider, and the revised minimum mode +materially improve a stable pairwise divider, and the practical minimum mode coalesces or rejects structure below its declared resolution. ## Public API diff --git a/docs/guide/install.md b/docs/guide/install.md index 5a44a6f..2d420ce 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -61,7 +61,7 @@ pip install -e ".[test,dev,docs,notebook]" - `notebook` provides direct notebook rendering, Jupyter execution, a Python kernel, and plotting. -Run the full local release-candidate validation with: +Run the full local release validation with: ```bash python tools/release_check.py diff --git a/docs/guide/proatomic_density.md b/docs/guide/proatomic_density.md index c584459..d6fccd8 100644 --- a/docs/guide/proatomic_density.md +++ b/docs/guide/proatomic_density.md @@ -56,7 +56,7 @@ provenance in downstream work. ## Pairwise estimates -Three functions expose the Stage 4 pairwise model: +Three functions expose the pairwise IAS estimators: ```python boundary = ar.estimate_proatomic_boundary("C", "O", 1.5, distance_unit="bohr") @@ -137,4 +137,4 @@ for the public workflows, the [IAS method-selection notebook](../notebooks/04-ias-method-selection-study.ipynb) for the executed supporting analysis, and the [durable method summary](../dev/ias_method_selection.md) for the numerical -decision and archived all-minima limitations. +decision and documented limitations of exhaustive all-minima enumeration. diff --git a/docs/index.md b/docs/index.md index 72b3bef..0c06bbc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,9 +6,9 @@ [![Python Versions](https://img.shields.io/pypi/pyversions/atomref.svg)](https://pypi.org/project/atomref/) [![License](https://img.shields.io/pypi/l/atomref.svg)](https://github.com/DeloneCommons/atomref/blob/main/LICENSE) -`atomref` provides cited atomic values and frozen spherical free-atom electron -densities through a small Python API for crystallographic, quantum-chemical, -and molecular-structure algorithms. +`atomref` provides cited atomic properties and frozen spherical free-atom +electron densities through a small Python API for crystallographic, +quantum-chemical, and molecular-structure algorithms. Use it when your software needs dependable atomic radii, X–H reference lengths, neutral proatomic densities, or pairwise reference-atom boundaries without @@ -123,7 +123,7 @@ and provisional X–H datasets. Registry metadata separates the requested quantity from scientific classification and from whether a dataset is a direct target or transfer support. -The neutral proatomic profiles are a deterministic consumer snapshot of +The neutral proatomic profiles are a deterministic packaged snapshot of the `atomref-proatoms` 2.0.0 dataset `pbe0_sfx2c_dyallv4z_h-lr_spherical_v2`. They cover H–Lr and record the PBE0, self-consistent spherical fractional-occupation UKS, spin-free one-electron @@ -178,12 +178,12 @@ Custom element-indexed scalar sets can participate in the same policy layer. Radial profiles deliberately do not: no scalar `ValuePolicy`, neighboring- element substitution, or fitted correlation is applied to density data. -## Relationship to Delone Commons +## Use in scientific software -`atomref` is a standalone package. Within Delone Commons it is intended for -chemistry-aware consumers such as `molcryst` and future `chemvoro` workflows. -General mathematical packages such as `pyvoro2` and `pbcgraph` remain -independent of atomic-reference policy. +`atomref` is a standalone package for physicochemical and structural-analysis +software that needs curated atomic properties, proatomic densities, or explicit +reference-data policy. Purely mathematical packages can remain independent of +those choices until a consuming application needs atomic context. ## Maintainer checks diff --git a/docs/notebooks/01-quickstart.ipynb b/docs/notebooks/01-quickstart.ipynb index 3758d99..50299ef 100644 --- a/docs/notebooks/01-quickstart.ipynb +++ b/docs/notebooks/01-quickstart.ipynb @@ -13,8 +13,7 @@ "**Prerequisites.** Install `atomref`; no optional dependencies or prior\n", "knowledge of its registry and policy internals are required.\n", "\n", - "The density and pairwise neutral-proatom workflows have their own focused\n", - "notebook later in this gallery." + "A separate notebook covers the density and pairwise neutral-proatom workflows." ] }, { diff --git a/docs/notebooks/04-ias-method-selection-study.ipynb b/docs/notebooks/04-ias-method-selection-study.ipynb index bed2275..291d78f 100644 --- a/docs/notebooks/04-ias-method-selection-study.ipynb +++ b/docs/notebooks/04-ias-method-selection-study.ipynb @@ -6,11 +6,11 @@ "metadata": {}, "source": [ "\n", - "# Choosing a practical neutral-proatom IAS proxy\n", + "# Choosing a practical neutral-proatom interatomic-surface (IAS) proxy\n", "\n", - "This notebook is supporting information for the Stage 4 numerical decision in\n", - "`atomref`. It compares two pairwise quantities derived from the packaged neutral\n", - "spherical proatomic densities:\n", + "This notebook documents the numerical study used to select practical parameters\n", + "for the IAS estimators in `atomref`. It compares two pairwise quantities derived\n", + "from the packaged neutral spherical proatomic densities:\n", "\n", "1. a **stable proatomic boundary**, based on equal proatomic contributions or a\n", " midpoint between fixed low-density contours;\n", @@ -18,9 +18,10 @@ " proatomic contributions remain above the cutoff and only to a declared\n", " practical spatial resolution.\n", "\n", - "The notebook does **not** implement the public API and does not claim to locate\n", - "an exact QTAIM zero-flux surface. Its helper functions are research prototypes\n", - "used to test the revised contract.\n", + "The calculations below form a standalone numerical sensitivity study rather\n", + "than the package API and do not claim to locate an exact QTAIM zero-flux\n", + "surface. Local helper functions isolate comparisons behind the documented\n", + "cutoff and resolution policy.\n", "\n", "**Prerequisites.** This supporting study assumes familiarity with radial\n", "electron densities, one-dimensional minimization, and the distinction between\n", @@ -73,7 +74,7 @@ " (2011), 852–867, [doi:10.1021/jp1093417](https://doi.org/10.1021/jp1093417).\n", "\n", "These references motivate the interpretation. The numerical experiments below\n", - "justify only the implementation policy for the supplied neutral-proatom data;\n", + "support only the documented numerical policy for the supplied neutral-proatom data;\n", "they do not validate either mode as an exact QTAIM surface.\n" ] }, @@ -672,9 +673,9 @@ "\n", "## Why homonuclear symmetry overrides raw shell minima\n", "\n", - "For Li–Li at 5 bohr, the exact all-minima investigation found symmetric\n", + "For Li–Li at 5 bohr, a dense local-minimum scan finds symmetric\n", "off-centre minima slightly below the midpoint. Returning either off-centre\n", - "point would be a poor pairwise separator for identical atoms. Both revised\n", + "point would be a poor pairwise separator for identical atoms. Both estimator\n", "modes therefore return $R/2$ and preserve the raw profile behaviour only as a\n", "diagnostic.\n" ] @@ -846,14 +847,14 @@ "\n", "## Practical minimum search against a slower reference\n", "\n", - "The production proposal is not an all-minima theorem. It asks whether a valley\n", - "is resolved at 0.01 bohr and checks it at two grid scales, with one finer\n", - "fallback. The comparison below uses 300 deterministic H–Lr cases, plus seven\n", - "adversarial cases discovered during Stage 4A, against a slower 0.001-bohr\n", + "The public minimum mode is not intended to enumerate every mathematical local\n", + "minimum. It asks whether a valley is resolved at 0.01 bohr and checks it at two\n", + "grid scales, with one finer fallback. The comparison below uses 300 deterministic\n", + "H–Lr cases, plus seven targeted numerical edge cases, against a slower 0.001-bohr\n", "fine-grid search over the same cutoff-bounded interval.\n", "\n", "A dense-reference minimum may be intentionally rejected when it is narrower\n", - "than the public resolution or lies immediately beside a nucleus at an extreme\n", + "than the documented resolution or lies immediately beside a nucleus at an extreme\n", "short distance.\n" ] }, @@ -966,7 +967,7 @@ "thresholds. The important point is the scale difference: the boundary mode\n", "needs one cached contour calculation plus a monotone bisection, while the\n", "optional minimum mode remains below about a millisecond per ordinary pair in\n", - "this pure-Python prototype.\n" + "this standalone pure-Python study.\n" ] }, { @@ -1024,24 +1025,24 @@ "metadata": {}, "source": [ "\n", - "## What the exhaustive Stage 4A work established\n", + "## Why exhaustive local-minimum enumeration is out of scope\n", "\n", - "The archived Stage 4A design, validation report, and two independent reviews\n", - "remain useful supporting evidence. They showed that an exact “return every\n", + "The exhaustive analysis showed that an exact “return every\n", "mathematical local minimum” contract really does require disproportionate\n", "numerical machinery:\n", "\n", - "| Case | Exact-topology observation | Product consequence |\n", + "| Case | Exact-topology observation | Consequence for the numerical policy |\n", "|---|---|---|\n", "| H–F near a knot | A minimum occurred 27 binary64 coordinates below a knot and needed high-precision derivative signs. | Important only when every microminimum is binding. |\n", - "| C–O exact knot | The stored knot and its immediate neighbor could differ by one density ULP. | Exact-knot topology is below the new 0.01-bohr resolution. |\n", + "| C–O exact knot | The stored knot and its immediate neighbor could differ by one density ULP. | Exact-knot topology is below the declared 0.01-bohr resolution. |\n", "| Li–Li, 5 bohr | Five local minima were found; the two off-centre minima were slightly deepest. | A raw minimum can violate the desired homonuclear separator symmetry. |\n", "| H–U, 1.58 bohr | Fourteen isolated minima were found while a boundary value was lower. | Counting minima does not produce a useful IAS estimate. |\n", - "| At–Bk event collision | Two exact-distinct profile events rounded to one float and caused orientation-dependent candidate counts in the prototype. | Exact event identity is expensive and unnecessary for the revised contract. |\n", - "| Ne–Ne open segment | Ordinary log/exp value error exceeded the previous global-tie budget. | Numerical tie lists require a separate high-precision value model. |\n", + "| At–Bk event collision | Two exact-distinct profile events rounded to one float and caused orientation-dependent candidate counts in an event-based enumerator. | Exact event identity is unnecessary for the documented resolution-limited estimator. |\n", + "| Ne–Ne open segment | Ordinary log/exp value error exceeded a stringent global tie tolerance. | Numerical tie lists require a separate high-precision value model. |\n", "\n", - "These are real numerical facts, not mistakes to hide. The revised API simply\n", - "places them outside the production semantics: boundary mode is unaffected, and\n", + "These cases illustrate genuine shell-structure and floating-point effects. They\n", + "lie outside the documented resolution-limited semantics: boundary mode is\n", + "unaffected, and\n", "minimum mode deliberately coalesces or rejects sub-resolution structure.\n" ] }, @@ -1051,9 +1052,9 @@ "metadata": {}, "source": [ "\n", - "## Decision recorded by this study\n", + "## Public interface and scope\n", "\n", - "The recommended Stage 4 API has three entry points:\n", + "The public IAS API has three entry points:\n", "\n", "```python\n", "estimate_proatomic_boundary(...)\n", @@ -1067,9 +1068,9 @@ "resolved candidate rather than every local minimum, and reports ambiguity or a\n", "non-result explicitly.\n", "\n", - "For identical atoms, both modes return exactly `R/2`. No universal 0.5 Å ban is\n", - "needed: extreme short-distance cases naturally become `one_atom_dominates`,\n", - "`no_resolved_interior_minimum`, or another explicit diagnostic.\n", + "For identical atoms, both modes return exactly `R/2`. Extreme short-distance\n", + "cases are handled through explicit statuses such as `one_atom_dominates` or\n", + "`no_resolved_interior_minimum`, rather than a universal distance exclusion.\n", "\n", "The remaining scientific limitation is deliberate: neither neutral-proatom\n", "mode has yet been calibrated against a curated set of molecular-density QTAIM\n", diff --git a/docs/notebooks/05-proatomic-density-and-ias.ipynb b/docs/notebooks/05-proatomic-density-and-ias.ipynb index 737b28f..45b4d2e 100644 --- a/docs/notebooks/05-proatomic-density-and-ias.ipynb +++ b/docs/notebooks/05-proatomic-density-and-ias.ipynb @@ -4,9 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Neutral proatomic density and pairwise IAS-proxy workflows\n", + "# Neutral proatomic density and pairwise interatomic-surface (IAS) proxy workflows\n", "\n", - "This notebook demonstrates the public `atomref 0.2.x` density and pairwise APIs. The profiles are frozen, isolated, neutral, spherical proatoms—not molecular electron densities. The pairwise results are useful reference-atom models, but neither mode is an exact molecular QTAIM zero-flux surface or critical point.\n\n**Prerequisites.** Install `atomref[notebook]` for Matplotlib support. Familiarity with scalar function calls and ordinary radial-density plots is sufficient; the scientific assumptions and mode distinction are introduced before use." + "This notebook demonstrates the public `atomref` density and pairwise APIs. The profiles are frozen, isolated, neutral, spherical proatoms—not molecular electron densities. The pairwise results are useful reference-atom models, but neither mode is an exact molecular QTAIM zero-flux surface or critical point.\n\n**Prerequisites.** Install `atomref[notebook]` for Matplotlib support. Familiarity with scalar function calls and ordinary radial-density plots is sufficient; the scientific assumptions and mode distinction are introduced before use." ] }, { @@ -15,7 +15,7 @@ "source": [ "## Setup\n", "\n", - "The package has no runtime plotting dependency. This release notebook uses the optional `notebook` extra for Matplotlib and selects its non-interactive backend so the same cells run deterministically in CI." + "The package has no runtime plotting dependency. This notebook uses the optional `notebook` extra for Matplotlib and selects a non-interactive backend so plots render reproducibly in scripts and documentation." ] }, { @@ -394,7 +394,7 @@ "\n", "Pair distances must satisfy `0 < R <= 20 bohr`; distance and density units are independent, and coordinates are reported from atom A toward atom B. Reversing A/B maps primary and alternative coordinates `x` to `R - x`, swaps component fields, and relabels dominance; orientation-independent status and diagnostics remain equivalent. The fixed per-atom cutoff is exactly `1e-4 electron/bohr^3`. The practical minimum resolution is `0.01 bohr`; sub-resolution minima are deliberately coalesced or rejected. Inspect `method`, `status`, `position_from_a`, `dominant_atom`, `ambiguous`, and search diagnostics instead of assuming every valid request has a coordinate. Explicit statuses include `low_density_gap`, `one_atom_dominates`, `no_resolved_interior_minimum`, `boundary_dominated`, `ambiguous_competing_minima`, and `search_unstable`.\n", "\n", - "The profiles cover neutral H–Lr only and are defined by the recorded PBE0/sf-X2C/dyall-v4z method, finite radial grid, sphericalization, and interpolation contract. There are no ions, vectorized or grid-density APIs, environment-dependent atoms, molecular densities, or exact QTAIM surfaces in this release." + "The profiles cover neutral H–Lr only and are defined by the recorded PBE0/sf-X2C/dyall-v4z method, finite radial grid, sphericalization, and interpolation contract. The package provides no ions, vectorized or grid-density APIs, environment-dependent atoms, molecular densities, or exact QTAIM surfaces." ] } ], From eb1f706377c6ae2bed021d651d6019e8fc934db0 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 08:11:48 +0300 Subject: [PATCH 18/23] chore: finalize 0.2.1 release metadata and extras --- .github/workflows/ci.yml | 8 +- .github/workflows/docs.yml | 2 +- .zenodo.json | 28 ++++++ CHANGELOG.md | 31 ++++-- CITATION.cff | 24 +++++ DEV_PLAN.md | 2 +- NOTICE.md | 9 +- README.md | 27 ++++-- docs/dev/architecture.md | 6 +- docs/guide/install.md | 48 +++++----- docs/guide/notebooks.md | 2 +- docs/index.md | 27 ++++-- .../05-proatomic-density-and-ias.ipynb | 4 +- pyproject.toml | 24 +++-- tests/meta/test_packaging_metadata.py | 32 +++++-- tests/meta/test_release_metadata.py | 50 ++++++++++ tests/meta/test_release_tools.py | 31 +++++- tools/README.md | 2 +- tools/check_dist.py | 94 ++++++++++++++----- tools/release_check.py | 2 +- 20 files changed, 353 insertions(+), 100 deletions(-) create mode 100644 .zenodo.json create mode 100644 CITATION.cff create mode 100644 tests/meta/test_release_metadata.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e473d49..e1f11b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: Check README sync run: python tools/gen_readme.py --check - notebook-smoke: + notebooks-smoke: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -33,7 +33,7 @@ jobs: - name: Install notebook dependencies run: | python -m pip install --upgrade pip - python -m pip install ".[notebook]" + python -m pip install ".[notebooks]" - name: Smoke-execute notebooks with Jupyter run: python tools/check_notebooks.py @@ -47,7 +47,7 @@ jobs: - name: Install docs extras run: | python -m pip install --upgrade pip - python -m pip install ".[docs,notebook]" + python -m pip install ".[docs,notebooks]" - name: Check README sync run: python tools/gen_readme.py --check - name: Build docs @@ -61,7 +61,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 33d0894..90e8620 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: - name: Install docs extras run: | python -m pip install --upgrade pip - python -m pip install ".[docs,notebook]" + python -m pip install ".[docs,notebooks]" - name: Check README sync run: python tools/gen_readme.py --check - name: Build docs diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 0000000..7e91444 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,28 @@ +{ + "title": "atomref: curated atomic reference data and proatomic densities", + "upload_type": "software", + "access_right": "open", + "description": "atomref is a dependency-free-core Python package for curated atomic reference data, spherical neutral proatomic electron-density profiles, provenance-aware lookup policies, and pairwise reference-atom boundary estimators. The software and accompanying repository content are licensed under LGPL-3.0-or-later except for the bundled neutral proatomic-density snapshot, which is licensed separately under CC BY 4.0. See NOTICE.md for the exact licensing boundary, attribution, source hashes, and source DOIs.", + "creators": [ + { + "name": "Chernyshov, Ivan Yu." + } + ], + "license": "LGPL-3.0-or-later", + "keywords": [ + "chemistry", + "crystallography", + "atomic reference data", + "proatomic density", + "electron density", + "interatomic surfaces" + ], + "related_identifiers": [ + { + "scheme": "doi", + "identifier": "10.5281/zenodo.21291022", + "relation": "isDerivedFrom", + "resource_type": "dataset" + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index b308928..5c6edd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,27 @@ # Changelog -## 0.2.1 - 2026-07-14 +## 0.2.1 - 2026-07-15 ### Added - Complete structured public API documentation with rendered typed signatures, parameters, returns, raised errors, attributes, examples, and cross-references. -- An `all` extra that exactly mirrors the complete user-facing `notebook` extra. -- Clean built-wheel installation checks for the base package, `notebook`, and +- Repository-level `CITATION.cff` metadata for citing `atomref` directly as + software, without a preferred paper citation. +- Controlled `.zenodo.json` software-deposit metadata with the project creator, + primary LGPL-3.0-or-later license, mixed-license notice, keywords, and the + exact upstream proatomic-dataset DOI relation. +- Clean built-wheel installation checks for the base package, `notebooks`, and `all` extras. ### Changed +- Renamed the optional `notebook` extra to `notebooks`, which describes the + shipped notebook collection without implying installation of the Jupyter + Notebook server application. +- Made `all` the exact deduplicated union of `test`, `notebooks`, `docs`, and + `dev`, including every optional dependency declared by the project. +- Added Python 3.14 to the test matrix and package classifiers. - Repositioned the documentation home page and generated README around rapid installation, first use, scientific provenance, and adoption by downstream structure-analysis software. @@ -19,20 +29,27 @@ Markdown, code, mathematics, text output, and PNG plots. - Replaced the bespoke notebook execution/export path with one temporary standard-Jupyter smoke check that discards its execution results. +- Clarified that release readiness was reviewed against project criteria rather + than claiming independent external review. ### Packaging - Declared the renderer, execution client, notebook format library, kernel, and - plotting library in both `notebook` and `all`, while keeping documentation, - test, lint, build, and release tools out of `all`. + plotting library in `notebooks`; clean-install validation now verifies that + `all` exactly combines every component extra. +- Included `CITATION.cff` and `.zenodo.json` in source distributions and require + exact copies during artifact validation. - Updated CI, source-distribution checks, and release preparation for the final single-source notebook layout and isolated artifact installations. -- Kept MkDocs below version 2 across documentation and notebook extras, and - suppressed Material's MkDocs 2 migration banner in automated strict builds. +- Kept MkDocs below version 2 across documentation and notebook-related extras, + and suppressed Material's MkDocs 2 migration banner in automated strict + builds. - Build release artifacts from a clean committed-source extraction and reject nonstandard executable modes on ordinary wheel and source-distribution files. - Removed generated notebook Markdown, the custom exporter, export-sync tests, and the duplicate documentation copy of the development plan. +- Clarified the mixed LGPL-3.0-or-later software and CC BY 4.0 bundled-data + licensing boundary in the shipped notice and Zenodo description. ### Scientific behavior diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..ac5c24b --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,24 @@ +cff-version: 1.2.0 +message: "If you use atomref, please cite this software." +title: "atomref" +type: software +authors: + - family-names: "Chernyshov" + given-names: "Ivan Yu." + email: "ivan.chernyshoff@gmail.com" +version: "0.2.1" +date-released: 2026-07-15 +repository-code: "https://github.com/DeloneCommons/atomref" +url: "https://delonecommons.github.io/atomref/" +license: "LGPL-3.0-or-later" +abstract: >- + A dependency-free-core Python package for curated atomic reference data, + spherical neutral proatomic electron-density profiles, provenance-aware + lookup policies, and pairwise reference-atom boundary estimators. +keywords: + - chemistry + - crystallography + - atomic reference data + - proatomic density + - electron density + - interatomic surfaces diff --git a/DEV_PLAN.md b/DEV_PLAN.md index 9210ea3..1ad4281 100644 --- a/DEV_PLAN.md +++ b/DEV_PLAN.md @@ -9,7 +9,7 @@ The `0.2.x` development cycle is complete. Its scientific implementation, documentation, notebooks, packaging, and release preparation have been -independently reviewed and accepted. +reviewed against the project's documented criteria and accepted for release. `atomref` is now a small, dependency-free-core package providing cited atomic reference data and frozen spherical free-atom electron densities for diff --git a/NOTICE.md b/NOTICE.md index b4b77e4..6d082f4 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1,10 +1,13 @@ # atomref -atomref is a Python library for curated atomic reference data and transfer -policies for geometry and structure-analysis algorithms. +atomref is a Python library for curated atomic reference data, spherical +proatomic electron-density profiles, and provenance-aware transfer policies +for geometry and structure-analysis algorithms. Copyright (c) 2026 Ivan Chernyshov -License: LGPL-3.0-or-later (see LICENSE and COPYING) +Except for the separately licensed material identified below, the atomref +software and accompanying repository content are licensed under +LGPL-3.0-or-later (see LICENSE and COPYING). ## Third-party material diff --git a/README.md b/README.md index e012061..38cf973 100644 --- a/README.md +++ b/README.md @@ -102,21 +102,23 @@ The base install is sufficient for every runtime API: pip install atomref ``` -Install the notebook extra to run the shipped Jupyter examples and their plots: +Install the `notebooks` extra to execute or render the shipped Jupyter examples +and their plots: ```bash -pip install "atomref[notebook]" +pip install "atomref[notebooks]" ``` -Install all current user-facing optional features with: +Install every optional dependency declared by the project with: ```bash pip install "atomref[all]" ``` -Contributor test, lint, build, upload, and release tools are intentionally not -part of `all`. See the [installation guide](https://delonecommons.github.io/atomref/guide/install/) -for development setup. +`all` is the exact union of `test`, `notebooks`, `docs`, and `dev`, so it is +appropriate for a complete contributor or release environment. See the +[installation guide](https://delonecommons.github.io/atomref/guide/install/) +for the narrower groups. ## Data, provenance, and scientific scope @@ -144,6 +146,19 @@ typed diagnostic without a coordinate. - [Policy guide](https://delonecommons.github.io/atomref/guide/policies/) - [Explicit non-goals](https://delonecommons.github.io/atomref/guide/non_goals/) +## Citation and licenses + +Cite `atomref` as software using the repository-level +[`CITATION.cff`](https://github.com/DeloneCommons/atomref/blob/main/CITATION.cff). +There is no preferred paper citation; the versioned software release is the +canonical object to cite. + +Except for separately identified material, the software and accompanying +repository content are licensed under LGPL-3.0-or-later. The bundled neutral +proatomic-density snapshot is licensed separately under CC BY 4.0. +[`NOTICE.md`](https://github.com/DeloneCommons/atomref/blob/main/NOTICE.md) +records the exact boundary, attribution, source hashes, and source DOIs. + ## Executable notebook documentation The documentation renders the actual committed `.ipynb` files directly, diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index d2cd1e0..8330951 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -131,5 +131,7 @@ notebook Markdown, source-copy step, or output-freshness contract. The wheel remains a focused runtime artifact containing package code, typing metadata, legal notices, and curated data. The source distribution additionally contains tests, tools, durable documentation, and the single notebook sources. -Base, `notebook`, and `all` installations are validated from the built wheel in -separate temporary environments during release preparation. +Base, `notebooks`, and `all` installations are validated from the built wheel +in separate temporary environments during release preparation. The `all` +variant is checked as the exact union of every declared optional dependency +group rather than as an alias for one feature group. diff --git a/docs/guide/install.md b/docs/guide/install.md index 2d420ce..c5aa125 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -9,9 +9,9 @@ and pairwise APIs: pip install atomref ``` -`atomref` supports Python 3.10 and later. Its required runtime dependency set is -empty; every core calculation uses the Python standard library and packaged -data. +`atomref` requires Python 3.10 or later. The `0.2.1` CI matrix covers Python +3.10 through 3.14. Its required runtime dependency set is empty; every core +calculation uses the Python standard library and packaged data. Verify the installation with a useful result: @@ -19,47 +19,49 @@ Verify the installation with a useful result: python -c "import atomref as ar; print(ar.get_covalent_radius('C'))" ``` -## Notebook-capable installation +## Notebook tooling Install the direct notebook renderer, standard Jupyter execution support, kernel, and plotting dependency with: ```bash -pip install "atomref[notebook]" +pip install "atomref[notebooks]" ``` -This extra is needed to run the shipped `.ipynb` examples locally. It is not -needed to use any `atomref` runtime API or to read the rendered notebooks on the -documentation site. +The plural `notebooks` name describes the shipped notebook collection and does +not imply installation of the Jupyter Notebook server application. This extra +is needed to execute or render the committed `.ipynb` examples locally. It is +not needed to use any `atomref` runtime API or to read the rendered notebooks on +the documentation site. -## All user-facing optional features +## Complete optional environment -Install the union of current user-facing feature extras with: +Install every optional dependency declared by the project with: ```bash pip install "atomref[all]" ``` -In `0.2.1`, notebooks are the only optional user-facing feature, so `all` and -`notebook` intentionally contain the same dependency entries. `all` is the -stable choice for applications that want every optional user capability as new -feature extras are added later. It does not include pytest, lint, build, -upload, or release tools. +In `0.2.1`, `all` is the exact deduplicated union of `test`, `notebooks`, +`docs`, and `dev`. It therefore includes notebook tooling, documentation +tooling, pytest, linting, build, and distribution-validation dependencies. The +base package remains dependency-free. ## Contributor environment -For repository development, install the editable package and the contributor -groups required by the task: +For a complete editable repository environment, use: ```bash -pip install -e ".[test,dev,docs,notebook]" +pip install -e ".[all]" ``` -- `test` provides the test runner. -- `dev` provides lint, build, and distribution-metadata checks. +Install a narrower group when only one task is needed: + +- `test` provides pytest and the Python 3.10 TOML compatibility dependency. +- `notebooks` provides notebook rendering and execution, a Python kernel, and + plotting. - `docs` provides MkDocs, Material, and typed API-documentation tooling. -- `notebook` provides direct notebook rendering, Jupyter execution, a Python - kernel, and plotting. +- `dev` provides lint, build, and distribution-metadata checks. Run the full local release validation with: @@ -69,5 +71,5 @@ python tools/release_check.py That command requires a clean worktree, builds fresh artifacts from a temporary extraction of the committed `HEAD` with conventional file modes, and validates -clean base, `notebook`, and `all` installations in temporary virtual +clean base, `notebooks`, and `all` installations in temporary virtual environments. diff --git a/docs/guide/notebooks.md b/docs/guide/notebooks.md index 9546191..b508617 100644 --- a/docs/guide/notebooks.md +++ b/docs/guide/notebooks.md @@ -49,7 +49,7 @@ download: Install all direct notebook dependencies with: ```bash -pip install "atomref[notebook]" +pip install "atomref[notebooks]" ``` The notebooks are designed for a standard Python Jupyter kernel. Saved outputs diff --git a/docs/index.md b/docs/index.md index 0c06bbc..808d21d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -100,21 +100,23 @@ The base install is sufficient for every runtime API: pip install atomref ``` -Install the notebook extra to run the shipped Jupyter examples and their plots: +Install the `notebooks` extra to execute or render the shipped Jupyter examples +and their plots: ```bash -pip install "atomref[notebook]" +pip install "atomref[notebooks]" ``` -Install all current user-facing optional features with: +Install every optional dependency declared by the project with: ```bash pip install "atomref[all]" ``` -Contributor test, lint, build, upload, and release tools are intentionally not -part of `all`. See the [installation guide](https://delonecommons.github.io/atomref/guide/install/) -for development setup. +`all` is the exact union of `test`, `notebooks`, `docs`, and `dev`, so it is +appropriate for a complete contributor or release environment. See the +[installation guide](https://delonecommons.github.io/atomref/guide/install/) +for the narrower groups. ## Data, provenance, and scientific scope @@ -142,6 +144,19 @@ typed diagnostic without a coordinate. - [Policy guide](https://delonecommons.github.io/atomref/guide/policies/) - [Explicit non-goals](https://delonecommons.github.io/atomref/guide/non_goals/) +## Citation and licenses + +Cite `atomref` as software using the repository-level +[`CITATION.cff`](https://github.com/DeloneCommons/atomref/blob/main/CITATION.cff). +There is no preferred paper citation; the versioned software release is the +canonical object to cite. + +Except for separately identified material, the software and accompanying +repository content are licensed under LGPL-3.0-or-later. The bundled neutral +proatomic-density snapshot is licensed separately under CC BY 4.0. +[`NOTICE.md`](https://github.com/DeloneCommons/atomref/blob/main/NOTICE.md) +records the exact boundary, attribution, source hashes, and source DOIs. + ## Executable notebook documentation The documentation renders the actual committed `.ipynb` files directly, diff --git a/docs/notebooks/05-proatomic-density-and-ias.ipynb b/docs/notebooks/05-proatomic-density-and-ias.ipynb index 45b4d2e..b3db3bb 100644 --- a/docs/notebooks/05-proatomic-density-and-ias.ipynb +++ b/docs/notebooks/05-proatomic-density-and-ias.ipynb @@ -6,7 +6,7 @@ "source": [ "# Neutral proatomic density and pairwise interatomic-surface (IAS) proxy workflows\n", "\n", - "This notebook demonstrates the public `atomref` density and pairwise APIs. The profiles are frozen, isolated, neutral, spherical proatoms—not molecular electron densities. The pairwise results are useful reference-atom models, but neither mode is an exact molecular QTAIM zero-flux surface or critical point.\n\n**Prerequisites.** Install `atomref[notebook]` for Matplotlib support. Familiarity with scalar function calls and ordinary radial-density plots is sufficient; the scientific assumptions and mode distinction are introduced before use." + "This notebook demonstrates the public `atomref` density and pairwise APIs. The profiles are frozen, isolated, neutral, spherical proatoms—not molecular electron densities. The pairwise results are useful reference-atom models, but neither mode is an exact molecular QTAIM zero-flux surface or critical point.\n\n**Prerequisites.** Install `atomref[notebooks]` for Matplotlib support. Familiarity with scalar function calls and ordinary radial-density plots is sufficient; the scientific assumptions and mode distinction are introduced before use." ] }, { @@ -15,7 +15,7 @@ "source": [ "## Setup\n", "\n", - "The package has no runtime plotting dependency. This notebook uses the optional `notebook` extra for Matplotlib and selects a non-interactive backend so plots render reproducibly in scripts and documentation." + "The package has no runtime plotting dependency. This notebook uses the optional `notebooks` extra for Matplotlib and selects a non-interactive backend so plots render reproducibly in scripts and documentation." ] }, { diff --git a/pyproject.toml b/pyproject.toml index 000741d..c724788 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Typing :: Typed", ] @@ -46,13 +47,14 @@ Documentation = "https://delonecommons.github.io/atomref/" Repository = "https://github.com/DeloneCommons/atomref" Issues = "https://github.com/DeloneCommons/atomref/issues" Changelog = "https://github.com/DeloneCommons/atomref/blob/main/CHANGELOG.md" +Citation = "https://github.com/DeloneCommons/atomref/blob/main/CITATION.cff" [project.optional-dependencies] test = [ "pytest>=7", "tomli>=2; python_version < '3.11'", ] -notebook = [ +notebooks = [ "ipykernel>=6.29", "matplotlib>=3.8", "mkdocs>=1.6,<2", @@ -60,21 +62,29 @@ notebook = [ "nbclient>=0.10,<0.12", "nbformat>=5.10,<6", ] +docs = [ + "mkdocs>=1.6,<2", + "mkdocs-material>=9.5", + "mkdocstrings[python]>=0.25", + "pymdown-extensions>=10.0", +] +dev = [ + "build>=1.2", + "twine>=5", + "flake8>=7", +] all = [ + "pytest>=7", + "tomli>=2; python_version < '3.11'", "ipykernel>=6.29", "matplotlib>=3.8", "mkdocs>=1.6,<2", "mkdocs-jupyter>=0.26,<0.27", "nbclient>=0.10,<0.12", "nbformat>=5.10,<6", -] -docs = [ - "mkdocs>=1.6,<2", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.25", "pymdown-extensions>=10.0", -] -dev = [ "build>=1.2", "twine>=5", "flake8>=7", @@ -102,6 +112,8 @@ include = [ "/README.md", "/CHANGELOG.md", "/DEV_PLAN.md", + "/CITATION.cff", + "/.zenodo.json", "/NOTICE.md", "/LICENSE", "/COPYING", diff --git a/tests/meta/test_packaging_metadata.py b/tests/meta/test_packaging_metadata.py index 94e21e7..9b121ba 100644 --- a/tests/meta/test_packaging_metadata.py +++ b/tests/meta/test_packaging_metadata.py @@ -10,7 +10,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2] PYPROJECT = REPO_ROOT / "pyproject.toml" -EXPECTED_NOTEBOOK_DEPENDENCIES = [ +EXPECTED_NOTEBOOKS_DEPENDENCIES = [ "ipykernel>=6.29", "matplotlib>=3.8", "mkdocs>=1.6,<2", @@ -18,6 +18,7 @@ "nbclient>=0.10,<0.12", "nbformat>=5.10,<6", ] +COMPONENT_EXTRAS = {"test", "notebooks", "docs", "dev"} def _project_metadata() -> dict[str, object]: @@ -30,20 +31,31 @@ def test_runtime_dependencies_remain_empty() -> None: assert project["dependencies"] == [] -def test_notebook_and_all_extras_are_identical_and_complete() -> None: +def test_notebooks_extra_is_complete_and_singular_name_is_removed() -> None: project = _project_metadata() extras = project["optional-dependencies"] - assert extras["notebook"] == EXPECTED_NOTEBOOK_DEPENDENCIES - assert extras["all"] == EXPECTED_NOTEBOOK_DEPENDENCIES + assert extras["notebooks"] == EXPECTED_NOTEBOOKS_DEPENDENCIES + assert "notebook" not in extras -def test_all_extra_excludes_contributor_tooling() -> None: +def test_all_extra_is_the_exact_union_of_component_extras() -> None: + project = _project_metadata() + extras = project["optional-dependencies"] + + expected_all = set().union( + *(set(extras[extra]) for extra in COMPONENT_EXTRAS) + ) + assert set(extras["all"]) == expected_all + assert len(extras["all"]) == len(set(extras["all"])) + + +def test_all_extra_includes_contributor_tooling() -> None: project = _project_metadata() all_requirements = "\n".join(project["optional-dependencies"]["all"]).lower() for contributor_package in ("build", "flake8", "pytest", "twine"): - assert contributor_package not in all_requirements + assert contributor_package in all_requirements def test_docs_extra_contains_only_used_documentation_tooling() -> None: @@ -58,5 +70,11 @@ def test_mkdocs_stays_on_the_supported_1_x_line() -> None: project = _project_metadata() extras = project["optional-dependencies"] - for extra in ("docs", "notebook", "all"): + for extra in ("docs", "notebooks", "all"): assert extras[extra].count("mkdocs>=1.6,<2") == 1 + + +def test_python_314_is_classified() -> None: + project = _project_metadata() + + assert "Programming Language :: Python :: 3.14" in project["classifiers"] diff --git a/tests/meta/test_release_metadata.py b/tests/meta/test_release_metadata.py new file mode 100644 index 0000000..6ed19ec --- /dev/null +++ b/tests/meta/test_release_metadata.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import json +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +ZENODO_METADATA = REPO_ROOT / ".zenodo.json" +CITATION_METADATA = REPO_ROOT / "CITATION.cff" + + +def test_zenodo_metadata_describes_software_and_mixed_licenses() -> None: + metadata = json.loads(ZENODO_METADATA.read_text(encoding="utf-8")) + + assert set(metadata) == { + "title", + "upload_type", + "access_right", + "description", + "creators", + "license", + "keywords", + "related_identifiers", + } + assert metadata["upload_type"] == "software" + assert metadata["license"] == "LGPL-3.0-or-later" + assert metadata["creators"] == [{"name": "Chernyshov, Ivan Yu."}] + assert "CC BY 4.0" in metadata["description"] + assert { + "scheme": "doi", + "identifier": "10.5281/zenodo.21291022", + "relation": "isDerivedFrom", + "resource_type": "dataset", + } in metadata["related_identifiers"] + + +def test_citation_metadata_targets_the_versioned_software_repository() -> None: + text = CITATION_METADATA.read_text(encoding="utf-8") + + for required_line in ( + "cff-version: 1.2.0", + 'title: "atomref"', + "type: software", + 'version: "0.2.1"', + "date-released: 2026-07-15", + 'repository-code: "https://github.com/DeloneCommons/atomref"', + 'license: "LGPL-3.0-or-later"', + ): + assert required_line in text + assert "preferred-citation:" not in text diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index 4aa2186..61e4afd 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -21,10 +21,19 @@ Metadata-Version: 2.4 Name: atomref Version: 0.2.1 -Provides-Extra: notebook Provides-Extra: all -Requires-Dist: ipykernel>=6.29; extra == 'notebook' +Provides-Extra: dev +Provides-Extra: docs +Provides-Extra: notebooks +Provides-Extra: test +Requires-Dist: build>=1.2; extra == 'dev' +Requires-Dist: mkdocs-material>=9.5; extra == 'docs' +Requires-Dist: ipykernel>=6.29; extra == 'notebooks' +Requires-Dist: pytest>=7; extra == 'test' +Requires-Dist: build>=1.2; extra == 'all' +Requires-Dist: mkdocs-material>=9.5; extra == 'all' Requires-Dist: ipykernel>=6.29; extra == 'all' +Requires-Dist: pytest>=7; extra == 'all' """ @@ -70,6 +79,8 @@ def test_dist_check_requires_release_tools_and_legacy_data() -> None: assert ".flake8" in check_dist.REQUIRED_SDIST_SUFFIXES assert "tools/check_registry.py" in check_dist.REQUIRED_SDIST_SUFFIXES assert "tools/check_dist.py" in check_dist.REQUIRED_SDIST_SUFFIXES + assert "CITATION.cff" in check_dist.REQUIRED_SDIST_SUFFIXES + assert ".zenodo.json" in check_dist.REQUIRED_SDIST_SUFFIXES assert "docs/notebooks/05-proatomic-density-and-ias.ipynb" in ( check_dist.REQUIRED_SDIST_SUFFIXES ) @@ -106,7 +117,7 @@ def test_sdist_layout_rejects_generated_notebook_markdown() -> None: check_dist._assert_sdist_layout(members, label="test sdist") -def test_wheel_metadata_accepts_empty_runtime_and_matching_user_extras() -> None: +def test_wheel_metadata_accepts_empty_runtime_and_complete_all_extra() -> None: check_dist._assert_wheel_metadata( VALID_WHEEL_METADATA, member="atomref.dist-info/METADATA", @@ -114,6 +125,20 @@ def test_wheel_metadata_accepts_empty_runtime_and_matching_user_extras() -> None ) +def test_wheel_metadata_rejects_incomplete_all_extra() -> None: + payload = VALID_WHEEL_METADATA.replace( + b"Requires-Dist: pytest>=7; extra == 'all'\n", + b"", + ) + + with pytest.raises(check_dist.DistCheckError, match="all extra must equal"): + check_dist._assert_wheel_metadata( + payload, + member="atomref.dist-info/METADATA", + label="test wheel", + ) + + def test_dist_check_accepts_conventional_regular_file_modes() -> None: check_dist._assert_regular_file_modes( [("atomref/module.py", 0o100644), ("atomref/data/table.csv", 0o644)], diff --git a/tools/README.md b/tools/README.md index 2bd03a7..9162c57 100644 --- a/tools/README.md +++ b/tools/README.md @@ -10,7 +10,7 @@ release preparation. H–Lr consumer ZIP. This is a maintainer-only tool and performs no network access. - `check_dist.py` — verify wheel and source-distribution contents and optionally - test clean base, `notebook`, and `all` installations from the built wheel. + test clean base, `notebooks`, and `all` installations from the built wheel. - `check_notebooks.py` — smoke-execute temporary notebook copies through a standard Jupyter kernel and discard the resulting outputs. - `check_registry.py` — validate curated registry metadata against every diff --git a/tools/check_dist.py b/tools/check_dist.py index fdf98c8..2d18216 100644 --- a/tools/check_dist.py +++ b/tools/check_dist.py @@ -52,6 +52,8 @@ "README.md", "CHANGELOG.md", "DEV_PLAN.md", + "CITATION.cff", + ".zenodo.json", "COPYING", "LICENSE", "NOTICE.md", @@ -85,17 +87,40 @@ EXPECTED_VERSION = "0.2.1" EXPECTED_REGULAR_FILE_MODE = 0o644 -REQUIRED_USER_EXTRAS = {"all", "notebook"} +COMPONENT_EXTRAS = {"test", "notebooks", "docs", "dev"} +REQUIRED_EXTRAS = COMPONENT_EXTRAS | {"all"} EXTRA_MARKER = re.compile(r"\bextra\s*==\s*(['\"])([-a-zA-Z0-9_.]+)\1") -OPTIONAL_IMPORTS = """\ +NOTEBOOKS_IMPORTS = """\ import ipykernel import matplotlib +import mkdocs import mkdocs_jupyter import nbclient import nbformat """ +ALL_IMPORTS = f"""\ +{NOTEBOOKS_IMPORTS} +import build +import flake8 +import material +import mkdocstrings +import mkdocstrings_handlers.python +import pymdownx +import pytest +import twine +try: + import tomllib +except ModuleNotFoundError: + import tomli +""" + +EXTRA_IMPORTS = { + "notebooks": NOTEBOOKS_IMPORTS, + "all": ALL_IMPORTS, +} + API_SMOKE = """\ import atomref as ar @@ -295,7 +320,7 @@ def _decode_utf8(payload: bytes, *, member: str, label: str) -> str: def _assert_wheel_metadata(payload: bytes, *, member: str, label: str) -> None: - """Validate release version, empty core requirements, and user extras.""" + """Validate release version, empty core requirements, and extras.""" try: metadata = BytesParser(policy=policy.default).parsebytes(payload) @@ -313,10 +338,10 @@ def _assert_wheel_metadata(payload: bytes, *, member: str, label: str) -> None: ) provided_extras = set(metadata.get_all("Provides-Extra", [])) - missing_extras = REQUIRED_USER_EXTRAS - provided_extras + missing_extras = REQUIRED_EXTRAS - provided_extras if missing_extras: joined = ", ".join(sorted(missing_extras)) - raise DistCheckError(f"{label} is missing user extras: {joined}") + raise DistCheckError(f"{label} is missing extras: {joined}") requirements = metadata.get_all("Requires-Dist", []) unconditional = [ @@ -331,19 +356,35 @@ def _assert_wheel_metadata(payload: bytes, *, member: str, label: str) -> None: ) by_extra: dict[str, set[str]] = { - extra: set() for extra in REQUIRED_USER_EXTRAS + extra: set() for extra in REQUIRED_EXTRAS } for requirement in requirements: requirement_text = requirement.split(";", 1)[0].strip() extras = {match[1] for match in EXTRA_MARKER.findall(requirement)} - for extra in REQUIRED_USER_EXTRAS & extras: + for extra in REQUIRED_EXTRAS & extras: by_extra[extra].add(requirement_text) - if not by_extra["notebook"]: - raise DistCheckError(f"{label} notebook extra must not be empty") - if by_extra["all"] != by_extra["notebook"]: + empty_extras = sorted( + extra for extra in COMPONENT_EXTRAS if not by_extra[extra] + ) + if empty_extras: + joined = ", ".join(empty_extras) + raise DistCheckError(f"{label} has empty component extras: {joined}") + + expected_all = set().union( + *(by_extra[extra] for extra in COMPONENT_EXTRAS) + ) + if by_extra["all"] != expected_all: + missing = sorted(expected_all - by_extra["all"]) + unexpected = sorted(by_extra["all"] - expected_all) + details: list[str] = [] + if missing: + details.append(f"missing: {', '.join(missing)}") + if unexpected: + details.append(f"unexpected: {', '.join(unexpected)}") raise DistCheckError( - f"{label} all and notebook extras must contain identical requirements" + f"{label} all extra must equal the union of test, notebooks, " + f"docs, and dev ({'; '.join(details)})" ) @@ -443,17 +484,18 @@ def check_sdist(path: Path) -> None: _assert_members_present(matched, REQUIRED_SDIST_SUFFIXES, label=path.name) _assert_sdist_layout(names, label=path.name) - readme_member = _sdist_root_member(names, "README.md", label=path.name) - readme_file = tf.extractfile(readme_member) - if readme_file is None: - raise DistCheckError( - f"{path.name} member {readme_member!r} is not a regular file" - ) - if readme_file.read() != (REPO_ROOT / "README.md").read_bytes(): - raise DistCheckError( - f"{path.name} member {readme_member!r} does not exactly match " - "the source README.md" - ) + for filename in ("README.md", "CITATION.cff", ".zenodo.json"): + root_member = _sdist_root_member(names, filename, label=path.name) + root_file = tf.extractfile(root_member) + if root_file is None: + raise DistCheckError( + f"{path.name} member {root_member!r} is not a regular file" + ) + if root_file.read() != (REPO_ROOT / filename).read_bytes(): + raise DistCheckError( + f"{path.name} member {root_member!r} does not exactly " + f"match the source {filename}" + ) snapshot_member = _member_matching_suffix( names, @@ -515,7 +557,7 @@ def _run_checked( def check_clean_installations(wheel: Path) -> None: - """Install base, notebook, and all variants in separate clean environments.""" + """Install base, notebooks, and all variants in clean environments.""" wheel = wheel.resolve() if not wheel.is_file(): @@ -530,7 +572,7 @@ def check_clean_installations(wheel: Path) -> None: outside_checkout = root / "outside-checkout" outside_checkout.mkdir() - for extra in (None, "notebook", "all"): + for extra in (None, "notebooks", "all"): label = "base" if extra is None else extra env_dir = root / f"venv-{label}" venv.EnvBuilder(with_pip=True).create(env_dir) @@ -566,7 +608,7 @@ def check_clean_installations(wheel: Path) -> None: smoke = API_SMOKE if extra is not None: - smoke = f"{OPTIONAL_IMPORTS}\n{smoke}" + smoke = f"{EXTRA_IMPORTS[extra]}\n{smoke}" _run_checked( [str(python), "-c", smoke], cwd=outside_checkout, @@ -586,7 +628,7 @@ def main() -> None: "--check-installs", action="store_true", help=( - "install the built wheel as base, notebook, and all variants in " + "install the built wheel as base, notebooks, and all variants in " "separate clean virtual environments" ), ) diff --git a/tools/release_check.py b/tools/release_check.py index 2ca255a..b16e324 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -125,7 +125,7 @@ def main() -> int: parser.add_argument( "--skip-install-checks", action="store_true", - help="skip clean base, notebook, and all wheel installation checks", + help="skip clean base, notebooks, and all wheel installation checks", ) args = parser.parse_args() From 7b46422fa52fdc5cafee7ad57de0112d9ebd1d58 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 08:47:54 +0300 Subject: [PATCH 19/23] fix: contain notebook smoke execution --- .github/workflows/ci.yml | 1 + CHANGELOG.md | 9 +- README.md | 8 +- docs/dev/architecture.md | 7 +- docs/guide/notebooks.md | 8 +- docs/index.md | 8 +- tests/meta/test_notebook_tool.py | 383 +++++++++++++++++++++++++++++++ tests/meta/test_release_tools.py | 22 ++ tools/README.md | 5 +- tools/check_notebooks.py | 213 +++++++++++++---- tools/release_check.py | 24 +- 11 files changed, 628 insertions(+), 60 deletions(-) create mode 100644 tests/meta/test_notebook_tool.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1f11b5..bd61ea3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ jobs: python -m pip install --upgrade pip python -m pip install ".[notebooks]" - name: Smoke-execute notebooks with Jupyter + timeout-minutes: 20 run: python tools/check_notebooks.py docs-check: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c6edd4..461811e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,8 +27,10 @@ structure-analysis software. - Rendered the maintained `.ipynb` notebooks directly in MkDocs with committed Markdown, code, mathematics, text output, and PNG plots. -- Replaced the bespoke notebook execution/export path with one temporary - standard-Jupyter smoke check that discards its execution results. +- Replaced the bespoke notebook execution/export path with bounded, isolated + standard Jupyter workers that execute temporary copies and discard their + results. Kernel startup, cell execution, cleanup, and process exit are all + contained by explicit time limits. - Clarified that release readiness was reviewed against project criteria rather than claiming independent external review. @@ -40,7 +42,8 @@ - Included `CITATION.cff` and `.zenodo.json` in source distributions and require exact copies during artifact validation. - Updated CI, source-distribution checks, and release preparation for the final - single-source notebook layout and isolated artifact installations. + single-source notebook layout, bounded notebook-process lifecycle, and + isolated artifact installations. - Kept MkDocs below version 2 across documentation and notebook-related extras, and suppressed Material's MkDocs 2 migration banner in automated strict builds. diff --git a/README.md b/README.md index 38cf973..b4d7756 100644 --- a/README.md +++ b/README.md @@ -163,8 +163,8 @@ records the exact boundary, attribution, source hashes, and source DOIs. The documentation renders the actual committed `.ipynb` files directly, including Markdown, code, mathematics, saved text, and saved PNG plots. Site -builds do not execute or rewrite them; a separate temporary Jupyter smoke check -verifies that their code still runs. +builds do not execute or rewrite them; separate bounded Jupyter workers verify +temporary copies without retaining their results. - [Notebook overview](https://delonecommons.github.io/atomref/guide/notebooks/) - [Quickstart notebook](https://delonecommons.github.io/atomref/notebooks/01-quickstart/) @@ -208,8 +208,8 @@ The repository keeps a small set of release tools: - `python tools/check_registry.py` validates registry metadata against every packaged scalar and radial payload; -- `python tools/check_notebooks.py` smoke-executes temporary notebook copies - through a standard Jupyter kernel and discards the results; +- `python tools/check_notebooks.py` smoke-executes each temporary notebook copy + in its own bounded standard Jupyter process, then discards the results; - `python tools/gen_readme.py` regenerates this README from `docs/index.md`; - `python tools/release_check.py` runs lint, tests, strict docs, distribution checks, and clean artifact-installation checks. diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 8330951..eabc971 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -125,8 +125,11 @@ That keeps the internals easy to test and lets other scientific packages reuse The five files under `docs/notebooks/` are both the maintained Jupyter sources and their documentation pages. `mkdocs-jupyter` renders their committed state with execution disabled; `tools/check_notebooks.py` exercises temporary copies -through `nbclient` and discards the results. There is no exporter, generated -notebook Markdown, source-copy step, or output-freshness contract. +one at a time through isolated, time-bounded standard Jupyter processes and +discards the results. Startup and cell timeouts remain separate, and a stalled +kernel cleanup or process exit is force-contained before the checker fails. +There is no exporter, generated notebook Markdown, source-copy step, or +output-freshness contract. The wheel remains a focused runtime artifact containing package code, typing metadata, legal notices, and curated data. The source distribution additionally diff --git a/docs/guide/notebooks.md b/docs/guide/notebooks.md index b508617..7c2cb87 100644 --- a/docs/guide/notebooks.md +++ b/docs/guide/notebooks.md @@ -6,9 +6,11 @@ Markdown copy or parallel notebook tree. Documentation builds show the committed Markdown, code, mathematics, saved text, and saved PNG plots. They do not execute or rewrite the notebooks. A -separate release check runs temporary copies through a standard Jupyter kernel, -fails on execution exceptions, and discards the temporary outputs without -comparing them with the committed files. +separate release check runs each temporary copy in its own bounded standard +Jupyter process, fails on execution exceptions or lifecycle timeouts, and +discards the temporary outputs without comparing them with the committed +files. Kernel startup and cell execution have separate timeouts; a stalled +cleanup or process exit cannot block the remaining release gate indefinitely. ## User workflows diff --git a/docs/index.md b/docs/index.md index 808d21d..dd14dbb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -161,8 +161,8 @@ records the exact boundary, attribution, source hashes, and source DOIs. The documentation renders the actual committed `.ipynb` files directly, including Markdown, code, mathematics, saved text, and saved PNG plots. Site -builds do not execute or rewrite them; a separate temporary Jupyter smoke check -verifies that their code still runs. +builds do not execute or rewrite them; separate bounded Jupyter workers verify +temporary copies without retaining their results. - [Notebook overview](https://delonecommons.github.io/atomref/guide/notebooks/) - [Quickstart notebook](https://delonecommons.github.io/atomref/notebooks/01-quickstart/) @@ -206,8 +206,8 @@ The repository keeps a small set of release tools: - `python tools/check_registry.py` validates registry metadata against every packaged scalar and radial payload; -- `python tools/check_notebooks.py` smoke-executes temporary notebook copies - through a standard Jupyter kernel and discards the results; +- `python tools/check_notebooks.py` smoke-executes each temporary notebook copy + in its own bounded standard Jupyter process, then discards the results; - `python tools/gen_readme.py` regenerates this README from `docs/index.md`; - `python tools/release_check.py` runs lint, tests, strict docs, distribution checks, and clean artifact-installation checks. diff --git a/tests/meta/test_notebook_tool.py b/tests/meta/test_notebook_tool.py new file mode 100644 index 0000000..3ef9ff0 --- /dev/null +++ b/tests/meta/test_notebook_tool.py @@ -0,0 +1,383 @@ +from __future__ import annotations + +import importlib.util +from pathlib import Path +import signal +import subprocess +import sys + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[2] +CHECK_NOTEBOOKS_PATH = REPO_ROOT / "tools" / "check_notebooks.py" +CI_WORKFLOW_PATH = REPO_ROOT / ".github" / "workflows" / "ci.yml" + +spec = importlib.util.spec_from_file_location( + "check_notebooks_tool", CHECK_NOTEBOOKS_PATH +) +assert spec is not None and spec.loader is not None +check_notebooks = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = check_notebooks +spec.loader.exec_module(check_notebooks) + + +class FakeProcess: + def __init__(self, returncode: int = 0) -> None: + self.pid = 4312 + self.returncode = returncode + self.wait_timeouts: list[float] = [] + self.killed = False + + def poll(self) -> int | None: + return None + + def wait(self, timeout: float) -> int: + self.wait_timeouts.append(timeout) + return self.returncode + + def kill(self) -> None: + self.killed = True + + +def test_worker_process_group_options_are_platform_specific( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr(check_notebooks, "IS_WINDOWS", False) + assert check_notebooks._worker_group_options() == {"start_new_session": True} + + monkeypatch.setattr(check_notebooks, "IS_WINDOWS", True) + monkeypatch.setattr( + check_notebooks.subprocess, + "CREATE_NEW_PROCESS_GROUP", + 0x00000200, + raising=False, + ) + assert check_notebooks._worker_group_options() == {"creationflags": 0x00000200} + + +def test_worker_uses_one_bounded_standard_jupyter_process( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + notebook = tmp_path / "example.ipynb" + notebook.touch() + working_dir = tmp_path / "working" + working_dir.mkdir() + runtime_root = tmp_path / "runtime" / "example" + process = FakeProcess() + captured: dict[str, object] = {} + + def popen(command: list[str], **kwargs: object) -> FakeProcess: + captured["command"] = command + captured.update(kwargs) + return process + + monkeypatch.setattr(check_notebooks.subprocess, "Popen", popen) + monkeypatch.setattr( + check_notebooks, + "_worker_group_options", + lambda: {"start_new_session": True}, + ) + + check_notebooks._run_worker( + notebook, + working_dir=working_dir, + runtime_root=runtime_root, + ) + + assert captured["command"] == [ + sys.executable, + "-m", + "jupyter", + "execute", + f"--timeout={check_notebooks.CELL_TIMEOUT_SECONDS}", + f"--startup_timeout={check_notebooks.KERNEL_STARTUP_TIMEOUT_SECONDS}", + "--Application.log_level=INFO", + "--inplace", + str(notebook), + ] + assert captured["cwd"] == working_dir + assert captured["start_new_session"] is True + environment = captured["env"] + assert isinstance(environment, dict) + assert environment["JUPYTER_RUNTIME_DIR"] == str(runtime_root / "jupyter-runtime") + assert environment["MPLBACKEND"] == "Agg" + assert process.wait_timeouts == [check_notebooks.WORKER_TIMEOUT_SECONDS] + + +def test_worker_timeout_terminates_group_and_names_phase( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + notebook = tmp_path / "cleanup-hang.ipynb" + notebook.touch() + process = FakeProcess() + terminated: list[FakeProcess] = [] + + def timeout(*, timeout: float) -> int: + raise subprocess.TimeoutExpired(["jupyter", "execute"], timeout) + + process.wait = timeout # type: ignore[method-assign] + monkeypatch.setattr( + check_notebooks.subprocess, + "Popen", + lambda *_args, **_kwargs: process, + ) + monkeypatch.setattr(check_notebooks, "_terminate_worker", terminated.append) + + with pytest.raises( + check_notebooks.NotebookCheckError, + match=( + r"cleanup-hang\.ipynb.*420-second.*kernel startup, cell execution, " + r"kernel cleanup, or process exit.*worker containment completed" + ), + ): + check_notebooks._run_worker( + notebook, + working_dir=tmp_path, + runtime_root=tmp_path / "runtime", + ) + + assert terminated == [process] + + +def test_worker_nonzero_exit_names_notebook_phase_and_status( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + notebook = tmp_path / "cell-error.ipynb" + notebook.touch() + process = FakeProcess(returncode=17) + monkeypatch.setattr( + check_notebooks.subprocess, + "Popen", + lambda *_args, **_kwargs: process, + ) + + with pytest.raises( + check_notebooks.NotebookCheckError, + match=r"cell-error\.ipynb.*startup, execution, or cleanup.*status 17", + ): + check_notebooks._run_worker( + notebook, + working_dir=tmp_path, + runtime_root=tmp_path / "runtime", + ) + + +def test_posix_worker_group_is_force_killed_and_reaped( + monkeypatch: pytest.MonkeyPatch, +) -> None: + process = FakeProcess() + signals: list[tuple[int, signal.Signals]] = [] + monkeypatch.setattr(check_notebooks, "IS_WINDOWS", False) + monkeypatch.setattr( + check_notebooks.os, + "killpg", + lambda pid, sent_signal: signals.append((pid, sent_signal)), + ) + + check_notebooks._terminate_worker(process) + + assert signals == [(process.pid, signal.SIGKILL)] + assert process.wait_timeouts == [ + check_notebooks.WORKER_TERMINATION_TIMEOUT_SECONDS + ] + assert not process.killed + + +def test_windows_worker_tree_is_force_killed_and_reaped( + monkeypatch: pytest.MonkeyPatch, +) -> None: + process = FakeProcess() + calls: list[tuple[list[str], dict[str, object]]] = [] + monkeypatch.setattr(check_notebooks, "IS_WINDOWS", True) + + def run( + command: list[str], **kwargs: object + ) -> subprocess.CompletedProcess[bytes]: + calls.append((command, kwargs)) + return subprocess.CompletedProcess(command, 0) + + monkeypatch.setattr(check_notebooks.subprocess, "run", run) + + check_notebooks._terminate_worker(process) + + assert calls[0][0] == [ + "taskkill", + "/PID", + str(process.pid), + "/T", + "/F", + ] + assert calls[0][1]["timeout"] == ( + check_notebooks.WORKER_TERMINATION_TIMEOUT_SECONDS + ) + assert process.wait_timeouts == [ + check_notebooks.WORKER_TERMINATION_TIMEOUT_SECONDS + ] + + +def test_posix_already_exited_group_is_still_reaped( + monkeypatch: pytest.MonkeyPatch, +) -> None: + process = FakeProcess() + monkeypatch.setattr(check_notebooks, "IS_WINDOWS", False) + + def missing_group(_pid: int, _sent_signal: signal.Signals) -> None: + raise ProcessLookupError + + monkeypatch.setattr(check_notebooks.os, "killpg", missing_group) + + check_notebooks._terminate_worker(process) + + assert process.wait_timeouts == [ + check_notebooks.WORKER_TERMINATION_TIMEOUT_SECONDS + ] + + +@pytest.mark.parametrize( + "taskkill_error", + [OSError("taskkill unavailable"), subprocess.TimeoutExpired("taskkill", 10)], +) +def test_windows_taskkill_failure_is_reported_after_direct_kill( + taskkill_error: BaseException, + monkeypatch: pytest.MonkeyPatch, +) -> None: + process = FakeProcess() + monkeypatch.setattr(check_notebooks, "IS_WINDOWS", True) + + def fail(*_args: object, **_kwargs: object) -> None: + raise taskkill_error + + monkeypatch.setattr(check_notebooks.subprocess, "run", fail) + + with pytest.raises( + check_notebooks.NotebookCheckError, + match=r"could not confirm termination of Windows worker tree 4312", + ): + check_notebooks._terminate_worker(process) + + assert process.killed + + +def test_unreapable_worker_has_bounded_diagnostic( + monkeypatch: pytest.MonkeyPatch, +) -> None: + process = FakeProcess() + wait_calls = 0 + monkeypatch.setattr(check_notebooks, "IS_WINDOWS", False) + monkeypatch.setattr(check_notebooks.os, "killpg", lambda *_args: None) + + def timeout(*, timeout: float) -> int: + nonlocal wait_calls + wait_calls += 1 + raise subprocess.TimeoutExpired("worker", timeout) + + process.wait = timeout # type: ignore[method-assign] + + with pytest.raises( + check_notebooks.NotebookCheckError, + match=r"worker process 4312 could not be reaped", + ): + check_notebooks._terminate_worker(process) + + assert wait_calls == 2 + assert process.killed + + +def test_aggregate_success_waits_for_every_worker( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + source_root = tmp_path / "source" + source_root.mkdir() + notebooks = [] + for name in ("one.ipynb", "two.ipynb"): + notebook = tmp_path / name + notebook.touch() + notebooks.append(notebook) + completed: list[str] = [] + + monkeypatch.setattr(check_notebooks, "SRC", source_root) + monkeypatch.setattr( + check_notebooks, + "_run_worker", + lambda path, **_kwargs: completed.append(path.name), + ) + + assert check_notebooks.smoke_execute(notebooks) == 0 + + assert completed == ["one.ipynb", "two.ipynb"] + output = capsys.readouterr().out + assert "phase=Jupyter startup/execution/cleanup" in output + assert output.rstrip().endswith( + "Smoke-executed 2 notebook(s) in temporary kernels." + ) + + +def test_cleanup_hang_cannot_print_aggregate_success( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + source_root = tmp_path / "source" + source_root.mkdir() + notebook = tmp_path / "cleanup-hang.ipynb" + notebook.touch() + monkeypatch.setattr(check_notebooks, "SRC", source_root) + + def fail(*_args: object, **_kwargs: object) -> None: + raise check_notebooks.NotebookCheckError("cleanup did not exit") + + monkeypatch.setattr(check_notebooks, "_run_worker", fail) + + with pytest.raises(check_notebooks.NotebookCheckError): + check_notebooks.smoke_execute([notebook]) + + output = capsys.readouterr().out + assert "cleanup-hang.ipynb" in output + assert "Smoke-executed" not in output + + +def test_complete_check_deadline_stops_before_another_worker( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + source_root = tmp_path / "source" + source_root.mkdir() + notebook = tmp_path / "not-started.ipynb" + notebook.touch() + monotonic_values = iter([10.0, 10.0 + check_notebooks.CHECK_TIMEOUT_SECONDS]) + monkeypatch.setattr(check_notebooks, "SRC", source_root) + monkeypatch.setattr( + check_notebooks.time, + "monotonic", + lambda: next(monotonic_values), + ) + monkeypatch.setattr( + check_notebooks, + "_run_worker", + lambda *_args, **_kwargs: pytest.fail("expired worker must not start"), + ) + + with pytest.raises( + check_notebooks.NotebookCheckError, + match=r"not-started\.ipynb.*complete notebook smoke check exceeded.*900", + ): + check_notebooks.smoke_execute([notebook]) + + +def test_ci_notebook_job_has_outer_timeout() -> None: + workflow = CI_WORKFLOW_PATH.read_text(encoding="utf-8") + notebook_job = workflow.split(" notebooks-smoke:", 1)[1].split( + "\n docs-check:", 1 + )[0] + smoke_step = notebook_job.split( + " - name: Smoke-execute notebooks with Jupyter", 1 + )[1] + + assert "timeout-minutes: 20" in smoke_step + assert "run: python tools/check_notebooks.py" in smoke_step diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index 61e4afd..1ec956f 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -246,12 +246,34 @@ def record(args: tuple[str, ...], **kwargs: object) -> None: "mkdocs", "build", extra_env={"NO_MKDOCS_2_WARNING": "true"}, + timeout=123, ) environment = captured["env"] assert isinstance(environment, dict) assert environment["ATOMREF_PARENT_ENV"] == "preserved" assert environment["NO_MKDOCS_2_WARNING"] == "true" + assert captured["timeout"] == 123 + + +def test_release_notebook_check_has_outer_timeout( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls: list[tuple[tuple[str, ...], dict[str, object]]] = [] + + def record(*args: str, **kwargs: object) -> None: + calls.append((args, kwargs)) + + monkeypatch.setattr(release_check, "_run", record) + + release_check._check_notebooks() + + assert calls == [ + ( + (sys.executable, "tools/check_notebooks.py"), + {"timeout": release_check.NOTEBOOK_CHECK_TIMEOUT_SECONDS}, + ) + ] @pytest.mark.parametrize( diff --git a/tools/README.md b/tools/README.md index 9162c57..f9e80a4 100644 --- a/tools/README.md +++ b/tools/README.md @@ -11,8 +11,9 @@ release preparation. access. - `check_dist.py` — verify wheel and source-distribution contents and optionally test clean base, `notebooks`, and `all` installations from the built wheel. -- `check_notebooks.py` — smoke-execute temporary notebook copies through a - standard Jupyter kernel and discard the resulting outputs. +- `check_notebooks.py` — smoke-execute each temporary notebook copy in an + isolated standard Jupyter child process, enforce startup, cell, and complete + process timeouts, and discard the resulting outputs. - `check_registry.py` — validate curated registry metadata against every packaged scalar and radial payload. - `gen_readme.py` — regenerate `README.md` from `docs/index.md`. diff --git a/tools/check_notebooks.py b/tools/check_notebooks.py index 916b836..89337ae 100644 --- a/tools/check_notebooks.py +++ b/tools/check_notebooks.py @@ -7,11 +7,11 @@ import os from pathlib import Path import shutil +import signal +import subprocess +import sys import tempfile -from unittest.mock import patch - -import nbformat -from nbclient import NotebookClient +import time REPO_ROOT = Path(__file__).resolve().parents[1] @@ -24,11 +24,16 @@ "04-ias-method-selection-study.ipynb", "05-proatomic-density-and-ias.ipynb", ) -DEFAULT_TIMEOUT_SECONDS = 300 +CELL_TIMEOUT_SECONDS = 300 +KERNEL_STARTUP_TIMEOUT_SECONDS = 60 +WORKER_TIMEOUT_SECONDS = 420 +WORKER_TERMINATION_TIMEOUT_SECONDS = 10 +CHECK_TIMEOUT_SECONDS = 15 * 60 +IS_WINDOWS = os.name == "nt" class NotebookCheckError(RuntimeError): - """Raised when the requested notebook smoke check cannot be prepared.""" + """Raised when a notebook smoke check cannot be prepared or completed.""" def default_notebooks() -> tuple[Path, ...]: @@ -59,30 +64,151 @@ def _resolve_notebooks(paths: list[Path]) -> tuple[Path, ...]: return notebooks -def _execute_temporary_copy(path: Path, *, working_dir: Path) -> None: - """Execute and write one temporary notebook copy, failing on cell errors.""" +def _worker_environment(temporary_root: Path) -> dict[str, str]: + """Return an isolated environment for one notebook worker.""" - with path.open(encoding="utf-8") as stream: - notebook = nbformat.read(stream, as_version=4) - kernel_name = notebook.get("metadata", {}).get("kernelspec", {}).get( - "name", "python3" + environment = os.environ.copy() + environment.update( + { + "IPYTHONDIR": str(temporary_root / "ipython"), + "JUPYTER_CONFIG_DIR": str(temporary_root / "jupyter"), + "JUPYTER_RUNTIME_DIR": str(temporary_root / "jupyter-runtime"), + "MPLBACKEND": "Agg", + "MPLCONFIGDIR": str(temporary_root / "matplotlib"), + "PYTHONPYCACHEPREFIX": str(temporary_root / "pycache"), + } ) - client = NotebookClient( - notebook, - timeout=DEFAULT_TIMEOUT_SECONDS, - kernel_name=kernel_name, - resources={"metadata": {"path": str(working_dir)}}, - allow_errors=False, + pythonpath = environment.get("PYTHONPATH") + environment["PYTHONPATH"] = str(temporary_root.parent.parent / "src") + if pythonpath: + environment["PYTHONPATH"] += os.pathsep + pythonpath + return environment + + +def _worker_group_options() -> dict[str, object]: + """Return platform-specific options that isolate a worker process group.""" + + if IS_WINDOWS: + return {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP} + return {"start_new_session": True} + + +def _terminate_worker(process: subprocess.Popen[bytes]) -> None: + """Force-terminate and reap one expired worker and its process group/tree.""" + + if process.poll() is not None: + return + + containment_error: BaseException | None = None + if IS_WINDOWS: + try: + result = subprocess.run( + ["taskkill", "/PID", str(process.pid), "/T", "/F"], + check=False, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + timeout=WORKER_TERMINATION_TIMEOUT_SECONDS, + ) + if result.returncode: + containment_error = NotebookCheckError( + f"taskkill exited with status {result.returncode}" + ) + except (OSError, subprocess.TimeoutExpired) as error: + containment_error = error + if containment_error is not None: + try: + process.kill() + except ProcessLookupError: + pass + else: + # nbclient handles SIGTERM by starting kernel cleanup. Cleanup is the + # phase being contained here, so an expired worker must be killed. + # Jupyter's parent-death handling remains responsible for its + # separately sessioned kernel once this owning worker disappears. + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + pass + + try: + process.wait(timeout=WORKER_TERMINATION_TIMEOUT_SECONDS) + except subprocess.TimeoutExpired: + try: + process.kill() + except ProcessLookupError: + pass + try: + process.wait(timeout=WORKER_TERMINATION_TIMEOUT_SECONDS) + except subprocess.TimeoutExpired as error: + raise NotebookCheckError( + f"worker process {process.pid} could not be reaped after forced " + "termination" + ) from error + + if containment_error is not None: + raise NotebookCheckError( + f"could not confirm termination of Windows worker tree {process.pid}; " + "the direct worker was force-terminated" + ) from containment_error + + +def _run_worker( + path: Path, + *, + working_dir: Path, + runtime_root: Path, + wall_timeout: float = WORKER_TIMEOUT_SECONDS, +) -> None: + """Run one temporary notebook in a bounded isolated child process.""" + + runtime_root.mkdir(parents=True, exist_ok=True) + # The standard CLI owns every Jupyter object and cleanup handler. The + # supervisor only waits for and, when necessary, terminates this process. + command = [ + sys.executable, + "-m", + "jupyter", + "execute", + f"--timeout={CELL_TIMEOUT_SECONDS}", + f"--startup_timeout={KERNEL_STARTUP_TIMEOUT_SECONDS}", + "--Application.log_level=INFO", + "--inplace", + str(path), + ] + process = subprocess.Popen( + command, + cwd=working_dir, + env=_worker_environment(runtime_root), + **_worker_group_options(), ) - executed = client.execute() - with path.open("w", encoding="utf-8", newline="\n") as stream: - nbformat.write(executed, stream) + try: + returncode = process.wait(timeout=wall_timeout) + except subprocess.TimeoutExpired as error: + try: + _terminate_worker(process) + except NotebookCheckError as containment_error: + raise NotebookCheckError( + f"{path.name}: worker exceeded the {wall_timeout:g}-second " + "wall-clock timeout and forced containment also failed: " + f"{containment_error}" + ) from error + raise NotebookCheckError( + f"{path.name}: worker exceeded the {wall_timeout:g}-second " + "wall-clock timeout during Jupyter kernel startup, cell execution, " + "kernel cleanup, or process exit; worker containment completed" + ) from error + if returncode: + raise NotebookCheckError( + f"{path.name}: Jupyter startup, execution, or cleanup failed; " + f"worker exited with status {returncode}" + ) def smoke_execute(paths: list[Path]) -> int: """Execute selected notebooks only after copying them into a temporary tree.""" notebooks = _resolve_notebooks(paths) + deadline = time.monotonic() + CHECK_TIMEOUT_SECONDS with tempfile.TemporaryDirectory(prefix="atomref-notebooks-") as tmp: temporary_root = Path(tmp) temporary_notebooks = temporary_root / "docs" / "notebooks" @@ -98,26 +224,35 @@ def smoke_execute(paths: list[Path]) -> int: shutil.copy2(source, destination) copied.append(destination) - environment = { - "IPYTHONDIR": str(temporary_root / "ipython"), - "JUPYTER_CONFIG_DIR": str(temporary_root / "jupyter"), - "MPLBACKEND": "Agg", - "MPLCONFIGDIR": str(temporary_root / "matplotlib"), - "PYTHONPYCACHEPREFIX": str(temporary_root / "pycache"), - } - pythonpath = os.environ.get("PYTHONPATH") - environment["PYTHONPATH"] = str(temporary_root / "src") - if pythonpath: - environment["PYTHONPATH"] += os.pathsep + pythonpath - - with patch.dict(os.environ, environment, clear=False): - for notebook in copied: - _execute_temporary_copy( - notebook, - working_dir=temporary_notebooks, + for index, notebook in enumerate(copied, start=1): + remaining = deadline - time.monotonic() + if remaining <= 0: + raise NotebookCheckError( + f"{notebook.name}: the complete notebook smoke check exceeded " + f"its {CHECK_TIMEOUT_SECONDS}-second wall-clock timeout before " + "this worker could start" ) + print( + f"[{index}/{len(copied)}] {notebook.name}: phase=Jupyter " + "startup/execution/cleanup", + flush=True, + ) + _run_worker( + notebook, + working_dir=temporary_notebooks, + runtime_root=temporary_root / "runtime" / notebook.stem, + wall_timeout=min(WORKER_TIMEOUT_SECONDS, remaining), + ) + print( + f"[{index}/{len(copied)}] {notebook.name}: phase=process exit; " + "worker exited cleanly", + flush=True, + ) - print(f"Smoke-executed {len(notebooks)} notebook(s) in temporary kernels.") + print( + f"Smoke-executed {len(notebooks)} notebook(s) in temporary kernels.", + flush=True, + ) return 0 diff --git a/tools/release_check.py b/tools/release_check.py index b16e324..a9add65 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -23,21 +23,29 @@ DIST_DIR = REPO_ROOT / "dist" BUILD_DIR = REPO_ROOT / "build" MKDOCS_ENV = {"NO_MKDOCS_2_WARNING": "true"} +NOTEBOOK_CHECK_TIMEOUT_SECONDS = 16 * 60 def _run( *args: str, cwd: Path = REPO_ROOT, extra_env: dict[str, str] | None = None, + timeout: float | None = None, ) -> None: """Run one subprocess command in the selected working directory.""" - print(f"+ [cwd={cwd}]", " ".join(args)) + print(f"+ [cwd={cwd}]", " ".join(args), flush=True) environment = None if extra_env is not None: environment = os.environ.copy() environment.update(extra_env) - subprocess.run(args, cwd=cwd, env=environment, check=True) + subprocess.run( + args, + cwd=cwd, + env=environment, + check=True, + timeout=timeout, + ) def _build_docs() -> None: @@ -46,6 +54,16 @@ def _build_docs() -> None: _run("mkdocs", "build", "--strict", extra_env=MKDOCS_ENV) +def _check_notebooks() -> None: + """Run the internally bounded checker with a final release-gate timeout.""" + + _run( + sys.executable, + "tools/check_notebooks.py", + timeout=NOTEBOOK_CHECK_TIMEOUT_SECONDS, + ) + + def _fresh_build_dirs() -> None: """Remove build artifacts from previous runs.""" @@ -132,7 +150,7 @@ def main() -> int: _assert_clean_worktree() _run("flake8", "src", "tests", "tools") _run(sys.executable, "tools/check_registry.py") - _run(sys.executable, "tools/check_notebooks.py") + _check_notebooks() _run(sys.executable, "tools/gen_readme.py", "--check") _run(sys.executable, "-m", "pytest", "-q") if not args.skip_docs: From a78a23f28595deee912e9ad489f82e7d22d04b2c Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 14:00:07 +0300 Subject: [PATCH 20/23] chore: streamline release metadata and checks --- .github/workflows/ci.yml | 3 +++ .zenodo.json | 28 ------------------------ CHANGELOG.md | 15 +++++++------ CITATION.cff | 5 ++++- pyproject.toml | 1 - tests/meta/test_release_metadata.py | 34 ++++++----------------------- tests/meta/test_release_tools.py | 21 +++++++++++++++++- tools/check_dist.py | 3 +-- tools/release_check.py | 14 +++++++++++- 9 files changed, 56 insertions(+), 68 deletions(-) delete mode 100644 .zenodo.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd61ea3..a32f928 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,9 @@ on: push: pull_request: +permissions: + contents: read + jobs: lint: runs-on: ubuntu-latest diff --git a/.zenodo.json b/.zenodo.json deleted file mode 100644 index 7e91444..0000000 --- a/.zenodo.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "title": "atomref: curated atomic reference data and proatomic densities", - "upload_type": "software", - "access_right": "open", - "description": "atomref is a dependency-free-core Python package for curated atomic reference data, spherical neutral proatomic electron-density profiles, provenance-aware lookup policies, and pairwise reference-atom boundary estimators. The software and accompanying repository content are licensed under LGPL-3.0-or-later except for the bundled neutral proatomic-density snapshot, which is licensed separately under CC BY 4.0. See NOTICE.md for the exact licensing boundary, attribution, source hashes, and source DOIs.", - "creators": [ - { - "name": "Chernyshov, Ivan Yu." - } - ], - "license": "LGPL-3.0-or-later", - "keywords": [ - "chemistry", - "crystallography", - "atomic reference data", - "proatomic density", - "electron density", - "interatomic surfaces" - ], - "related_identifiers": [ - { - "scheme": "doi", - "identifier": "10.5281/zenodo.21291022", - "relation": "isDerivedFrom", - "resource_type": "dataset" - } - ] -} diff --git a/CHANGELOG.md b/CHANGELOG.md index 461811e..3d83cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,8 @@ - Complete structured public API documentation with rendered typed signatures, parameters, returns, raised errors, attributes, examples, and cross-references. - Repository-level `CITATION.cff` metadata for citing `atomref` directly as - software, without a preferred paper citation. -- Controlled `.zenodo.json` software-deposit metadata with the project creator, - primary LGPL-3.0-or-later license, mixed-license notice, keywords, and the - exact upstream proatomic-dataset DOI relation. + software, without a preferred paper citation, and with the bundled-data + licensing boundary recorded in its abstract. - Clean built-wheel installation checks for the base package, `notebooks`, and `all` extras. @@ -39,8 +37,8 @@ - Declared the renderer, execution client, notebook format library, kernel, and plotting library in `notebooks`; clean-install validation now verifies that `all` exactly combines every component extra. -- Included `CITATION.cff` and `.zenodo.json` in source distributions and require - exact copies during artifact validation. +- Included `CITATION.cff` in source distributions and require an exact copy + during artifact validation. - Updated CI, source-distribution checks, and release preparation for the final single-source notebook layout, bounded notebook-process lifecycle, and isolated artifact installations. @@ -49,10 +47,13 @@ builds. - Build release artifacts from a clean committed-source extraction and reject nonstandard executable modes on ordinary wheel and source-distribution files. +- Use an explicit safe tar extraction filter where supported when reconstructing + the committed source tree, avoiding Python 3.14's implicit-filter warning. +- Restrict the CI workflow to read-only repository-content permissions. - Removed generated notebook Markdown, the custom exporter, export-sync tests, and the duplicate documentation copy of the development plan. - Clarified the mixed LGPL-3.0-or-later software and CC BY 4.0 bundled-data - licensing boundary in the shipped notice and Zenodo description. + licensing boundary in the shipped notice and citation metadata. ### Scientific behavior diff --git a/CITATION.cff b/CITATION.cff index ac5c24b..e751acc 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -14,7 +14,10 @@ license: "LGPL-3.0-or-later" abstract: >- A dependency-free-core Python package for curated atomic reference data, spherical neutral proatomic electron-density profiles, provenance-aware - lookup policies, and pairwise reference-atom boundary estimators. + lookup policies, and pairwise reference-atom boundary estimators. The + bundled neutral proatomic-density snapshot is licensed under CC BY 4.0; see + NOTICE.md for the exact licensing boundary, attribution, source hashes, and + source DOIs. keywords: - chemistry - crystallography diff --git a/pyproject.toml b/pyproject.toml index c724788..c01f340 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,7 +113,6 @@ include = [ "/CHANGELOG.md", "/DEV_PLAN.md", "/CITATION.cff", - "/.zenodo.json", "/NOTICE.md", "/LICENSE", "/COPYING", diff --git a/tests/meta/test_release_metadata.py b/tests/meta/test_release_metadata.py index 6ed19ec..bed4323 100644 --- a/tests/meta/test_release_metadata.py +++ b/tests/meta/test_release_metadata.py @@ -1,39 +1,12 @@ from __future__ import annotations -import json from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[2] -ZENODO_METADATA = REPO_ROOT / ".zenodo.json" CITATION_METADATA = REPO_ROOT / "CITATION.cff" -def test_zenodo_metadata_describes_software_and_mixed_licenses() -> None: - metadata = json.loads(ZENODO_METADATA.read_text(encoding="utf-8")) - - assert set(metadata) == { - "title", - "upload_type", - "access_right", - "description", - "creators", - "license", - "keywords", - "related_identifiers", - } - assert metadata["upload_type"] == "software" - assert metadata["license"] == "LGPL-3.0-or-later" - assert metadata["creators"] == [{"name": "Chernyshov, Ivan Yu."}] - assert "CC BY 4.0" in metadata["description"] - assert { - "scheme": "doi", - "identifier": "10.5281/zenodo.21291022", - "relation": "isDerivedFrom", - "resource_type": "dataset", - } in metadata["related_identifiers"] - - def test_citation_metadata_targets_the_versioned_software_repository() -> None: text = CITATION_METADATA.read_text(encoding="utf-8") @@ -45,6 +18,13 @@ def test_citation_metadata_targets_the_versioned_software_repository() -> None: "date-released: 2026-07-15", 'repository-code: "https://github.com/DeloneCommons/atomref"', 'license: "LGPL-3.0-or-later"', + "CC BY 4.0", + "NOTICE.md", ): assert required_line in text assert "preferred-citation:" not in text + + +def test_citation_cff_is_the_only_repository_deposit_metadata_file() -> None: + assert CITATION_METADATA.is_file() + assert not (REPO_ROOT / ".zenodo.json").exists() diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index 1ec956f..e971f4d 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -80,7 +80,6 @@ def test_dist_check_requires_release_tools_and_legacy_data() -> None: assert "tools/check_registry.py" in check_dist.REQUIRED_SDIST_SUFFIXES assert "tools/check_dist.py" in check_dist.REQUIRED_SDIST_SUFFIXES assert "CITATION.cff" in check_dist.REQUIRED_SDIST_SUFFIXES - assert ".zenodo.json" in check_dist.REQUIRED_SDIST_SUFFIXES assert "docs/notebooks/05-proatomic-density-and-ias.ipynb" in ( check_dist.REQUIRED_SDIST_SUFFIXES ) @@ -276,6 +275,26 @@ def record(*args: str, **kwargs: object) -> None: ] +def test_release_source_archive_uses_explicit_safe_filter( + tmp_path: Path, +) -> None: + calls: list[tuple[Path, dict[str, str]]] = [] + + class RecordingArchive: + def extractall(self, path: Path, **kwargs: str) -> None: + calls.append((path, kwargs)) + + archive = RecordingArchive() + release_check._extract_source_archive(archive, tmp_path) + + expected_kwargs = ( + {"filter": "data"} + if hasattr(release_check.tarfile, "data_filter") + else {} + ) + assert calls == [(tmp_path, expected_kwargs)] + + @pytest.mark.parametrize( "workflow", [REPO_ROOT / ".github/workflows/ci.yml", REPO_ROOT / ".github/workflows/docs.yml"], diff --git a/tools/check_dist.py b/tools/check_dist.py index 2d18216..f8128c6 100644 --- a/tools/check_dist.py +++ b/tools/check_dist.py @@ -53,7 +53,6 @@ "CHANGELOG.md", "DEV_PLAN.md", "CITATION.cff", - ".zenodo.json", "COPYING", "LICENSE", "NOTICE.md", @@ -484,7 +483,7 @@ def check_sdist(path: Path) -> None: _assert_members_present(matched, REQUIRED_SDIST_SUFFIXES, label=path.name) _assert_sdist_layout(names, label=path.name) - for filename in ("README.md", "CITATION.cff", ".zenodo.json"): + for filename in ("README.md", "CITATION.cff"): root_member = _sdist_root_member(names, filename, label=path.name) root_file = tf.extractfile(root_member) if root_file is None: diff --git a/tools/release_check.py b/tools/release_check.py index a9add65..9547272 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -100,6 +100,18 @@ def _normalize_source_modes(source_root: Path) -> None: path.chmod(0o644) +def _extract_source_archive( + source_archive: tarfile.TarFile, + source_root: Path, +) -> None: + """Extract a trusted Git archive with an explicit safe filter when available.""" + + if hasattr(tarfile, "data_filter"): + source_archive.extractall(source_root, filter="data") + else: # pragma: no cover - extraction filters were added after Python 3.10 + source_archive.extractall(source_root) + + def _build_from_committed_head() -> None: """Build artifacts from a normalized temporary extraction of ``HEAD``.""" @@ -117,7 +129,7 @@ def _build_from_committed_head() -> None: "HEAD", ) with tarfile.open(archive, mode="r:") as source_archive: - source_archive.extractall(source_root) + _extract_source_archive(source_archive, source_root) _normalize_source_modes(source_root) _run( sys.executable, From 0bc5d8f8fb1b092c2a5fd8bfa76972097cd35f31 Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 16:51:33 +0300 Subject: [PATCH 21/23] release: strengthen typing and metadata validation --- .github/workflows/ci.yml | 24 ++++---- .github/workflows/docs.yml | 4 +- CHANGELOG.md | 9 +++ CITATION.cff | 5 +- README.md | 3 +- docs/index.md | 3 +- pyproject.toml | 8 +++ src/atomref/elements.py | 5 +- src/atomref/policy.py | 82 ++++++++++++++++----------- src/atomref/proatoms.py | 17 ++++-- src/atomref/radii.py | 18 ++++-- src/atomref/registry.py | 19 ++++--- src/atomref/transfer.py | 10 +++- src/atomref/xh.py | 5 +- tests/meta/test_packaging_metadata.py | 23 +++++++- tests/meta/test_release_metadata.py | 73 +++++++++++++++++++----- tests/meta/test_release_tools.py | 13 +++++ tools/README.md | 23 +++++++- tools/check_dist.py | 2 + tools/release_check.py | 2 + 20 files changed, 262 insertions(+), 86 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a32f928..cbcf35e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,8 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install lint dependencies @@ -21,6 +21,10 @@ jobs: python -m pip install ".[dev]" - name: Lint run: flake8 src tests tools + - name: Type check + run: mypy src/atomref + - name: Validate citation metadata + run: cffconvert --validate - name: Validate packaged registry run: python tools/check_registry.py - name: Check README sync @@ -29,8 +33,8 @@ jobs: notebooks-smoke: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install notebook dependencies @@ -44,8 +48,8 @@ jobs: docs-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install docs extras @@ -67,8 +71,8 @@ jobs: matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install @@ -81,8 +85,8 @@ jobs: build-dist: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install build dependencies diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 90e8620..2153aca 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,10 +15,10 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Configure GitHub Pages uses: actions/configure-pages@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install docs extras diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d83cdd..0757942 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,11 @@ contained by explicit time limits. - Clarified that release readiness was reviewed against project criteria rather than claiming independent external review. +- Corrected citation and documentation wording so `NOTICE.md` is described as + the license, attribution, and DOI record, while exact source and payload + hashes remain identified with the packaged registry metadata. +- Kept the PEP 561 marker and corrected inline annotations so the package passes + strict mypy checking against its minimum supported Python 3.10 target. ### Packaging @@ -50,6 +55,10 @@ - Use an explicit safe tar extraction filter where supported when reconstructing the committed source tree, avoiding Python 3.14's implicit-filter warning. - Restrict the CI workflow to read-only repository-content permissions. +- Added strict mypy and CFF 1.2 schema validation to CI and the local release + gate, with repository-specific citation checks retained for atomref metadata. +- Updated the checkout and Python-setup GitHub Actions to their current Node 24 + generations. - Removed generated notebook Markdown, the custom exporter, export-sync tests, and the duplicate documentation copy of the development plan. - Clarified the mixed LGPL-3.0-or-later software and CC BY 4.0 bundled-data diff --git a/CITATION.cff b/CITATION.cff index e751acc..1642d00 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -16,8 +16,9 @@ abstract: >- spherical neutral proatomic electron-density profiles, provenance-aware lookup policies, and pairwise reference-atom boundary estimators. The bundled neutral proatomic-density snapshot is licensed under CC BY 4.0; see - NOTICE.md for the exact licensing boundary, attribution, source hashes, and - source DOIs. + NOTICE.md for the exact licensing boundary, attribution, and source DOIs. + The exact source commit and SHA-256 hashes are recorded in the packaged + registry metadata. keywords: - chemistry - crystallography diff --git a/README.md b/README.md index b4d7756..a3bbced 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,8 @@ Except for separately identified material, the software and accompanying repository content are licensed under LGPL-3.0-or-later. The bundled neutral proatomic-density snapshot is licensed separately under CC BY 4.0. [`NOTICE.md`](https://github.com/DeloneCommons/atomref/blob/main/NOTICE.md) -records the exact boundary, attribution, source hashes, and source DOIs. +records the exact boundary, attribution, and source DOIs. The packaged registry +metadata records the exact source commit and SHA-256 hashes. ## Executable notebook documentation diff --git a/docs/index.md b/docs/index.md index dd14dbb..075cd2a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -155,7 +155,8 @@ Except for separately identified material, the software and accompanying repository content are licensed under LGPL-3.0-or-later. The bundled neutral proatomic-density snapshot is licensed separately under CC BY 4.0. [`NOTICE.md`](https://github.com/DeloneCommons/atomref/blob/main/NOTICE.md) -records the exact boundary, attribution, source hashes, and source DOIs. +records the exact boundary, attribution, and source DOIs. The packaged registry +metadata records the exact source commit and SHA-256 hashes. ## Executable notebook documentation diff --git a/pyproject.toml b/pyproject.toml index c01f340..6caed2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,8 @@ dev = [ "build>=1.2", "twine>=5", "flake8>=7", + "mypy>=2.3,<3", + "cffconvert==2.0.0", ] all = [ "pytest>=7", @@ -88,6 +90,8 @@ all = [ "build>=1.2", "twine>=5", "flake8>=7", + "mypy>=2.3,<3", + "cffconvert==2.0.0", ] [tool.hatch.version] @@ -131,3 +135,7 @@ norecursedirs = [ ".eggs", "*.egg-info", ] + +[tool.mypy] +python_version = "3.10" +strict = true diff --git a/src/atomref/elements.py b/src/atomref/elements.py index 4b062de..019f83f 100644 --- a/src/atomref/elements.py +++ b/src/atomref/elements.py @@ -3,6 +3,7 @@ from __future__ import annotations import csv +import io import re from dataclasses import dataclass from functools import lru_cache @@ -100,7 +101,9 @@ def _load_elements_by_symbol() -> dict[str, Element]: """Load the packaged periodic table into a symbol-keyed mapping.""" table_path = resources.files("atomref.data").joinpath("periodic_table.csv") - with table_path.open("r", encoding="utf-8", newline="") as handle: + with io.TextIOWrapper( + table_path.open("rb"), encoding="utf-8", newline="" + ) as handle: reader = csv.DictReader(handle) out: dict[str, Element] = {} for row in reader: diff --git a/src/atomref/policy.py b/src/atomref/policy.py index 92105b1..b019c56 100644 --- a/src/atomref/policy.py +++ b/src/atomref/policy.py @@ -8,7 +8,14 @@ from functools import lru_cache import math from types import MappingProxyType -from typing import Generic, Literal, TypeVar +from typing import ( + Generic, + Literal, + SupportsFloat, + SupportsIndex, + TypeVar, + cast, +) from .elements import ( canonicalize_element_symbol, @@ -29,10 +36,13 @@ SubstitutionTransfer, SupportsValuePolicy, TransferModel, + TransferValueSource, ) K = TypeVar("K") +_FloatLike = str | bytes | bytearray | memoryview | SupportsFloat | SupportsIndex + LookupSource = Literal[ "override", "base", @@ -176,27 +186,31 @@ def __post_init__(self) -> None: normalized_overrides: dict[str, float] = {} seen_original_keys: dict[str, str] = {} - for key, value in self.overrides.items(): - if not isinstance(key, str): + for override_key, value in self.overrides.items(): + if not isinstance(override_key, str): raise PolicyError( "element-domain policy overrides must be keyed by element " "symbols" ) - sym = _normalize_element_symbol(key) + sym = _normalize_element_symbol(override_key) if sym is None: - raise PolicyError(f"invalid override element symbol: {key!r}") + raise PolicyError( + f"invalid override element symbol: {override_key!r}" + ) if sym in seen_blocked: - raise PolicyError(f"override key {key!r} is blocked in this policy") + raise PolicyError( + f"override key {override_key!r} is blocked in this policy" + ) previous = seen_original_keys.get(sym) - if previous is not None and previous != key: + if previous is not None and previous != override_key: raise PolicyError( - f"override keys {previous!r} and {key!r} both normalize to " - f"{sym!r}" + f"override keys {previous!r} and {override_key!r} both " + f"normalize to {sym!r}" ) - seen_original_keys[sym] = key + seen_original_keys[sym] = override_key normalized_overrides[sym] = _coerce_policy_float( value, - what=f"override value for {key!r}", + what=f"override value for {override_key!r}", ) object.__setattr__( @@ -236,7 +250,7 @@ def _coerce_policy_float(value: object, *, what: str) -> float: """Return a finite float for policy configuration values.""" try: - out = float(value) + out = float(cast(_FloatLike, value)) except (TypeError, ValueError) as exc: raise PolicyError(f"{what} must be a finite float") from exc if not math.isfinite(out): @@ -260,14 +274,14 @@ def _normalize_element_symbol(symbol: str | None) -> str | None: return cand -def _resolve_target_ref(policy: ValuePolicy[object]) -> DatasetRef: +def _resolve_target_ref(policy: ValuePolicy[K]) -> DatasetRef: """Return the target dataset reference implied by a policy base.""" return resolve_scalar_dataset_like(policy.base).ref def _policy_resolution_tokens( - policy: ValuePolicy[object], + policy: ValuePolicy[K], *, owner: object | None = None, ) -> tuple[PolicyToken, ...]: @@ -320,25 +334,26 @@ def _materialize_transfer_source( nested_policy, nested_owner = _coerce_nested_policy(source) if nested_policy is None: - dataset = resolve_scalar_dataset_like(source) - placeholders = tuple( + dataset_source = cast(ScalarDatasetLike, source) + dataset = resolve_scalar_dataset_like(dataset_source) + dataset_placeholders = tuple( False if value is None else _is_placeholder_value(dataset.info, float(value)) for value in dataset.values_by_z ) - lookup_sources = tuple( + dataset_lookup_sources: tuple[LookupSource | None, ...] = tuple( "base" if value is not None else None for value in dataset.values_by_z ) - transfer_depths = tuple( + dataset_transfer_depths = tuple( 0 if value is not None else None for value in dataset.values_by_z ) return _ResolvedElementSource( ref=dataset.ref, values_by_z=dataset.values_by_z, - placeholder_by_z=placeholders, - lookup_source_by_z=lookup_sources, - transfer_depth_by_z=transfer_depths, + placeholder_by_z=dataset_placeholders, + lookup_source_by_z=dataset_lookup_sources, + transfer_depth_by_z=dataset_transfer_depths, via_policy=False, ) @@ -377,7 +392,8 @@ def _lookup_transfer_source_value( nested_policy, nested_owner = _coerce_nested_policy(source) if nested_policy is None: - source_set = resolve_scalar_dataset_like(source) + dataset_source = cast(ScalarDatasetLike, source) + source_set = resolve_scalar_dataset_like(dataset_source) value = source_set.get(symbol) if value is None: return None, f"no value in {source_set.ref.set_id}" @@ -428,7 +444,7 @@ def _transfer_source_is_allowed( lookup_source: LookupSource | None, transfer_depth: int | None, *, - allowed_sources: tuple[str, ...], + allowed_sources: tuple[TransferValueSource, ...], max_depth: int, ) -> bool: """Return whether a nested predictor value may participate downstream.""" @@ -443,7 +459,7 @@ def _explain_rejected_transfer_source( source_role: str, lookup_source: LookupSource | None, transfer_depth: int | None, - allowed_sources: tuple[str, ...], + allowed_sources: tuple[TransferValueSource, ...], max_depth: int, ) -> str: """Return a human-readable explanation for a rejected nested source.""" @@ -468,7 +484,7 @@ def _fit_linear_transfer( *, min_points: int, exclude_placeholders: bool, - fit_sources: tuple[str, ...], + fit_sources: tuple[TransferValueSource, ...], fit_max_depth: int, ) -> LinearFit: """Fit a one-predictor linear transfer model between two sources.""" @@ -541,7 +557,7 @@ def _fit_linear_transfer_cached( predictor_ref: DatasetRef, min_points: int, exclude_placeholders: bool, - fit_sources: tuple[str, ...], + fit_sources: tuple[TransferValueSource, ...], fit_max_depth: int, ) -> LinearFit: """Cache fits between two packaged datasets for repeated reuse.""" @@ -725,12 +741,14 @@ def _resolve_value( sym = _normalize_element_symbol(symbol) if sym is None: - note = "unknown element" if symbol is not None else "missing element symbol" + missing_note = ( + "unknown element" if symbol is not None else "missing element symbol" + ) return LookupResult( value=None, source="missing", target=target, - notes=(note,), + notes=(missing_note,), ) if sym in policy.blocked: @@ -772,13 +790,13 @@ def _resolve_value( transfer_notes: list[str] = ["missing in base set"] for transfer in policy.transfers: if isinstance(transfer, SubstitutionTransfer): - result, note = _apply_substitution_transfer( + result, transfer_note = _apply_substitution_transfer( sym, target=target, transfer=transfer, ) elif isinstance(transfer, LinearTransfer): - result, note = _apply_linear_transfer( + result, transfer_note = _apply_linear_transfer( sym, base=policy.base, target=target, @@ -789,8 +807,8 @@ def _resolve_value( if result is not None: return result - if note: - transfer_notes.append(note) + if transfer_note: + transfer_notes.append(transfer_note) if policy.fallback is not None: return LookupResult( diff --git a/src/atomref/proatoms.py b/src/atomref/proatoms.py index 9b6113d..d694481 100644 --- a/src/atomref/proatoms.py +++ b/src/atomref/proatoms.py @@ -7,7 +7,7 @@ from dataclasses import dataclass, field from functools import lru_cache import math -from typing import Literal +from typing import Literal, SupportsFloat, SupportsIndex, cast from .elements import Element, get_element, iter_elements from .errors import DatasetError @@ -50,6 +50,8 @@ _CUTOFF_REPRODUCTION_REL_TOL = 5.0e-14 _FLOAT_COMPARISON_REL_TOL = 64.0 * 2.220446049250313e-16 +_FloatLike = str | bytes | bytearray | memoryview | SupportsFloat | SupportsIndex + @dataclass(frozen=True, slots=True) class IASPositionResult: @@ -203,7 +205,9 @@ def _coerce_public_max_radius(storage: Mapping[str, object], ref: DatasetRef) -> """Return the finite positive public radius limit declared by metadata.""" try: - public_max = float(storage["public_max_radius_bohr"]) + public_max = float( + cast(_FloatLike, storage["public_max_radius_bohr"]) + ) except (KeyError, TypeError, ValueError) as exc: raise DatasetError(f"invalid public radius limit for dataset: {ref!r}") from exc if not math.isfinite(public_max) or public_max <= 0.0: @@ -1110,12 +1114,12 @@ def _native_boundary_estimate( cutoff_regime=regime, ) - position, dominant_side = _equal_contribution_position( + equal_position, dominant_side = _equal_contribution_position( profile_a, profile_b, distance_bohr, ) - if position is None: + if equal_position is None: return _NativeIASResult( requested_mode="boundary", method="none", @@ -1135,13 +1139,13 @@ def _native_boundary_estimate( profile_a, profile_b, distance_bohr, - position, + equal_position, ) return _NativeIASResult( requested_mode="boundary", method="equal_proatom_density", status="ok", - position_bohr=position, + position_bohr=equal_position, rho_a=rho_a, rho_b=rho_b, rho_sum=rho_sum, @@ -1757,6 +1761,7 @@ def _estimate_pairwise( contact_distance_bohr = ( canonical_a.cutoff_radius_bohr + canonical_b.cutoff_radius_bohr ) + contact_distances_requested: tuple[float, ...] if distance_unit == "bohr": contact_distances_requested = (contact_distance_bohr,) else: diff --git a/src/atomref/radii.py b/src/atomref/radii.py index bd54ef8..dd5cdbd 100644 --- a/src/atomref/radii.py +++ b/src/atomref/radii.py @@ -5,7 +5,7 @@ from collections.abc import Iterable, Mapping from dataclasses import dataclass, field import math -from typing import Literal +from typing import Literal, SupportsFloat, SupportsIndex, cast from .elements import canonicalize_element_symbol, get_element, is_valid_element_symbol from .errors import PolicyError @@ -33,6 +33,8 @@ RadiiSet = ElementScalarSet """Typing alias for an immutable element-indexed radii dataset.""" +_FloatLike = str | bytes | bytearray | memoryview | SupportsFloat | SupportsIndex + _KIND_TO_QUANTITY = { "covalent": "covalent_radius", @@ -86,6 +88,7 @@ def as_value_policy(self) -> ValuePolicy[str]: """ quantity = _quantity_for_kind(self.kind) + base: DatasetRef | ElementScalarSet if isinstance(self.base_set, ElementScalarSet): if self.base_set.ref.quantity != quantity: msg = ( @@ -187,7 +190,7 @@ def _coerce_non_negative_radii_value(value: object, *, what: str) -> float: """ try: - out = float(value) + out = float(cast(_FloatLike, value)) except (TypeError, ValueError) as exc: raise PolicyError(f"{what} must be a finite float") from exc if not math.isfinite(out): @@ -226,9 +229,14 @@ def _normalize_assessment_elements(elements: Iterable[str]) -> tuple[str, ...]: if not is_valid_element_symbol(sym): raise ValueError(f"invalid element symbol: {sym!r}") symbols.add(sym) - return tuple( - sorted(symbols, key=lambda s: get_element(s).z if get_element(s) else 0) - ) + + def atomic_number(symbol: str) -> int: + element = get_element(symbol) + if element is None: # pragma: no cover - validated above + raise ValueError(f"invalid element symbol: {symbol!r}") + return element.z + + return tuple(sorted(symbols, key=atomic_number)) def list_radii_sets( diff --git a/src/atomref/registry.py b/src/atomref/registry.py index 0674509..4254cff 100644 --- a/src/atomref/registry.py +++ b/src/atomref/registry.py @@ -13,6 +13,7 @@ import stat from types import MappingProxyType import unicodedata +from typing import SupportsFloat, SupportsIndex, cast import zipfile import zlib @@ -25,6 +26,8 @@ DomainId = str """Typing alias for a registry lookup-domain identifier.""" +_FloatLike = str | bytes | bytearray | memoryview | SupportsFloat | SupportsIndex + @dataclass(frozen=True, slots=True) class DatasetRef: @@ -246,6 +249,8 @@ def from_mapping( for key, value in values.items(): sym = _normalize_element_domain_symbol(key) + if sym is None: + raise DatasetError(f"invalid element symbol in custom set: {key!r}") elem = get_element(sym) if elem is None: raise DatasetError(f"invalid element symbol in custom set: {key!r}") @@ -427,7 +432,7 @@ def _coerce_finite_float(value: object, *, what: str) -> float: """ try: - out = float(value) + out = float(cast(_FloatLike, value)) except (TypeError, ValueError) as exc: raise DatasetError(f"{what} must be a finite float") from exc if not math.isfinite(out): @@ -707,16 +712,14 @@ def get_dataset_info(ref: DatasetRef) -> DatasetInfo: if isinstance(raw_entry.get("storage"), dict) else None ) + raw_name = raw_entry.get("name") + name = raw_name if isinstance(raw_name, str) else actual_ref.set_id return DatasetInfo( ref=actual_ref, domain=domain, units=units, - name=( - raw_entry.get("name") - if isinstance(raw_entry.get("name"), str) - else actual_ref.set_id - ), + name=name, description=( raw_entry.get("description") if isinstance(raw_entry.get("description"), str) @@ -773,7 +776,9 @@ def _load_csv_columns(filename: str) -> dict[str, tuple[float | None, ...]]: """Load all value columns from one packaged dense-by-Z CSV table.""" path = resources.files("atomref.data").joinpath(filename) - with path.open("r", encoding="utf-8", newline="") as handle: + with io.TextIOWrapper( + path.open("rb"), encoding="utf-8", newline="" + ) as handle: reader = csv.DictReader(handle) if reader.fieldnames is None or "z" not in reader.fieldnames: raise DatasetError(f"invalid CSV file: {filename!r}") diff --git a/src/atomref/transfer.py b/src/atomref/transfer.py index e5e6c9b..272c9b3 100644 --- a/src/atomref/transfer.py +++ b/src/atomref/transfer.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, Literal, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Literal, Protocol, TypeGuard, runtime_checkable from .errors import PolicyError from .registry import ScalarDatasetLike @@ -43,6 +43,12 @@ ) +def _is_transfer_value_source(source: str) -> TypeGuard[TransferValueSource]: + """Return whether ``source`` is an admitted nested-result label.""" + + return source in _ALLOWED_TRANSFER_VALUE_SOURCES + + @runtime_checkable class SupportsValuePolicy(Protocol): """Protocol for wrappers that expose a generic scalar value policy. @@ -214,7 +220,7 @@ def _normalize_transfer_value_sources( normalized: list[TransferValueSource] = [] seen: set[str] = set() for source in sources: - if source not in _ALLOWED_TRANSFER_VALUE_SOURCES: + if not _is_transfer_value_source(source): allowed = ", ".join(sorted(_ALLOWED_TRANSFER_VALUE_SOURCES)) raise PolicyError( f"LinearTransfer {field_name} contains unsupported source " diff --git a/src/atomref/xh.py b/src/atomref/xh.py index 9408265..14d9980 100644 --- a/src/atomref/xh.py +++ b/src/atomref/xh.py @@ -5,6 +5,7 @@ from collections.abc import Mapping from dataclasses import dataclass, field import math +from typing import SupportsFloat, SupportsIndex, cast from .elements import canonicalize_element_symbol, is_valid_element_symbol from .errors import PolicyError @@ -29,6 +30,7 @@ """Typing alias for an immutable parent-element X-H bond-length dataset.""" _QUANTITY = "xh_bond_length" +_FloatLike = str | bytes | bytearray | memoryview | SupportsFloat | SupportsIndex @dataclass(frozen=True, slots=True) @@ -74,6 +76,7 @@ def as_value_policy(self) -> ValuePolicy[str]: or if H/D/T is used as an override parent. """ + base: DatasetRef | ElementScalarSet if isinstance(self.base_set, ElementScalarSet): if self.base_set.ref.quantity != _QUANTITY: raise PolicyError( @@ -116,7 +119,7 @@ def _coerce_non_negative_xh_value(value: object, *, what: str) -> float: """Validate an X-H-like policy number.""" try: - out = float(value) + out = float(cast(_FloatLike, value)) except (TypeError, ValueError) as exc: raise PolicyError(f"{what} must be a finite float") from exc if not math.isfinite(out): diff --git a/tests/meta/test_packaging_metadata.py b/tests/meta/test_packaging_metadata.py index 9b121ba..7017ee8 100644 --- a/tests/meta/test_packaging_metadata.py +++ b/tests/meta/test_packaging_metadata.py @@ -54,10 +54,31 @@ def test_all_extra_includes_contributor_tooling() -> None: project = _project_metadata() all_requirements = "\n".join(project["optional-dependencies"]["all"]).lower() - for contributor_package in ("build", "flake8", "pytest", "twine"): + for contributor_package in ( + "build", + "cffconvert", + "flake8", + "mypy", + "pytest", + "twine", + ): assert contributor_package in all_requirements +def test_dev_extra_contains_release_validation_tools() -> None: + project = _project_metadata() + dev_requirements = "\n".join(project["optional-dependencies"]["dev"]).lower() + + assert "mypy" in dev_requirements + assert "cffconvert" in dev_requirements + + +def test_mypy_uses_strict_minimum_python_configuration() -> None: + config = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))["tool"]["mypy"] + + assert config == {"python_version": "3.10", "strict": True} + + def test_docs_extra_contains_only_used_documentation_tooling() -> None: project = _project_metadata() docs_requirements = "\n".join(project["optional-dependencies"]["docs"]) diff --git a/tests/meta/test_release_metadata.py b/tests/meta/test_release_metadata.py index bed4323..bb335f7 100644 --- a/tests/meta/test_release_metadata.py +++ b/tests/meta/test_release_metadata.py @@ -1,28 +1,75 @@ from __future__ import annotations from pathlib import Path +import re + +from atomref import __version__ REPO_ROOT = Path(__file__).resolve().parents[2] CITATION_METADATA = REPO_ROOT / "CITATION.cff" +CHANGELOG = REPO_ROOT / "CHANGELOG.md" + + +def _top_level_scalar(text: str, key: str) -> str: + """Return one simple top-level CFF scalar after schema validation.""" + + match = re.search( + rf"^{re.escape(key)}:\s*(?P.+)$", + text, + flags=re.MULTILINE, + ) + assert match is not None, f"missing top-level CFF field: {key}" + value = match.group("value").strip() + if value.startswith('"') and value.endswith('"'): + return value[1:-1] + return value + + +def _current_changelog_release() -> tuple[str, str]: + """Return the first version and date recorded in the changelog.""" + + text = CHANGELOG.read_text(encoding="utf-8") + match = re.search( + r"^## (?P\d+\.\d+\.\d+) - " + r"(?P\d{4}-\d{2}-\d{2})$", + text, + flags=re.MULTILINE, + ) + assert match is not None + return match.group("version"), match.group("date") def test_citation_metadata_targets_the_versioned_software_repository() -> None: text = CITATION_METADATA.read_text(encoding="utf-8") + changelog_version, changelog_date = _current_changelog_release() + + assert _top_level_scalar(text, "cff-version") == "1.2.0" + assert _top_level_scalar(text, "title") == "atomref" + assert _top_level_scalar(text, "type") == "software" + assert _top_level_scalar(text, "version") == __version__ == changelog_version + assert _top_level_scalar(text, "date-released") == changelog_date + assert _top_level_scalar(text, "repository-code") == ( + "https://github.com/DeloneCommons/atomref" + ) + assert _top_level_scalar(text, "license") == "LGPL-3.0-or-later" + + +def test_citation_abstract_describes_the_mixed_license_and_hash_locations() -> None: + text = CITATION_METADATA.read_text(encoding="utf-8") + normalized = " ".join(text.split()) - for required_line in ( - "cff-version: 1.2.0", - 'title: "atomref"', - "type: software", - 'version: "0.2.1"', - "date-released: 2026-07-15", - 'repository-code: "https://github.com/DeloneCommons/atomref"', - 'license: "LGPL-3.0-or-later"', - "CC BY 4.0", - "NOTICE.md", - ): - assert required_line in text - assert "preferred-citation:" not in text + assert "CC BY 4.0" in normalized + assert ( + "NOTICE.md for the exact licensing boundary, attribution, and source DOIs." + in normalized + ) + assert ( + "The exact source commit and SHA-256 hashes are recorded in the packaged " + "registry metadata." + in normalized + ) + assert re.search(r"^preferred-citation:", text, flags=re.MULTILINE) is None def test_citation_cff_is_the_only_repository_deposit_metadata_file() -> None: diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index e971f4d..2a06da7 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -303,3 +303,16 @@ def test_docs_workflows_suppress_material_banner(workflow: Path) -> None: text = workflow.read_text(encoding="utf-8") assert 'NO_MKDOCS_2_WARNING: "true"' in text + + +@pytest.mark.parametrize( + "workflow", + [REPO_ROOT / ".github/workflows/ci.yml", REPO_ROOT / ".github/workflows/docs.yml"], +) +def test_workflows_use_current_node_action_generations(workflow: Path) -> None: + text = workflow.read_text(encoding="utf-8") + + assert "actions/checkout@v7" in text + assert "actions/setup-python@v6" in text + assert "actions/checkout@v4" not in text + assert "actions/setup-python@v5" not in text diff --git a/tools/README.md b/tools/README.md index f9e80a4..250bfde 100644 --- a/tools/README.md +++ b/tools/README.md @@ -18,12 +18,15 @@ release preparation. packaged scalar and radial payload. - `gen_readme.py` — regenerate `README.md` from `docs/index.md`. - `release_check.py` — run the full release-preparation checklist, - including linting, tests, docs, a clean committed-source build with - conventional archive modes, and artifact validation. + including linting, strict type and citation-schema checks, tests, docs, a + clean committed-source build with conventional archive modes, and artifact + validation. ## Typical commands ```bash +mypy src/atomref +cffconvert --validate python tools/check_registry.py python tools/check_notebooks.py python tools/gen_readme.py @@ -31,6 +34,22 @@ python tools/check_dist.py dist --check-installs python tools/release_check.py ``` +## Citation metadata checks + +`cffconvert --validate` parses `CITATION.cff` and validates it against the CFF +1.2 schema. It catches malformed YAML, unsupported fields, missing required +fields, and invalid field values. The focused repository test checks facts that +the general schema cannot know, such as atomref's version, release date, +repository URL, license boundary, and provenance wording: + +```bash +pytest tests/meta/test_release_metadata.py +``` + +For a new release, update `version` and `date-released` in `CITATION.cff`, then +run both commands. `release_check.py` runs the schema validation and the complete +test suite automatically. + ## Neutral proatomic-density snapshot Run the snapshot builder against the immutable local reference dataset. Write diff --git a/tools/check_dist.py b/tools/check_dist.py index f8128c6..759d76b 100644 --- a/tools/check_dist.py +++ b/tools/check_dist.py @@ -102,10 +102,12 @@ ALL_IMPORTS = f"""\ {NOTEBOOKS_IMPORTS} import build +import cffconvert import flake8 import material import mkdocstrings import mkdocstrings_handlers.python +import mypy import pymdownx import pytest import twine diff --git a/tools/release_check.py b/tools/release_check.py index 9547272..893f1f5 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -161,6 +161,8 @@ def main() -> int: _assert_clean_worktree() _run("flake8", "src", "tests", "tools") + _run("mypy", "src/atomref") + _run("cffconvert", "--validate") _run(sys.executable, "tools/check_registry.py") _check_notebooks() _run(sys.executable, "tools/gen_readme.py", "--check") From 41dd0d83bb6863f9bb1cec418e8fab2f70f1a54e Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 17:44:07 +0300 Subject: [PATCH 22/23] fix: align release mypy invocation and workflow actions --- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/docs.yml | 2 +- tests/meta/test_release_tools.py | 20 +++++++++++++++++++- tools/README.md | 2 +- tools/release_check.py | 8 +++++++- 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbcf35e..3296cff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -22,7 +22,7 @@ jobs: - name: Lint run: flake8 src tests tools - name: Type check - run: mypy src/atomref + run: python -m mypy src/atomref - name: Validate citation metadata run: cffconvert --validate - name: Validate packaged registry @@ -33,7 +33,7 @@ jobs: notebooks-smoke: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -48,7 +48,7 @@ jobs: docs-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -71,7 +71,7 @@ jobs: matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -85,7 +85,7 @@ jobs: build-dist: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.12" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2153aca..1cb4d08 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,7 +15,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Configure GitHub Pages uses: actions/configure-pages@v5 - uses: actions/setup-python@v6 diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index 2a06da7..0e89022 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -275,6 +275,23 @@ def record(*args: str, **kwargs: object) -> None: ] +def test_release_type_check_uses_running_python( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls: list[tuple[tuple[str, ...], dict[str, object]]] = [] + + def record(*args: str, **kwargs: object) -> None: + calls.append((args, kwargs)) + + monkeypatch.setattr(release_check, "_run", record) + + release_check._check_types() + + assert calls == [ + ((sys.executable, "-m", "mypy", "src/atomref"), {}), + ] + + def test_release_source_archive_uses_explicit_safe_filter( tmp_path: Path, ) -> None: @@ -312,7 +329,8 @@ def test_docs_workflows_suppress_material_banner(workflow: Path) -> None: def test_workflows_use_current_node_action_generations(workflow: Path) -> None: text = workflow.read_text(encoding="utf-8") - assert "actions/checkout@v7" in text + assert "actions/checkout@v6" in text assert "actions/setup-python@v6" in text assert "actions/checkout@v4" not in text + assert "actions/checkout@v7" not in text assert "actions/setup-python@v5" not in text diff --git a/tools/README.md b/tools/README.md index 250bfde..b54f614 100644 --- a/tools/README.md +++ b/tools/README.md @@ -25,7 +25,7 @@ release preparation. ## Typical commands ```bash -mypy src/atomref +python -m mypy src/atomref cffconvert --validate python tools/check_registry.py python tools/check_notebooks.py diff --git a/tools/release_check.py b/tools/release_check.py index 893f1f5..6df19b9 100644 --- a/tools/release_check.py +++ b/tools/release_check.py @@ -54,6 +54,12 @@ def _build_docs() -> None: _run("mkdocs", "build", "--strict", extra_env=MKDOCS_ENV) +def _check_types() -> None: + """Run mypy with the same Python environment as this release check.""" + + _run(sys.executable, "-m", "mypy", "src/atomref") + + def _check_notebooks() -> None: """Run the internally bounded checker with a final release-gate timeout.""" @@ -161,7 +167,7 @@ def main() -> int: _assert_clean_worktree() _run("flake8", "src", "tests", "tools") - _run("mypy", "src/atomref") + _check_types() _run("cffconvert", "--validate") _run(sys.executable, "tools/check_registry.py") _check_notebooks() From c4af7592987abb6e7853548b88ea7430f5c3658f Mon Sep 17 00:00:00 2001 From: Ivan Chernyshov Date: Wed, 15 Jul 2026 20:11:57 +0300 Subject: [PATCH 23/23] fix: proatomic validation precedence and IAS typing --- .github/workflows/ci.yml | 10 +-- .github/workflows/docs.yml | 2 +- CHANGELOG.md | 2 + src/atomref/proatoms.py | 127 +++++++++++++++++++------------ tests/meta/test_release_tools.py | 3 +- tests/proatoms/test_density.py | 72 ++++++++++++++++++ tests/proatoms/test_ias.py | 17 +++++ 7 files changed, 178 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3296cff..9563faa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -33,7 +33,7 @@ jobs: notebooks-smoke: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -48,7 +48,7 @@ jobs: docs-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -71,7 +71,7 @@ jobs: matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -85,7 +85,7 @@ jobs: build-dist: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: "3.12" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1cb4d08..2153aca 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,7 +15,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Configure GitHub Pages uses: actions/configure-pages@v5 - uses: actions/setup-python@v6 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0757942..cdd55fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ hashes remain identified with the packaged registry metadata. - Kept the PEP 561 marker and corrected inline annotations so the package passes strict mypy checking against its minimum supported Python 3.10 target. +- Validate proatomic-density datasets, units, and radii before missing-profile + fallback, and reject boolean radii explicitly. ### Packaging diff --git a/src/atomref/proatoms.py b/src/atomref/proatoms.py index d694481..b6d50cd 100644 --- a/src/atomref/proatoms.py +++ b/src/atomref/proatoms.py @@ -52,6 +52,27 @@ _FloatLike = str | bytes | bytearray | memoryview | SupportsFloat | SupportsIndex +_IASRequestedMode = Literal["boundary", "minimum"] +_IASMethod = Literal[ + "homonuclear_midpoint", + "equal_proatom_density", + "cutoff_gap_midpoint", + "promolecular_density_minimum", + "none", +] +_IASStatus = Literal[ + "ok", + "low_density_gap", + "one_atom_dominates", + "no_resolved_interior_minimum", + "boundary_dominated", + "ambiguous_competing_minima", + "search_unstable", +] +_CutoffRegime = Literal["overlap", "contact", "gap"] +_NativeDominantSide = Literal["a", "b"] +_DominantAtomRole = Literal["atom_a", "atom_b"] + @dataclass(frozen=True, slots=True) class IASPositionResult: @@ -134,23 +155,9 @@ class IASPositionResult: distance: float distance_unit: str density_unit: str - requested_mode: Literal["boundary", "minimum"] - method: Literal[ - "homonuclear_midpoint", - "equal_proatom_density", - "cutoff_gap_midpoint", - "promolecular_density_minimum", - "none", - ] - status: Literal[ - "ok", - "low_density_gap", - "one_atom_dominates", - "no_resolved_interior_minimum", - "boundary_dominated", - "ambiguous_competing_minima", - "search_unstable", - ] + requested_mode: _IASRequestedMode + method: _IASMethod + status: _IASStatus position_from_a: float | None position_from_b: float | None fraction_from_a: float | None @@ -161,9 +168,9 @@ class IASPositionResult: cutoff_radius_a: float cutoff_radius_b: float contour_separation: float - cutoff_regime: Literal["overlap", "contact", "gap"] + cutoff_regime: _CutoffRegime dominant_atom: str | None - dominant_atom_role: Literal["atom_a", "atom_b"] | None + dominant_atom_role: _DominantAtomRole | None alternative_position_from_a: float | None alternative_position_from_b: float | None alternative_rho_sum: float | None @@ -215,6 +222,18 @@ def _coerce_public_max_radius(storage: Mapping[str, object], ref: DatasetRef) -> return public_max +def _dataset_public_max_radius_bohr(dataset: ElementRadialSet) -> float: + """Validate and return one dataset's declared public radius limit.""" + + storage = _require_storage(dataset.info) + public_max = _coerce_public_max_radius(storage, dataset.ref) + if not dataset.radii or public_max > dataset.radii[-1]: + raise DatasetError( + f"radial grid does not bracket the public limit for {dataset.ref!r}" + ) + return public_max + + @dataclass(frozen=True, slots=True) class ProatomicDensityProfile: """Immutable view of one neutral spherical proatomic-density profile. @@ -275,7 +294,6 @@ def __post_init__(self) -> None: ) object.__setattr__(self, "symbol", element.symbol) - storage = _require_storage(self.dataset.info) radii = self.dataset.radii densities = self.dataset.get(self.atomic_number) if densities is None: @@ -301,12 +319,7 @@ def __post_init__(self) -> None: f"{self.dataset.ref!r}, Z={self.atomic_number}" ) - public_max = _coerce_public_max_radius(storage, self.dataset.ref) - if public_max > radii[-1]: - raise DatasetError( - f"radial grid does not bracket the public limit for " - f"{self.dataset.ref!r}" - ) + public_max = _dataset_public_max_radius_bohr(self.dataset) object.__setattr__(self, "_densities", densities) object.__setattr__(self, "_log_radii", tuple(math.log(r) for r in radii)) @@ -497,6 +510,8 @@ def _radius_to_bohr( if radius_unit not in {"angstrom", "bohr"}: raise ValueError(f"unknown radius unit: {radius_unit!r}") + if isinstance(radius, bool): + raise ValueError("radius must be a finite non-negative scalar") try: value = float(radius) except (TypeError, ValueError) as exc: @@ -606,6 +621,16 @@ def get_proatomic_density_set( return loaded +def _resolve_proatomic_density_set( + set_id: str, +) -> tuple[ProatomicDensitySet, float]: + """Resolve and load one selected proatomic-density dataset.""" + + info = get_proatomic_density_set_info(set_id) + dataset = get_proatomic_density_set(info.ref.set_id) + return dataset, _dataset_public_max_radius_bohr(dataset) + + @lru_cache(maxsize=None) def _get_element_by_atomic_number_cached( atomic_number: int, @@ -689,11 +714,10 @@ def get_proatomic_density_profile( scalar [ValuePolicy][atomref.ValuePolicy] is applied. """ + dataset, _ = _resolve_proatomic_density_set(set_id) resolved = _resolve_density_element(element) if resolved is None: return None - info = get_proatomic_density_set_info(set_id) - dataset = get_proatomic_density_set(info.ref.set_id) if dataset.get(resolved.z) is None: return None return _get_profile_cached(dataset.ref, resolved.z) @@ -740,12 +764,20 @@ def get_proatomic_density( dependency-free, with no extrapolation beyond 20 bohr. """ - profile = get_proatomic_density_profile(element, set_id=set_id) - if profile is None: - return None - return profile( + dataset, public_max_radius_bohr = _resolve_proatomic_density_set(set_id) + radius_bohr = _radius_to_bohr( radius, radius_unit=radius_unit, + public_max_bohr=public_max_radius_bohr, + ) + _density_from_native(1.0, density_unit=density_unit) + + resolved = _resolve_density_element(element) + if resolved is None or dataset.get(resolved.z) is None: + return None + profile = _get_profile_cached(dataset.ref, resolved.z) + return _density_from_native( + profile._evaluate_bohr(radius_bohr), density_unit=density_unit, ) @@ -792,9 +824,9 @@ def selected(self) -> _MinimumCandidate | None: class _NativeIASResult: """Pairwise result before requested orientation and unit conversion.""" - requested_mode: str - method: str - status: str + requested_mode: _IASRequestedMode + method: _IASMethod + status: _IASStatus position_bohr: float | None rho_a: float | None rho_b: float | None @@ -802,8 +834,8 @@ class _NativeIASResult: cutoff_radius_a_bohr: float cutoff_radius_b_bohr: float contour_separation_bohr: float - cutoff_regime: str - dominant_side: str | None = None + cutoff_regime: _CutoffRegime + dominant_side: _NativeDominantSide | None = None alternative_position_bohr: float | None = None alternative_rho_sum: float | None = None relative_depth_gap: float | None = None @@ -1004,7 +1036,7 @@ def _cutoff_regime( distance_bohr: float, cutoff_radius_a_bohr: float, cutoff_radius_b_bohr: float, -) -> tuple[float, str]: +) -> tuple[float, _CutoffRegime]: """Return signed contour separation and its exact-sign regime.""" radius_sum = cutoff_radius_a_bohr + cutoff_radius_b_bohr @@ -1020,7 +1052,7 @@ def _equal_contribution_position( profile_a: _PreparedPairwiseProfile, profile_b: _PreparedPairwiseProfile, distance_bohr: float, -) -> tuple[float | None, str | None]: +) -> tuple[float | None, _NativeDominantSide | None]: """Solve the continuous log-density equality by bracketed bisection.""" def difference(position: float) -> float: @@ -1526,7 +1558,7 @@ def _native_minimum_estimate( ) ambiguous = relative_depth_gap <= _COMPETITIVE_RELATIVE_DEPTH - status = "ambiguous_competing_minima" if ambiguous else "ok" + status: _IASStatus = "ambiguous_competing_minima" if ambiguous else "ok" if not search_converged: status = "search_unstable" @@ -1660,7 +1692,7 @@ def oriented_coordinate_pair( cutoff_b_native = native.cutoff_radius_b_bohr dominant_atom = None - dominant_atom_role = None + dominant_atom_role: _DominantAtomRole | None = None if native.dominant_side is not None: canonical_dominant = ( canonical_profile_a @@ -1679,9 +1711,9 @@ def oriented_coordinate_pair( distance=requested_distance, distance_unit=distance_unit, density_unit=density_unit, - requested_mode=native.requested_mode, # type: ignore[arg-type] - method=native.method, # type: ignore[arg-type] - status=native.status, # type: ignore[arg-type] + requested_mode=native.requested_mode, + method=native.method, + status=native.status, position_from_a=position_a, position_from_b=position_b, fraction_from_a=fraction_a, @@ -1694,9 +1726,9 @@ def oriented_coordinate_pair( cutoff_radius_a=cutoff_a_native * distance_factor, cutoff_radius_b=cutoff_b_native * distance_factor, contour_separation=native.contour_separation_bohr * distance_factor, - cutoff_regime=native.cutoff_regime, # type: ignore[arg-type] + cutoff_regime=native.cutoff_regime, dominant_atom=dominant_atom, - dominant_atom_role=dominant_atom_role, # type: ignore[arg-type] + dominant_atom_role=dominant_atom_role, alternative_position_from_a=alternative_a, alternative_position_from_b=alternative_b, alternative_rho_sum=( @@ -1737,12 +1769,13 @@ def _estimate_pairwise( ) _density_output_factor(density_unit) + dataset, _ = _resolve_proatomic_density_set(set_id) + resolved_a = _resolve_density_element(atom_a) resolved_b = _resolve_density_element(atom_b) if resolved_a is None or resolved_b is None: return None - dataset = get_proatomic_density_set(set_id) if dataset.get(resolved_a.z) is None or dataset.get(resolved_b.z) is None: return None requested_profile_a = _get_profile_cached(dataset.ref, resolved_a.z) diff --git a/tests/meta/test_release_tools.py b/tests/meta/test_release_tools.py index 0e89022..6f80dc4 100644 --- a/tests/meta/test_release_tools.py +++ b/tests/meta/test_release_tools.py @@ -329,8 +329,7 @@ def test_docs_workflows_suppress_material_banner(workflow: Path) -> None: def test_workflows_use_current_node_action_generations(workflow: Path) -> None: text = workflow.read_text(encoding="utf-8") - assert "actions/checkout@v6" in text + assert "actions/checkout@v7" in text assert "actions/setup-python@v6" in text assert "actions/checkout@v4" not in text - assert "actions/checkout@v7" not in text assert "actions/setup-python@v5" not in text diff --git a/tests/proatoms/test_density.py b/tests/proatoms/test_density.py index f3075ad..3984b20 100644 --- a/tests/proatoms/test_density.py +++ b/tests/proatoms/test_density.py @@ -249,6 +249,54 @@ def test_negative_and_non_finite_radii_raise(radius: float) -> None: ar.get_proatomic_density("O", radius, radius_unit="bohr") +@pytest.mark.parametrize( + "radius", + [ + -1.0, + math.nan, + math.inf, + -math.inf, + math.nextafter(20.0, math.inf), + [1.0], + object(), + True, + False, + ], + ids=[ + "negative", + "nan", + "positive-infinity", + "negative-infinity", + "above-public-limit", + "nonscalar", + "nonconvertible", + "true", + "false", + ], +) +def test_missing_profile_does_not_hide_invalid_radius(radius: object) -> None: + with pytest.raises(ValueError, match="radius"): + ar.get_proatomic_density("Og", radius, radius_unit="bohr") + + +@pytest.mark.parametrize("radius", [True, False]) +@pytest.mark.parametrize("api", ["evaluate", "call", "accessor"]) +def test_boolean_radii_are_rejected_by_every_density_api( + radius: bool, + api: str, +) -> None: + profile = ar.get_proatomic_density_profile("O") + assert profile is not None + + with pytest.raises(ValueError, match="radius"): + if api == "evaluate": + profile.evaluate(radius, radius_unit="bohr") + elif api == "call": + profile(radius, radius_unit="bohr") + else: + ar.get_proatomic_density("O", radius, radius_unit="bohr") + + def test_unknown_units_raise() -> None: with pytest.raises(ValueError, match="unknown radius unit"): ar.get_proatomic_density("O", 1.0, radius_unit="meter") @@ -354,6 +402,30 @@ def test_missing_density_dataset_raises_dataset_error() -> None: ar.get_proatomic_density_profile("O", set_id="missing") +def test_profile_validates_dataset_before_missing_element() -> None: + with pytest.raises(DatasetError, match="unknown dataset id"): + ar.get_proatomic_density_profile("Xx", set_id="missing") + + +def test_density_validates_dataset_before_missing_element() -> None: + with pytest.raises(DatasetError, match="unknown dataset id"): + ar.get_proatomic_density("Xx", 1.0, set_id="missing") + + +def test_missing_profile_does_not_hide_invalid_radius_unit() -> None: + with pytest.raises(ValueError, match="unknown radius unit"): + ar.get_proatomic_density("Og", 1.0, radius_unit="meter") + + +def test_missing_profile_does_not_hide_invalid_density_unit() -> None: + with pytest.raises(ValueError, match="unknown density unit"): + ar.get_proatomic_density( + "Og", + 1.0, + density_unit="electron/meter^3", + ) + + def test_scalar_accessors_remain_narrow_after_density_use() -> None: assert isinstance( ar.get_radii_set("covalent", "cordero2008"), diff --git a/tests/proatoms/test_ias.py b/tests/proatoms/test_ias.py index d1dacb9..d59f4d3 100644 --- a/tests/proatoms/test_ias.py +++ b/tests/proatoms/test_ias.py @@ -1430,3 +1430,20 @@ def test_invalid_mode_and_units_raise_value_error() -> None: def test_missing_dataset_raises_dataset_error() -> None: with pytest.raises(DatasetError, match="unknown dataset id"): ar.estimate_proatomic_boundary("H", "O", 2.0, set_id="missing") + + +@pytest.mark.parametrize( + ("atom_a", "atom_b"), + [("Xx", "O"), ("Xx", "invalid")], +) +def test_pairwise_validates_dataset_before_missing_atoms( + atom_a: str, + atom_b: str, +) -> None: + with pytest.raises(DatasetError, match="unknown dataset id"): + ar.estimate_proatomic_boundary( + atom_a, + atom_b, + 1.0, + set_id="missing", + )