Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4283200
Replace uid_map with direct parameter references in aliases
AndrewSazonov Apr 2, 2026
eb1202f
Auto-enable constraints on create, add enable/disable API
AndrewSazonov Apr 2, 2026
0a0385c
Implement Project.load() from CIF directory
AndrewSazonov Apr 3, 2026
47e268e
Encode free flags via CIF uncertainty brackets
AndrewSazonov Apr 3, 2026
4db00cf
Update notebooks
AndrewSazonov Apr 3, 2026
0cbfc94
Update serialization process to apply constraints before saving project
AndrewSazonov Apr 3, 2026
87d3669
Move CIF loop truncation from persistence to display methods
AndrewSazonov Apr 3, 2026
9a11882
Add CIF round-trip integration tests for experiments and structures
AndrewSazonov Apr 3, 2026
ca8d736
Add new integration test
AndrewSazonov Apr 3, 2026
21941c9
Move analysis.cif into analysis/ directory
AndrewSazonov Apr 3, 2026
5dffac3
Add destination parameter to extract_data_paths_from_zip
AndrewSazonov Apr 3, 2026
16af6de
Add missing Returns sections to docstrings
AndrewSazonov Apr 3, 2026
4ddd38e
Add sequential fitting infrastructure with CSV output
AndrewSazonov Apr 3, 2026
de0d218
Unify plot_param_series to read from CSV with snapshot fallback
AndrewSazonov Apr 3, 2026
0a6bd3b
Add multiprocessing support to fit_sequential
AndrewSazonov Apr 3, 2026
69accc1
Write results.csv from existing single-fit mode
AndrewSazonov Apr 3, 2026
5a4e760
Add apply_params_from_csv for dataset replay
AndrewSazonov Apr 3, 2026
2d49d16
Prevent spawn re-import of __main__ in parallel fit_sequential
AndrewSazonov Apr 3, 2026
8b209df
Support negative indexing and force recalc in apply_params_from_csv
AndrewSazonov Apr 3, 2026
e88d18d
Add extract_project_from_zip helper function
AndrewSazonov Apr 3, 2026
dcaef62
Refactor extract_project_from_zip call to use zip_path variable
AndrewSazonov Apr 3, 2026
ed22516
Refactor notebook cell IDs and update project loading process
AndrewSazonov Apr 3, 2026
8a30ae1
Remove CSV writing from fit() to fix sequential crash recovery
AndrewSazonov Apr 3, 2026
592a2c5
Fix extract_project_from_zip to find project.cif from zip contents
AndrewSazonov Apr 4, 2026
9df29ab
Add unit tests
AndrewSazonov Apr 4, 2026
6cf1bf2
Add functional tests
AndrewSazonov Apr 4, 2026
28ccbe7
Add unit tests
AndrewSazonov Apr 4, 2026
a195cd8
More tests
AndrewSazonov Apr 4, 2026
a22b07c
Add more tests
AndrewSazonov Apr 4, 2026
29d66a7
Replace deprecated loadData with load_data from diffpy
AndrewSazonov Apr 4, 2026
4d78b49
Suppress specific warnings in pyproject.toml for better test output
AndrewSazonov Apr 4, 2026
e695f4d
Enable RUF rules and fix all 297 violations
AndrewSazonov Apr 4, 2026
d9b17b2
Enable PRL rules (phase 1)
AndrewSazonov Apr 4, 2026
9bc0461
Use ruff default PLR thresholds with max-args=6
AndrewSazonov Apr 4, 2026
fb518e4
Refactor FitResults to fix PLR complexity violations in reporting.py
AndrewSazonov Apr 4, 2026
1ac7d60
Remove verbosity param from Experiments.add_from_data_path
AndrewSazonov Apr 4, 2026
b8babaa
Move data-loading print from ExperimentFactory to collection
AndrewSazonov Apr 4, 2026
bd3b2eb
Fix plotting tests and add numpy conversion in _prepare_powder_context
AndrewSazonov Apr 4, 2026
cc35eb8
Update tests, tutorials, and docs to use analysis.display API
AndrewSazonov Apr 4, 2026
8b2d9af
Reduce McCabe complexity for C90 rule compliance
AndrewSazonov Apr 4, 2026
24f2397
Fix all FURB rule violations
AndrewSazonov Apr 4, 2026
a4e4756
Fix all PIE rule violations
AndrewSazonov Apr 4, 2026
e33bd4b
Fix all BLE001 blind-except violations
AndrewSazonov Apr 4, 2026
eeef5bf
Add test-all task
AndrewSazonov Apr 4, 2026
77e6722
Fix tutorial
AndrewSazonov Apr 4, 2026
c92ab7e
Update diagrams and clean up
AndrewSazonov Apr 4, 2026
c287405
Merge remote-tracking branch 'origin/develop' into more-rules
AndrewSazonov Apr 4, 2026
75d48ec
Harden pickle and urlopen against security audit findings
AndrewSazonov Apr 4, 2026
eb193e4
Increase integration test coverage
AndrewSazonov Apr 4, 2026
b20b904
Add unit test coverage for utils.py helper functions
AndrewSazonov Apr 4, 2026
80c69f4
Update coverage.yml to refine workflow triggers and concurrency settings
AndrewSazonov Apr 5, 2026
3e15b77
Add unit tests to improve patch coverage for changed files
AndrewSazonov Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@
with both getter and setter) or **read-only** (property with getter
only). If internal code needs to mutate a read-only property, add a
private `_set_<name>` method instead of exposing a public setter.
- Lint complexity thresholds (`max-args`, `max-branches`,
`max-statements`, `max-locals`, `max-nested-blocks`, etc. in
`pyproject.toml`) are intentional code-quality guardrails. They are
not arbitrary numbers — the project uses ruff's defaults (with
`max-args` and `max-positional-args` set to 6 instead of 5 to account
for ruff counting `self`/`cls`). When code violates a threshold, it is
a signal that the function or class needs refactoring — not that the
threshold needs raising. Do not raise thresholds, add `# noqa`
comments, or use any other mechanism to silence complexity violations.
Instead, refactor the code (extract helpers, introduce parameter
objects, flatten nesting, etc.). For complex refactors that touch many
lines or change public API, propose a refactoring plan and wait for
approval before proceeding.

## Architecture

Expand Down Expand Up @@ -108,6 +121,26 @@
`*.py` script, then run `pixi run notebook-prepare` to regenerate the
notebook.

## Testing

- Every new module, class, or bug fix must ship with tests. See
`docs/architecture/architecture.md` §10 for the full test strategy.
- **Unit tests mirror the source tree:**
`src/easydiffraction/<pkg>/<mod>.py` →
`tests/unit/easydiffraction/<pkg>/test_<mod>.py`. Run
`pixi run test-structure-check` to verify.
- Category packages with only `default.py`/`factory.py` may use a single
parent-level `test_<package>.py` instead of per-file tests.
- Supplementary test files use the pattern `test_<mod>_coverage.py`.
- Tests that expect `log.error()` to raise must `monkeypatch` Logger to
RAISE mode (another test may have leaked WARN mode).
- `@typechecked` setters raise `typeguard.TypeCheckError`, not
`TypeError`.
- No test-ordering dependence, no network, no sleeping, no real
calculation engines in unit tests.
- After adding or modifying tests, run `pixi run unit-tests` and confirm
all tests pass.

## Changes

- Before implementing any structural or design change (new categories,
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
name: Coverage checks

on:
# Trigger the workflow on push
# Trigger the workflow on push to develop
push:
branches:
- develop
# Do not run on version tags (those are handled by other workflows)
tags-ignore: ['v*']
# Trigger the workflow on pull request
Expand All @@ -15,11 +17,11 @@ permissions:
actions: write
contents: read

# Allow only one concurrent workflow, skipping runs queued between the run
# in-progress and latest queued. And cancel in-progress runs.
# Allow only one concurrent workflow per PR or branch ref.
# Cancel in-progress runs only for pull requests, but let branch push runs finish.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

# Set the environment variables to be used in all jobs defined in this workflow
env:
Expand Down
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,23 @@ repos:
pass_filenames: false
stages: [manual]

- id: pixi-test-structure-check
name: pixi run test-structure-check
entry: pixi run test-structure-check
language: system
pass_filenames: false
stages: [manual]

- id: pixi-unit-tests
name: pixi run unit-tests
entry: pixi run unit-tests
language: system
pass_filenames: false
stages: [manual]

- id: pixi-functional-tests
name: pixi run functional-tests
entry: pixi run functional-tests
language: system
pass_filenames: false
stages: [manual]
131 changes: 126 additions & 5 deletions docs/architecture/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,7 @@ project.experiments['hrpt'].calculator_type = 'cryspy'
project.analysis.current_minimizer = 'lmfit'

# Plot before fitting
project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)
project.plotter.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)

# Select free parameters
project.structures['lbco'].cell.length_a.free = True
Expand All @@ -866,14 +866,14 @@ project.experiments['hrpt'].instrument.calib_twotheta_offset.free = True
project.experiments['hrpt'].background['10'].y.free = True

# Inspect free parameters
project.analysis.show_free_params()
project.analysis.display.free_params()

# Fit and show results
project.analysis.fit()
project.analysis.show_fit_results()
project.analysis.display.fit_results()

# Plot after fitting
project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)
project.plotter.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)

# Save
project.save()
Expand Down Expand Up @@ -1170,9 +1170,130 @@ def length_a(self) -> Parameter:
- The CI tool `pixi run param-consistency-check` validates compliance;
`pixi run param-consistency-fix` auto-fixes violations.

### 9.9 Lint Complexity Thresholds

The Pylint-style complexity limits in `pyproject.toml` are **intentional
code-quality guardrails**, not arbitrary numbers. A violation is a
signal that the function or class needs refactoring — not that the
threshold needs raising.

The project uses **ruff's defaults** for all PLR thresholds, with one
exception: `max-args` and `max-positional-args` are set to **6** instead
of the ruff default of 5, because ruff counts `self`/`cls` while
traditional pylint does not. Setting 6 in ruff matches pylint's standard
limit of 5 real parameters per function.

| Threshold | Value | Rule |
| --------------------- | ----- | ------- |
| `max-args` | 6 | PLR0913 |
| `max-positional-args` | 6 | PLR0917 |
| `max-branches` | 12 | PLR0912 |
| `max-statements` | 50 | PLR0915 |
| `max-locals` | 15 | PLR0914 |
| `max-nested-blocks` | 5 | PLR1702 |
| `max-returns` | 6 | PLR0911 |
| `max-public-methods` | 20 | PLR0904 |

**Rules:**

- **Do not raise thresholds.** The current values represent the
project's design intent for maximum acceptable complexity.
- **Do not add `# noqa` comments** (or any other mechanism) to silence
complexity rules such as `PLR0912`, `PLR0913`, `PLR0914`, `PLR0915`,
`PLR0917`, `PLR1702`.
- **Refactor the code instead:** extract helper functions, introduce
parameter objects, flatten nesting, use early returns, etc.
- **For complex refactors** that touch many lines or change public API,
propose a refactoring plan and wait for approval before proceeding.

---

## 10. Test Strategy

Every new feature, category, factory, or bug fix must ship with tests.
The project enforces a multi-layered testing approach that catches
regressions at different levels of abstraction.

### 10.1 Test Layers

| Layer | Location | Runner command | Scope |
| --------------------- | ----------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| **Unit** | `tests/unit/` | `pixi run unit-tests` | Single class or function in isolation. Fast, no I/O, no external engines. |
| **Functional** | `tests/functional/` | `pixi run functional-tests` | Multi-component workflows (e.g. create experiment → load data → fit). No external data files beyond tiny test stubs. |
| **Integration** | `tests/integration/` | `pixi run integration-tests` | End-to-end pipelines using real calculation engines (cryspy, crysfml, pdffit2) and real data files from `data/`. |
| **Script (tutorial)** | `tools/test_scripts.py` | `pixi run script-tests` | Runs each tutorial `*.py` script under `docs/docs/tutorials/` as a subprocess and checks for a zero exit code. |
| **Notebook** | `docs/docs/tutorials/` | `pixi run notebook-tests` | Executes every Jupyter notebook end-to-end via `nbmake`. |

### 10.2 Directory Structure Convention

The unit-test tree **mirrors** the source tree:

```
src/easydiffraction/<pkg>/<module>.py
→ tests/unit/easydiffraction/<pkg>/test_<module>.py
```

Two additional patterns are recognised:

1. **Supplementary coverage files** — `test_<module>_coverage.py`,
`test_<module>_more.py`, etc. sit beside the main test file and add
extra scenarios.
2. **Parent-level roll-up** — for category packages that contain only
`default.py` and `factory.py`, a single `test_<package_name>.py` one
directory up covers the whole package (e.g.
`categories/test_experiment_type.py` covers
`categories/experiment_type/default.py` and
`categories/experiment_type/factory.py`).

The CI tool `pixi run test-structure-check` validates that every source
module has a corresponding test file and reports any gaps. Explicit name
aliases (e.g. `variable.py` tested by `test_parameters.py`) are declared
in `KNOWN_ALIASES` inside the tool script.

### 10.3 What to Test per Source Module Type

| Source module type | Required tests |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Core base class** (`core/`) | Instantiation, public properties, validation edge cases, identity wiring. |
| **Factory** (`factory.py`) | Registration check, `supported_tags()`, `default_tag()`, `create()` for each tag, `show_supported()` output, invalid-tag handling. |
| **Category** (`default.py`) | Instantiation, all public properties (read + write where applicable), CIF round-trip (`as_cif` → `from_cif`), parameter enumeration. |
| **Enum** (`enums.py`) | Membership of all members, `default()` method, `description()` for every member, `StrEnum` string equality. |
| **Datablock item** (`base.py`) | Construction, switchable-category full API (`<cat>`, `<cat>_type` get/set, `show_supported_<cat>_types`, `show_current_<cat>_type`), `show`/`show_as_cif`. |
| **Collection** (`collection.py`) | `create`, `add`, `remove`, `names`, `show_names`, `show_params`, iteration, duplicate-name handling. |
| **Calculator / Minimizer** | `can_handle()` with compatible and incompatible experiment types, `_compute()` stub or mock. |
| **Display / IO** | Input → output for representative cases; file-not-found and malformed-input error paths. |

### 10.4 Test Conventions

- **No test-ordering dependence.** Each test must be self-contained. Use
`monkeypatch` to set `Logger._reaction` when the test expects a raised
exception (another test may have leaked WARN mode via the global
`Logger` singleton).
- **Error paths are tested explicitly.** Use `pytest.raises()` (with
`monkeypatch` for Logger RAISE mode) for `log.error()` calls that
specify `exc_type`.
- **`@typechecked` setters raise `typeguard.TypeCheckError`**, not
`TypeError`. Tests must catch the correct exception.
- **Use `capsys` / `capfd`** for asserting console output from `show_*`
methods.
- **Prefer `tmp_path`** (pytest fixture) for file-system tests.
- **No sleeping, no network calls, no real calculation engines** in unit
tests.
- Test files carry the SPDX license header and a module-level docstring.
They are exempt from most lint rules (ANN, D, DOC, INP001, S101, etc.)
per `pyproject.toml`.

### 10.5 Coverage Threshold

The minimum line-coverage threshold is **70 %** (`fail_under = 70` in
`pyproject.toml`). The project aspires to test every code path; the
threshold is a safety net, not a target.

Run `pixi run unit-tests-coverage` for a per-module report.

---

## 10. Issues
## 11. Issues

- **Open:** [`issues_open.md`](issues_open.md) — prioritised backlog.
- **Closed:** [`issues_closed.md`](issues_closed.md) — resolved items
Expand Down
Loading
Loading