Prioritised list of issues, improvements, and design questions to
address. Items are ordered by a combination of user impact, blocking
potential, and implementation readiness. When an item is fully
implemented, remove it from this file and update
adrs/index.md or the relevant ADR if needed.
Legend: π΄ High Β· π‘ Medium Β· π’ Low
Type: Physics / Engine feature
The calculators (cryspy, crysfml) apply no sample-absorption
correction. For a cylindrical sample in DebyeβScherrer geometry this is
an angle-dependent intensity factor that boosts high-angle peaks. The
LaBβ verification reference (pd-neut-cwl_tch-fcj_lab6) was refined in
FullProf with ΞΌR = 0.7; the unmodelled correction is the entire
intensity residual on the companion pd-neut-cwl_tch-fcj_abs_lab6 page
(β5% profile difference), while the ΞΌR = 0 page passes to corr 0.9999.
Correction (Hewat, DebyeβScherrer), validated to 4 decimals against FullProf output:
A(ΞΈ) = exp( -(1.7133 β 0.0368Β·sinΒ²ΞΈ)Β·ΞΌR + (0.0927 + 0.375Β·sinΒ²ΞΈ)Β·ΞΌRΒ² )
A LobanovβAlte-da-Veiga form covers ΞΌR > 3.
Implementation sketch:
- Add a
ΞΌRinstrument parameter for CWL powder (DebyeβScherrer). crysfml: CrysFML08 already implements this β reachable viaLorentz_abs_CW(..., ilor='DBS', cabs='HEWAT', tmv=ΞΌR)through pycrysfml.cryspy: multiply each reflection's intensity byA(ΞΈ_hkl), analogous to the existing Lorentz factor (one extra term).
Note: absorption is nearly degenerate with Biso + scale (its angle
term is linear in sinΒ²ΞΈ, like the DebyeβWaller), so refining Biso can
partly absorb it β but that biases Biso, so an explicit correction is
preferable.
References:
- A. W. Hewat, Acta Cryst. A35 (1979) 248 β cylindrical absorption.
- N. N. Lobanov & L. Alte da Veiga, 6th EPDIC, Abstract P12-16 (1998).
- CrysFML08:
Src/CFML_Powder/Pow_Lorentz_Absorption.f90,Lorentz_abs_CW. - FullProf
ΞΌR:.pcrLambda line field 7;iabscor = 2selects HEWAT.
Depends on: adding a ΞΌR instrument parameter.
Type: Fragility
joint_fit is created once when fit.mode becomes 'joint'. If
experiments are added, removed, or renamed afterwards, the weight
collection is stale. Joint fitting can fail with missing keys or run
with incorrect weights.
Fix: rebuild or validate joint_fit at the start of every joint
fit. At minimum, fit() should assert that the weight keys exactly
match project.experiments.names.
Depends on: nothing.
Type: API safety
CategoryCollection.create(**kwargs) accepts arbitrary keyword
arguments and applies them via setattr. Typos are silently dropped
(GuardedBase logs a warning but does not raise), so items are created
with incorrect defaults.
Fix: concrete collection subclasses (e.g. AtomSites, Background)
should override create() with explicit parameters for IDE autocomplete
and typo detection. The base create(**kwargs) remains as an internal
implementation detail.
Depends on: nothing.
Type: Design improvement
The four current experiment axes will be extended with at least two more:
| New axis | Options | Enum (proposed) |
|---|---|---|
| Data dimensionality | 1D, 2D | DataDimensionalityEnum |
| Beam polarisation | unpolarised, polarised | PolarisationEnum |
These should follow the same str, Enum pattern and integrate into
Compatibility (new FrozenSet fields), _default_rules, and
ExperimentType (new StringDescriptors with MembershipValidators).
Migration path: existing Compatibility objects that don't specify
the new fields use frozenset() (empty = "any"), so all existing
classes remain compatible without changes.
Depends on: nothing.
Type: Maintainability
Project._update_categories(expt_name) hard-codes the update order
(structures β analysis β one experiment). The _update_priority system
exists on categories but is not used across datablocks. The expt_name
parameter means only one experiment is updated per call, inconsistent
with joint-fit workflows.
Fix: consider a project-level _update_priority on datablocks, or
at minimum document the required update order. For joint fitting, all
experiments should be updateable in a single call.
Depends on: benefits from the CategoryOwner migration.
Type: Maintainability
_update() is an optional override with a no-op default. A clearer
contract would help contributors:
- Active categories (those that compute something, e.g.
Background,Data) should have an explicit_update()implementation. - Passive categories (those that only store parameters, e.g.
Cell,SpaceGroup) keep the no-op default.
The distinction is already implicit in the code; making it explicit in documentation (and possibly via a naming convention or flag) would reduce confusion for new contributors.
Depends on: nothing.
Type: Performance
Symmetry constraint application (cell metric, atomic coordinates, ADPs)
goes through the public value setter for each parameter, setting the
dirty flag repeatedly during what is logically a single batch operation.
No correctness issue β the dirty-flag guard handles this correctly. The redundant sets are a minor inefficiency that only matters if profiling shows it is a bottleneck.
Fix: introduce a private _set_value_no_notify() method on
GenericDescriptorBase for internal batch operations, or a context
manager / flag on the owning datablock to suppress notifications during
a batch.
Depends on: nothing, but low priority.
Type: Performance
The current dirty-flag approach (_need_categories_update on
DatablockItem) triggers a full update of all categories when any
parameter changes. This is simple and correct. If performance becomes a
concern with many categories, a more granular approach could track which
specific categories are dirty. Only implement when profiling proves it
is needed.
Depends on: nothing, but low priority.
Type: API design
Analysis currently allows direct access to inactive mode-specific
categories such as joint_fit or sequential_fit. The values remain
editable, but inactive sections are hidden from help and dropped during
serialization.
Fix: confirm whether this lenient access is the long-term contract, or replace it with a dedicated mode error to prevent silent state loss on save.
Depends on: nothing.
Type: Fragility
joint_fit is validated and auto-populated at fit() time, but it does
not react when experiments are later renamed or removed.
Fix: decide whether joint_fit should stay passive until execution,
or listen for experiment lifecycle changes and prune or warn earlier.
Depends on: nothing.
Type: Data model
Joint-fit rows currently allow any non-negative weight, but the public
contract is still unclear about whether 0 means exclusion and whether
an upper bound should exist.
Fix: define the supported range and validator semantics for
joint_fit.weight.
Depends on: nothing.
Type: Data model
Sequential extract rules currently target one numeric descriptor under
experiment.diffrn. Open questions remain around nested targets,
duplicate rules writing the same target, and how additional supported
prefixes should be introduced when new environment categories appear.
Fix: pin the allowed target grammar and duplicate-target behaviour in an ADR and validation rules.
Depends on: nothing.
Type: Runtime behaviour
Today a failed required extract rule marks that file as failed and the run continues. The overall aggregation policy is still undefined.
Fix: decide whether one failed file should abort the whole run, remain an isolated row-level failure, or count toward a configurable failure threshold.
Depends on: nothing.
Type: Performance
Sequential metadata extraction currently re-reads input files when the run is repeated or resumed.
Fix: decide whether extracted diffrn.* values should be cached in
analysis/results.csv only, or also in a dedicated reusable cache.
Depends on: nothing.
Type: Recovery design
If a sequential fit fails partway through, the recovery and persistence
contract for analysis/results.csv is not fully specified.
Fix: define whether partial CSV output is authoritative for resume, left untouched for manual recovery, or replaced on the next run.
Depends on: nothing.
Type: CLI design
The CLI can override mode and worker settings, but persisted
sequential_fit_extract rules are not yet overridable from the command
line.
Fix: decide whether extraction rules stay project-file-only or gain an explicit CLI override syntax.
Depends on: nothing.
Type: Discoverability
help() now hides inactive analysis categories by fitting mode, while
dir() and tab completion still expose the full class surface.
Fix: decide whether dir() should mirror the help filter or remain
an always-complete developer surface.
Depends on: nothing.
Type: Scope planning
Single mode currently has no dedicated persisted category. Future single-mode settings could require one, but the threshold is not yet defined.
Fix: decide what concrete single-mode behaviour would justify a
single_fit category instead of keeping the mode configuration on the
owner only.
Depends on: nothing.
Type: Correctness
Joint-fit weights currently allow invalid numeric values such as
negatives or an all-zero set. The residual code then normalises by the
total weight and applies sqrt(weight), which can produce
division-by-zero or nan residuals.
Fix: require weights to be strictly positive, or at minimum validate that all weights are non-negative and their total is greater than zero before normalisation. This should fail with a clear user-facing error instead of letting invalid floating-point values propagate into the minimiser.
Depends on: related to issue 3, but independent.
Type: Performance
The dev environment previously installed pytest-benchmark, but the
repository does not currently define any benchmark tests. At the same
time, the integration, script, and notebook pytest tasks all run with
pytest-xdist, so benchmark plugins only add warning noise and do not
provide reliable performance regression coverage.
Performance regressions are still worth tracking, especially for single diffraction-pattern calculation where backend or profile changes can quietly slow interactive workflows.
Fix: add a dedicated serial benchmark task outside the normal
parallel pytest suite. Benchmark representative single-pattern
calculations on fixed datasets and calculators, run without -n auto,
and define regression thresholds only after measurements are stable on a
controlled runner.
Depends on: nothing.
Type: Naming
The TotalPdDataPoint class reuses Bragg powder CIF tag names (e.g.
_pd_data.point_id, _pd_proc.r, _pd_meas.intensity_total) as
placeholders. These should be replaced with proper total-scattering /
PDF-specific CIF names.
TODOs:
- total_pd.py
β
_pd_data.point_id - total_pd.py
β
_pd_proc.r - total_pd.py
β
_pd_meas.intensity_total - total_pd.py
β
_pd_meas.intensity_total_su - total_pd.py
β
_pd_calc.intensity_total - total_pd.py
β
_pd_data.refinement_status
Depends on: nothing.
Type: Maintainability
PdffitCalculator.calculate_pattern contains inline CIF v2βv1
conversion (dot-to-underscore rewriting). This should live in a shared
io module.
TODOs:
Depends on: nothing.
Type: Diagnostics
Several calculator modules have commented-out print statements for import success/failure. These should be wired into the logging system under a debug level.
TODOs:
Depends on: nothing.
Type: UX
CrysPy emits warnings to stderr during pattern calculation. The code has TODO markers to redirect these.
TODOs:
Depends on: nothing.
Type: Correctness / Naming
The CrysPy calculator uses TOF background CIF tags
(_tof_backgroundpoint_time, _tof_backgroundpoint_intensity) and
hardcoded 0.0 intensity values marked with TODO: !!!!????. The
mapping and the hardcoded defaults need verification.
TODOs:
Depends on: nothing.
Type: Correctness
For time-of-flight powder data using the JorgensenβVon Dreele peak
profile, the cryspy backend diverges from FullProf and crysfml
whenever the Lorentzian term (broad_lorentz_gamma_*) is non-zero. On
the Si Verification reference case the profile difference reaches β22%
with an integrated-intensity ratio β0.72β0.76, while crysfml matches
FullProf to <1%. When the Lorentzian term is zero (NaCaAlF) cryspy
agrees to <1%, which localises the problem to the cryspy translation of
the pseudo-Voigt (Gaussian β Lorentzian) mixing for TOF.
Fix: verify how broad_lorentz_gamma_* is passed to cryspy for the
jorgensen-von-dreele profile and reconcile the convention with
crysfml/FullProf.
Visible on: the Si TOF Verification page (pd-neut-tof_jvd_si),
whose closeness table flags the cryspy rows in red β reported via a
non-raising agreement check, not enforced, so CI stays green.
Re-introduce a strict check, or skip the page via
docs/docs/verification/ci_skip.txt, once work on the cryspy backend
begins.
Depends on: nothing.
Type: Feature / Experiment model
FullProf models systematic peak-position aberrations with SyCos
(sample displacement) and SySin (transparency), shifting peaks as a
function of angle on top of the Zero offset. EasyDiffraction has no
category for these, so it cannot reproduce datasets that use them. The
cryspy side is implemented in
cryspy PR #46 (see
issue #38); the
EasyDiffraction side β an instrument-category parameter pair plus the
calculator wiring β is still to do.
A prepared verification page,
docs/docs/verification/pd-neut-cwl_tch-fcj_lab6.py, uses the issue #38
LaB6 dataset and is skipped via ci_skip.txt. Finishing it also needs a
custom ΒΉΒΉB scattering length, the ThompsonβCoxβHastings profile, and a
FullProf-style polynomial background, which that dataset relies on.
Fix: add SyCos/SySin to the CWL instrument category, pass them
to the calculators, then un-skip the LaB6 page.
Depends on: nothing.
Type: Correctness
_cif_instrument_section uses an empty instrument_mapping dict for
single crystal and a TODO: Check this mapping! marker.
TODOs:
Depends on: nothing.
Type: Correctness
CrysFML calculator adjusts pattern length post-calculation with a TODO asking to investigate the origin of the off-by-one discrepancy. The same epsilon workaround appears in the dict builder.
TODOs:
Depends on: nothing.
Type: Design
Default instrument/peak values for the CrysFML dict are filled in at calculation time with inline fallbacks rather than being set at experiment creation.
TODOs:
Depends on: nothing.
Type: Maintainability
Multiple _update helpers in Bragg PD, Bragg SC, and Total PD data
classes have TODO: split into multiple methods or
TODO: refactor _get_valid_linked_phases markers. The update logic
should be decomposed and the _get_valid_linked_phases responsibility
should be narrowed. The Total PD and Bragg PD classes should also adapt
the pattern from bragg_sc.py.
TODOs:
Depends on: nothing.
Type: Cleanup
Many array constructions pass dtype=float or dtype=object with a
TODO: needed? DataTypes.NUMERIC? comment. Decide whether explicit
dtype is needed and align with DataTypes.
TODOs:
- bragg_pd.py
- bragg_pd.py
- bragg_pd.py
- bragg_pd.py
- bragg_pd.py
- bragg_pd.py
- bragg_pd.py
- bragg_pd.py
- bragg_pd.py
- bragg_pd.py
- total_pd.py
- total_pd.py
Depends on: nothing.
Type: Correctness
A temporary workaround exists for zero uncertainties in measured data.
TODOs:
Depends on: nothing.
Type: Cleanup
PdCwlDataCollection has a commented-out _description and a
TODO: ??? marker.
TODOs:
Depends on: nothing.
Type: Consistency
Multiple category item classes use the same regex r'^[A-Za-z0-9_]*$'
for their id/label validators with an identical TODO about CIF label vs.
internal label conversion.
TODOs:
Depends on: nothing.
Type: Design
bragg_pd.py uses default='incl' as a raw string with a TODO to make
it an Enum. The _pd_data.refinement_status CIF name should also be
renamed to calc_status.
TODOs:
Depends on: nothing.
Type: Naming
Mixin classes PdDataPointBaseMixin and PdCwlDataPointMixin have TODO
markers suggesting a rename to BasePdDataPointMixin and
CwlPdDataPointMixin for consistency.
TODOs:
Depends on: nothing.
Type: Maintainability
Both Experiments and Structures collections duplicate methods
(from_cif_str, from_cif_file, show, show_as_cif, etc.) that
could live in the base DatablockCollection.
TODOs:
- collection.py
β
Make abstract in DatablockCollection? - collection.py
β
Move to DatablockCollection? - collection.py
- collection.py
- collection.py
- collection.py
- collection.py
- collection.py
- collection.py
- collection.py
Depends on: nothing.
Type: Design
DatablockItem._update_categories has a TODO to make it abstract and
implement it in subclasses for structures (symmetry + constraints) and
experiments (calculation updates). Currently it is a concrete no-op.
TODOs:
Depends on: related to issue 11.
Type: Design
Three related TODOs in enums.py ask whether PeakProfileTypeEnum
values can be auto-extracted from the actual peak profile classes in
peak/cwl.py, tof.py, total.py instead of being hardcoded, and
whether the same pattern can be reused for other enums.
TODOs:
Depends on: related to issue 9.
Type: Naming
BeamModeEnum.CONSTANT_WAVELENGTH and TIME_OF_FLIGHT have a TODO to
be renamed to CWL and TOF.
TODOs:
Depends on: nothing.
Type: Design
BackgroundTypeEnum and other enums repeat the same default() /
description() method pattern. A shared EnumBase would reduce
boilerplate.
TODOs:
Depends on: related to issue 9.
Type: Naming
ExperimentBase.type returns experimental metadata but the name shadows
the built-in type. A TODO suggests finding a better name.
TODOs:
Depends on: nothing.
Type: Bug
Both StructureFactory and ExperimentFactory have from_cif_str
methods where @typechecked is commented out because it "fails to find
gemmi". They also share TODOs about adding minimal default configuration
for missing parameters and reading content from files.
TODOs:
- factory.py
- factory.py
- factory.py
- factory.py
β
Add to core/factory.py? - factory.py
- factory.py
- factory.py
Depends on: nothing.
Type: Design
CategoryItem and CategoryCollection both define
_update_priority = 10 with a TODO to set different defaults and use
them during CIF serialisation. The duplicated _update no-op methods
are also marked.
TODOs:
Depends on: related to issues 10, 11.
Type: API cleanup
Powder Bragg plots now show the residual row by default when
show_residual=None, but the public show_residual argument still
exists and some call sites still pass show_residual=True explicitly.
The API should be clarified: either keep the argument as a compatibility
option, remove it, or standardize a single meaning across powder and
single-crystal plots.
TODOs:
Depends on: nothing.
Type: Naming / CIF UX
The implemented powder reflection category uses phase_id throughout
(experiment.refln, PowderReflnRecord, Bragg tick labels) and assigns
global sequential row ids. This matches the current implementation, but
the archived planning notes left two follow-up questions open:
- whether
structure_idwould be clearer thanphase_idin the public API / CIF output, and - whether phase-prefixed row ids would make CIF inspection and
debugging easier than simple
1,2,3, ...
TODOs:
Depends on: nothing.
Type: Feature
ConstraintsHandler has a TODO to implement changing the
.user_constrained attribute back to False when constraints are
removed.
TODOs:
Depends on: nothing.
Type: Cleanup
GenericDescriptorBase._set_value marks the parent datablock dirty with
a TODO questioning whether this path is exercised.
TODOs:
Depends on: nothing.
Type: Docs
A TODO in validation.py notes that MkDocs doesn't unpack types
properly.
TODOs:
Depends on: nothing.
Type: UX
The summary module has TODOs about fixing description wrapping and inconsistent header capitalisation.
TODOs:
Depends on: nothing.
Type: Cleanup
Analysis._params_to_dataframe has TODOs to merge record construction
for StringDescriptor/NumericDescriptor/Parameter and to use repr
formatting for StringDescriptor values.
TODOs:
Depends on: nothing.
Type: Design
Aliases and Constraints categories use default='_' with a
TODO, Maybe None? marker.
TODOs:
Depends on: nothing.
Type: Naming
JointFitItem uses name='experiment_id', but two description fields
are still incomplete.
TODOs:
Depends on: nothing.
Type: Diagnostics
crystallography.py logs errors with a TODO asking whether these should
raise ValueError or provide better diagnostics.
TODOs:
Depends on: nothing.
Type: Bug workaround
TofInstrument.calib_d_to_tof_quad defaults to -0.00001 because
CrysPy does not accept 0.
TODOs:
Depends on: upstream CrysPy fix.
Type: Maintainability
SpaceGroup.name_h_m lists multiple CIF tag variants (with . and
_). A TODO asks to keep only the dotted version and automate variant
generation.
TODOs:
Depends on: nothing.
Type: Cleanup
Cell._update deletes called_by_minimizer with a TODO: ???.
TODOs:
Depends on: related to issue 11.
Type: Naming
LineSegmentBackgroundPoint.y has TODOs to rename to intensity.
TODOs:
Depends on: nothing.
Type: Maintainability
ExcludedRegions.show() and BackgroundBase.show() duplicate table-
rendering logic. The TODO suggests moving it to the base class.
TODOs:
Depends on: nothing.
Type: Completeness
ExcludedRegion has a TODO to add point_id similar to background
categories.
TODOs:
Depends on: nothing.
Type: Docs / UX
display/__init__.py has disabled JupyterScrollManager because it
breaks MkDocs builds.
TODOs:
Depends on: nothing.
Type: UX
ascii.py hardcodes width = 60 with a TODO to make it configurable.
TODOs:
Depends on: nothing.
Type: Maintainability
serialize.py has several TODOs: verify methods after the
format_param_value section, extract a helper for quoted-string
stripping, find a better way to set _item_type on
CategoryCollection, rename it to _item_cls, and remove duplicated
param_from_cif logic.
TODOs:
Depends on: nothing.
Type: Maintainability
ProjectInfo methods as_cif and show_as_cif have TODOs suggesting
they belong in the serialisation module.
TODOs:
Depends on: nothing.
Type: Robustness
io/cif/parse.py has a TODO about adding a validator or normalisation
step.
TODOs:
Depends on: nothing.
Type: Cleanup
io/ascii.py has a TODO to unify directory creation with other uses.
TODOs:
Depends on: nothing.
Type: Design
Logger._reaction defaults to Reaction.RAISE with a
TODO: not default? marker.
TODOs:
Depends on: nothing.
Type: Cleanup
utils.py has a temporary render_table utility that should be
replaced with TableRenderer.
TODOs:
Depends on: nothing.
Type: Design
CalculatorBase.calculate_pattern takes structure: Structures but the
TODO asks whether it should be Structure (singular).
TODOs:
Depends on: nothing.
Type: Cleanup
BraggPdExperiment has a block marked
TODO: Not used if loading from cif file?.
TODOs:
Depends on: nothing.
Type: Code quality
~22 bare print() calls exist in src/ (not console.print(), not
commented out). All output should go through log or console so that
verbosity is controllable. Key offenders: fitting.py, sequential.py,
ascii.py, base.py (experiment), calculator modules, singleton.py.
Depends on: nothing.
Type: Design
The codebase mixes log.error(msg) (which may raise depending on
Reaction mode) and direct raise ValueError(...). A consistent
strategy is needed: when to use log.error (user-facing, recoverable)
vs native exceptions (programmer errors, unrecoverable). This also
relates to the Reaction mode setting (issue 61).
Depends on: issue 61.
Type: Design
Parameters and Descriptors use RangeValidator, RegexValidator,
MembershipValidator for values but rely on @typechecked (only in
some places) for type checking. Category switchable types use different
validation paths. Decide whether to:
- Use custom validators for both types and values on Parameters.
- Use custom validators for category type setters.
- Standardise the approach across the codebase.
Depends on: issue 38 (@typechecked / gemmi interaction).
Type: Design
@typechecked is currently applied only in ~24 places (factories,
collections). Decide whether it should be applied systematically to all
public method signatures, or whether custom validation (issue 67) is
preferred.
Depends on: issue 67.
Type: API ergonomics
Classes are imported in __init__.py files but users still need deep
paths to reach them. Consider whether top-level re-exports (e.g.
from easydiffraction import Project, Structure, Experiment) should
provide shorter access, and document the policy.
Depends on: nothing.
Type: Code style
Agree on and enforce a consistent ordering within every class:
- Class-level attributes / metadata
__init__- Private helper methods
- Public properties (getters/setters)
- Public methods
Each group should have a comment header (e.g.
# --- Public properties ---) for visual separation. Some classes
already use this pattern; apply it uniformly.
Depends on: nothing.
Type: Documentation
Create and maintain a table listing all categories that implement
_update(), sorted by their _update_priority. This makes the update
order explicit and helps catch priority conflicts.
Depends on: related to issues 11, 39.
Type: Code style
Some setters use new, others use value, others use the attribute
name. For example:
@id.setter
def id(self, new):
self._id.value = newAgree on a single convention (e.g. always value) and apply
consistently.
Depends on: nothing.
Type: Tooling / Correctness
Public property getters return Parameter / StringDescriptor etc.,
and setters accept float / str etc. These annotations must stay in
sync with the private _attr type. Currently there is no automated
check. Options:
- A custom script (like
param_consistency.py) to verify sync. - A ruff plugin or post-ruff check step.
- Also covers: enforcing
Basesuffix (not prefix), checking missing docstrings (issue 81), and other project-specific conventions.
Depends on: nothing.
Type: API completeness
show_calculator_types() exists per-experiment, but there is no
project/analysis-level method to list all available calculator engines.
Users exploring the API have no single entry point to see what
calculators are installed.
Depends on: nothing.
Type: Correctness
analysis_to_cif() and analysis_from_cif() exist, but audit whether
all analysis state is persisted: aliases, constraints, fit mode,
joint-fit weights, minimiser type, calculator assignments. Any missing
fields means a loaded project silently differs from the saved one.
Depends on: related to issue 16.
Type: Code style
Both Any and object are used as generic parameter types. Current
pattern: Any inside containers (dict[str, Any]), object for
standalone params (often to avoid circular imports). Decide on a policy:
- Use protocol types /
TYPE_CHECKINGimports instead ofobject. - Reserve
Anyfor genuinely unknown types. - Document when each is appropriate.
Depends on: nothing.
Type: Code quality
Some public methods (e.g. plot_meas_vs_calc, others) lack docstrings.
Decide:
- All public methods must have numpy-style docstrings.
- Private helpers: minimal one-liner docstring or none? Choose a policy.
- Enable a ruff rule (e.g.
D103,D102) or add a custom check to enforce.
Depends on: nothing.
Type: Documentation
Two manual workflow steps are required between releases/changes:
pixi run param-docstring-fixβ sync Parameter docstrings.pixi run notebook-prepareβ regenerate tutorial notebooks from scripts.
Document these in CONTRIBUTING.md or a relevant ADR so they are not
forgotten.
Depends on: nothing.
Type: Cleanup
Parameters currently carry some form of self-listing metadata that is redundant with the category/collection level. Remove it to keep the single-responsibility principle.
Depends on: nothing.
Type: Correctness
CIF output currently writes None as literal text for some fields (e.g.
_diffrn.ambient_pressure None). CIF convention uses . for
inapplicable values and ? for unknown. The CIF writer should map
None β . (or ? depending on semantics), and the reader should map
. β None.
Depends on: nothing.
Type: Correctness / UX
In single fit mode, only the last experiment's fit results are
retained because structure parameters are overwritten on each iteration.
This means earlier experiments cannot be plotted correctly after
fitting.
Fix: after fitting each experiment, store a snapshot of its fitted
parameters (both structure and experiment) so that any experiment can be
re-plotted or inspected later. Also clarify: does fit_results need to
keep the last mutable parameter set after adding a snapshot?
Depends on: nothing (issue 78 resolved).
Type: UX
plot_param_series currently requires the user to manually specify the
x-axis parameter (e.g. x_axis='temperature' β look up
diffrn.ambient_temperature). This should be auto-resolved from the
parameter name. Additionally, axis labels should include units (e.g.
"Temperature (K)").
Depends on: nothing.
Type: Documentation / UX
The current tutorial index uses a flat "level": "advanced" tag. A
richer categorisation system is needed:
- Group by topic (matching the docs structure).
- Multiple difficulty levels.
- Multiple Python-knowledge levels.
Depends on: nothing.
Type: Data
Dataset 26 description says "57 files" but should say "47 files":
"Co2SiO4, D20 (ILL), 57 files, T from ~50K to ~500K" β
"Co2SiO4, D20 (ILL), 47 files, T from ~50K to ~500K".
Depends on: nothing.
Type: Performance
In single (independent) fit mode, each experiment has its own
structure parameters and is completely independent. These fits could run
in parallel threads. Sequential mode, by contrast, must remain single-
threaded because each step's output is the next step's input.
Depends on: nothing (issue 78 resolved).
Type: Performance / Script runtime
On macOS and other spawn-based platforms, direct Bayesian tutorial
execution via python script.py or wrappers such as
pixi run tutorial docs/docs/tutorials/ed-21.py can fail during BUMPS
MPMapper startup because worker processes re-import __main__ and
re-execute top-level tutorial code. The current defensive workaround is
to fall back to serial execution for these direct-script entry points,
which avoids the crash but disables DREAM multiprocessing and causes a
large performance drop.
Observed behavior for ed-21 today:
- Jupyter execution and
easydiffraction PROJECT_DIR fitboth appear to use working parallel DREAM and complete361/361in about 40 seconds. - Direct Python-script execution of the same tutorial runs
361/361in about 220 seconds, consistent with the serial fallback path.
Possible solution: keep the existing tracker-state cleanup before
pickling and mapper startup, but replace the blanket serial fallback
with an EasyDiffraction-controlled multiprocessing context policy. For
direct Python script entry points, prefer a fork context when
available so workers do not re-import the tutorial top level. Keep the
existing behavior for import-safe module entry points such as
easydiffraction PROJECT_DIR fit and for platforms where fork is
unavailable. Document the tradeoff clearly because fork on macOS is
less conservative than spawn.
Depends on: related to issue 89, but independent.
Type: UX
Currently prints: Using experiment π¬ 'd20_30' for 'single' fitting
Should print:
Using experiment π¬ 'd20_30' (No. 30 of 47) for 'single' fitting
Depends on: nothing.
Type: CI / Tooling
CodeFactor flags TODO comments as unresolved issues (rule C100) in PRs.
Since TODOs are tracked in issues/open.md, the CodeFactor check adds
noise. Disable the C100 rule or configure CodeFactor to ignore TODO
comments.
Depends on: nothing.
Type: UX
Project.save() unconditionally prints progress via console.print().
It should respect the logger's verbosity mode so that silent/quiet
operation is possible (e.g. in automated pipelines or tests).
Depends on: nothing.
Type: UX
The shared ActivityIndicator / Rich Live region used by single fit,
sequential fit, and DREAM sampling visibly flickers in terminals
whenever the live renderable grows (new rows appended) or is updated at
a moderate rate. The effect is most pronounced in sequential fit because
rows are added more frequently than in single fit.
Findings from current investigation:
- Both single fit (
FitProgressTracker._refresh_activity_indicator) and sequential fit (_report_chunk_progress) push a freshbuild_table_renderable(...)intoActivityIndicator.update(content=...)on each progress event. The RichTableinstance is rebuilt from scratch every time. _TerminalLiveHandle/ActivityIndicatorstartrich.live.Livewithauto_refresh=True,refresh_per_second=1/_SPINNER_FRAME_SECONDS(β10 Hz), andvertical_overflow='visible'. At every refresh tick, Rich re-renders the full multi-line region (table + spinner line), which on many terminals causes a visible flicker that scales with row count.- Earlier attempts to mitigate this in sequential fit by switching to a
single-line spinner-only
Liveand printing rows above it (so Rich's print-above-live mechanism handled them) removed flicker entirely, but produced a different visual style from single fit and could not show the closing border during the run. That approach was reverted for consistency with single fit; flicker came back with it. vertical_overflow='visible'is required so the growing table is not clipped, but it also forces Rich to repaint the whole region rather than scroll/append.- The spinner animation itself drives the refresh rate; lowering
refresh_per_secondreduces flicker frequency but makes the spinner feel sluggish. - Single fit appears smoother in practice mainly because content changes
are throttled (
FIT_PROGRESS_UPDATE_SECONDS = 5.0) and rows grow slowly; the underlying mechanism is the same and it still flickers when many iterations are appended quickly.
Possible directions (not yet evaluated):
- Decouple spinner refresh from content refresh: drive
Liveat a lowrefresh_per_second(e.g. 2β4 Hz) and update content explicitly only when a new row arrives, while animating the spinner via the label string rather than Rich's renderable diff. - Render the table once as static
console.print(...)above a single-line spinner-onlyLive, and re-print only the new row(s) on each update β restore the streaming approach but emit the bottom border at the end (accept the trade-off that the closing border is not visible during the run, or print it as part of every update with ANSI cursor movement). - Use
rich.live.Live(transient=False, auto_refresh=False)and calllive.refresh()manually only when content changes; let the spinner animate via a separate background timer or label updates. - Investigate
rich.progress.Progresswith custom columns and a table panel β Rich has optimised diff rendering there. - Evaluate the actual cause on macOS Terminal / iTerm2 / VS Code terminal separately β flicker behaviour differs across emulators.
Depends on: nothing. Affects single fit, sequential fit, and DREAM sampler progress displays β any fix should keep their visuals consistent (issue #93 should be solved for all three at once).
Type: Dead code / clarity Source: Review 8 finding F7. Recommended: fold into the emcee-minimizer plan.
_restore_persisted_fit_state
(serialize.py:595-611)
calls FitResultKindEnum(result_kind_value) purely for the warning side
effect; the result is discarded. After P1.10 absorbed the
Bayesian-specific categories there is nothing else to do per
result_kind.
Fix: replace with a validator helper that takes a string and logs
the warning, or move the warning into fit_result.result_kind setter so
invalid values are caught on read. Either removes the "compute and
ignore" pattern.
Depends on: nothing.
Type: Robustness / partial-data edge case Source: Review 8 finding F9.
FitParameterItem.has_posterior_summary returns True if any posterior
field is set, and posterior_summary then builds a
PosteriorParameterSummary whose missing floats become NaN. A
hand-edited or partially-written CIF row with only
posterior_gelman_rubin = 1.02 and the rest unset produces a summary
whose median, standard_deviation, and both interval bounds are
NaN. Downstream plotting and the display.fit_results table render
NaN intervals β harder to debug than a clean "no posterior" outcome.
The deterministic-fit case is fine: deterministic fits set all required
fields to None, so has_posterior_summary() returns False.
Fix: tighten has_posterior_summary to require the core stats (at
least posterior_median and one interval bound) before emitting a
summary, or split the dataclass into required-statistics and
optional-diagnostics components.
Depends on: nothing.
Type: Cleanup Source: minimizer-input-output-split review 6.
Analysis._clear_fit_result_projection is a private method with no
callers after _clear_persisted_fit_state switched to replacing
self._fit_result with a fresh paired result instance.
TODOs:
Fix: delete the unused helper, or reintroduce a caller only if a future fit-result reset path genuinely needs to preserve the active instance.
Depends on: nothing.
Type: Code readability Source: minimizer-input-output-split
review 6.
Most FitResultBase descriptors use default=None, allow_none=True so
pre-fit CIF output serializes unknown values as ?. result_kind
intentionally keeps a valid enum default because it drives deterministic
versus Bayesian projection handling, but that exception is not
documented in code.
TODOs:
Fix: add a short code comment near the result_kind descriptor
explaining why it keeps a concrete default while unknown result values
use None.
Depends on: nothing.
Type: Test coverage
The runtime gemmi self-check in the IUCr CIF writer was removed (it
validated our own deterministic output at write time and depended on
dictionaries under tmp/iucr-dicts/; see the Β§2.5 amendment in
iucr-cif-tag-alignment.md).
That spec-compliance guarantee now needs to live in a dev-time test
instead.
Fix: add a unit or functional test that renders both a powder and a
single-crystal IUCr report CIF and validates every emitted tag against
the latest official COMCIFS cif_core.dic and cif_pow.dic.
Requirements:
- Cover both powder (
_pd_*, profile/reflection loops) and single-crystal report outputs. - Do not read
tmp/at runtime β pass the dictionaries explicitly as a committed test fixture (or fetch them in test setup and pass the path in). The check belongs in the test suite, not in the user's write path. - Parse the DDLm/CIF2 form correctly: the current dictionaries use
save_<name>frames with_definition.id, which the removed helper'ssave__tagregex and a plaingemmi.cif.read_filecould not handle. Use gemmi's DDL reader or a scan adapted to the DDLm layout. - Allow the project's private
_easydiffraction_*extension namespace.
Depends on: nothing.
Type: UX / Visualization
crysview generates bonds with the cif_core distance rule
(min_bond_distance_cutoff β€ d β€ r_bond(A) + r_bond(B) + bond_distance_incr),
then prunes to the first coordination shell β a contact survives only if
it is within 1.3Γ the nearer atom's nearest-neighbour distance
(COORDINATION_SHELL_FACTOR in display/structure/builder.py). This
stop-gap handles the common cases (e.g. LBCO renders just the CoβO
octahedron) without a new dependency, but the fixed factor is still a
heuristic: it can over-prune strongly distorted shells (e.g. elongated
JahnβTeller octahedra) or under-prune others, and it is not yet
user-configurable.
Fix: consider a robust, configurable near-neighbour algorithm for
automatic "reasonable" bonding β e.g. a Voronoi / solid-angle method
such as pymatgen's CrystalNN or VoronoiNN, which weights neighbours
by solid angle instead of a single relative cutoff. The Voronoi route is
the most robust across arbitrary structures but introduces a heavyweight
dependency (pymatgen), so it needs a dependency decision; an
ASE/Jmol-style multiplicative covalent tolerance is lighter but, like
the current factor, cannot separate shells when ionic-cation covalent
radii are large.
Depends on: dependency decision for pymatgen (if the Voronoi route is chosen).
Type: UX / Display
list_tutorials now renders its table at the real terminal width via a
new optional width parameter threaded through the table render path
(render_table β TableRenderer.render β backend render; Rich
applies it, the HTML backend ignores it). Every other table and all log
output still go through the shared Rich console, whose width is floored
at ConsoleManager._MIN_CONSOLE_WIDTH = 130 ("to avoid cramped
layouts"). On a standard ~80-column terminal that floor makes wide
tables overflow and soft-wrap badly.
Fix: decide on a global policy β either have _detect_width trust
the detected terminal width (keeping 130 only as a fallback when
detection fails), or pass the terminal width into more table call sites
the way list_tutorials now does. A global change affects every table
(fit results, parameters, ...) and all logs, so weigh it against the
deliberate minimum-width choice.
Depends on: related to issue 62.
Type: Display / Notebook parity
list_tutorials shows a two-line cell in the terminal β a colored title
on the first line and a dimmed description on the second β using Rich
markup and an embedded newline. The Jupyter table backend
(PandasTableBackend) cannot render this: _strip_rich_markup only
matches a single full-cell [color]text[/color], and HTML collapses the
newline, so the markup would show as literal text. list_tutorials is
therefore gated via in_jupyter() to show only the plain title in
notebooks, which drops the description and the color there.
Fix: teach the HTML backend to render the same styling β translate
embedded newlines to <br>, map [dim] to reduced opacity, and accept
multiple/mixed markup tags per cell β then remove the terminal-only gate
in list_tutorials so notebooks also get the styled two-line entry.
Depends on: related to issue 62.
Type: Test coverage
The list_tutorials table gained a styled two-line cell (colored title
plus dimmed description), a terminal-only in_jupyter() gate that falls
back to the plain title, and a new optional width parameter on the
table render path. Existing tests only assert that titles appear in the
output.
Fix: add unit tests for the description line appearing in the
terminal (non-Jupyter) path, the Jupyter-gated path showing the plain
title with no literal Rich markup, and the width parameter sizing the
rendered Rich table. Run pixi run fix / check / unit-tests to
confirm the shared-renderer signature change.
Depends on: nothing.
Type: Display / UX
TableRenderer._prepare_dataframe bumps the DataFrame index to 1-based,
and both the Rich and pandas backends always render it as the first
column. For tables that already carry an explicit identifier β e.g.
list_tutorials, whose id column duplicates that 1-based counter β
the leading index column is redundant and reads as a duplicate.
Fix: add an opt-out (e.g. a show_index flag on the render path) so
callers with their own id column can hide the auto-generated index, or
only render the index column when no explicit id column is present.
Depends on: nothing.
Type: Test infrastructure
Deferred cross-repository work for the
Test Suite and Validation Strategy
ADR (Β§7, Β§8). The harness code lives in diffraction-lib; the corpus,
results database, and benchmark/reference history live in the
diffraction data repository, fetched at runtime and written back by a
nightly job that installs easydiffraction from PyPI (acceptance-style).
Items:
- COD corpus check. Download ~100β200 CIF files from the
Crystallography Open Database, load each, and record per-file status
(
ok/partial+ missing fields /fail) in a git-diffable CSV keyed and ordered by COD id. Add a--recheck-failedflag to re-run only the failed/partial entries after fixes (instead of new random files). Extend with per-engine calculation results on the corpus. - Generative fuzzing. Randomly generate ~100β200 structures (random space group, cell, 1β10 atoms with random coordinates/ADP/occupancy), compute patterns across engines, and record disagreements in the same database.
- Benchmark history + gate. Commit
pytest-benchmarkbaseline JSON to the data repository and add a regression threshold once timing variance is characterised on a controlled runner. (The serial benchmark task itself now exists β see issue 16 β this is the history/gating remainder.) - External-software comparison. Add FullProf (then GSAS-II/TOPAS) pre-calculated profiles as zipped projects in the data repository so the Verification pages can overlay them against easydiffraction.
Depends on: the diffraction data repository; cross-repo
coordination.
Type: CI / Documentation
The fast docs gate (docs-build + link-check + spell-check) catches
broken nav/internal links and typos on every push, but does not yet
check external URLs. Add a lychee link checker (with an allowlist for
rate-limited/unstable domains), coordinated with the
Documentation CI and Build Verification
ADR. Run it nightly or on pull requests to avoid flakiness from external
sites. Also covers link-checking of URLs that appear only inside
executed notebook output cells (a feature that does not exist yet).
Depends on: nothing.
Type: Test coverage / Documentation
The Verification docs section ships with the framework and the first
cross-engine comparison page (constant-wavelength powder, cryspy β
crysfml). Extend it to the remaining supported combinations declared by
the calculator support matrix β time-of-flight powder (cryspy β crysfml)
and single crystal β so every valid experiment/instrument combination is
documented and regression-checked at least once. Each new page is a
calculation-only .py under docs/docs/verification/ wired into
script-tests and notebook-tests, with explicit metric tolerances.
Depends on: nothing.
Type: Tooling / Correctness
The project type-annotates public signatures but runs no static type
checker (only ruff's TC import-placement rules). A genuine bug slipped
through as a result:
Plotter._plot_single_crystal_posterior_predictive_summary called
PlotlyPlotter._get_diagonal_shape() with no arguments while the method
requires (minimum, maximum), so the single-crystal posterior-
predictive plot raised TypeError whenever reached. A type checker
would have flagged the wrong-arity call at lint time, without needing a
test to exercise the path β and would catch the whole class of such
errors.
Fix: add a checker (mypy, pyright, or ty) as a pixi task wired
into check and the lint CI workflow, alongside the existing ruff /
pydoclint / interrogate gates. Roll out incrementally to manage the
initial error backlog: start lenient (e.g. --follow-imports=silent or
a per-package allowlist) and gate only new/changed code first, then
tighten. The codebase already annotates public signatures, so it is
well-positioned.
Depends on: nothing. Best landed as its own focused effort because enabling a checker on an existing codebase surfaces a backlog that needs a baseline-cleanup plan.
Type: Display / Architecture
Records the two viable strategies for rendering interactive Plotly
figures in live notebooks, so the trade-off is not re-litigated. See
plotting-docs-performance.md.
Background. Live notebooks historically rendered via
display(HTML(pio.to_html(..., include_plotlyjs='cdn'))), which caused
an empty first plot after kernel restart (the CDN <script src> loaded
asynchronously while Plotly.newPlot ran immediately) and a loading
gap. Two ways to fix it:
-
Option 1 β Native mimetype renderer (
fig.show()). Emit theapplication/vnd.plotly.v1+jsonmime bundle and let the JupyterLab Plotly extension render it. Pros: simplest, lowest maintenance, officially supported, no CDN, no script-in-output artifacts. Cons: live notebooks lose the three custom post-script behaviours (dynamic theme-sync on JupyterLab light/dark toggle, hidden-tab resize, the modebar legend-toggle button); a saved.ipynb's plot output is the spec JSON, not self-contained HTML. -
Option 2 β Self-hosted loader (current). Ship the vendored Plotly bundle + the shared
ed-figures.jsloader in the wheel; the first figure injects them once per kernel session and each figure renders viawindow.edFigures.renderSpec(id, spec)delivered as adisplay(Javascript(...))output, so the HTML output is just the plot div (no<script>tags some hosts render as empty rows). Pros: keeps all three custom behaviours; CDN-free/archival; unified with the docs delivery. Cons: more moving parts; depends on the notebook being trusted for a reopened (not re-run) notebook to re-render.
Current choice: Option 2 (keeps the live-notebook extras). Revisit Option 1 if the loader proves fragile across notebook frontends or the maintenance cost outweighs the three behaviours.
Depends on: nothing.
Type: Display / Environment
Symptom. In the VISA-hosted, iframe-embedded JupyterLab
(visa.ess.eu/.../jupyter/.../lab), every interactive Plotly figure is
preceded by several empty rows that fill in over ~1β2 s, and the plot
often renders collapsed. On a standard local JupyterLab the branch is
fine β the only issue there is the CDN race (issue addressed by the
self-hosted runtime; see below). So this is specific to the VISA
environment, not the library.
Root cause (from a DOM inspection in VISA). The plot element renders
at height 0, nested under
div.jp-WindowedPanel-viewport.jp-content-visibility-mode. VISA's
JupyterLab runs notebooks in windowed mode (content-visibility
virtualization). The output container is 0Γ0 at the moment Plotly
draws, and because the figure config is responsive: true, Plotly
sizes to that 0Γ0 container and renders a zero-size plot that does not
recover. The "empty rows" are that collapsed zero-height output as the
viewport re-measures. This is a known Plotly β JupyterLab-windowing
interaction.
Workaround (user side). JupyterLab β Settings β Notebook β
Windowing mode = defer/none disables the virtualization. (The
user reported this alone did not resolve it in VISA, so VISA may
force or wrap the setting β needs confirmation.)
Attempts that did NOT resolve it in VISA (all reverted to keep the code minimal; recorded so they are not retried blindly):
- Self-hosted runtime instead of CDN, delivered as a single HTML output,
as a
display(Javascript(...))output, and as multiple/one inline<script>tags. (The self-hosting itself is kept β it fixes the real CDN race β but none of the delivery variants changed the VISA empty rows.) - Removing the loading skeleton; removing then restoring the
min-heightreservation. - Trimming the figure top margin +
title.automargin(chasing a misread "default margin" theory). - Preloading the runtime on
import easydiffraction(regressed: added a visible empty output line on the import cell). - Skipping the resize
ResizeObserverfor live figures. - Reserving an explicit container height for windowing.
- Deferring the render until the container reports a non-zero size
(
requestAnimationFramepoll). This did get the plot to render at full height in VISA (DOM showedh=565instead of0), but the user still reported empty rows visually β so it is necessary-but-not- sufficient there.
Promising future directions. Set responsive: false with an
explicit width/height for live figures so Plotly never depends on the
0Γ0 container; or render the figure off-screen and swap it in once
sized; or detect the VISA/windowed environment and special-case it.
Needs to be developed and tested inside VISA, since it does not
reproduce on a standard JupyterLab.
Depends on: nothing. Lower priority β affects only the VISA deployment, and a user-side windowing-mode change may suffice.
Type: Experiment model / Peak profile / API naming
The four empirical peak-asymmetry parameters (asym_empir_1β¦4, the
pd-neut-cwl_pv-asym_empir_pbso4 Verification page) are the
BΓ©rarβBaldinozzi correction β FullProf's P1βP4 β a
phenomenological sum of functions in 1/tan ΞΈ and 1/tan 2ΞΈ. It can
fit an asymmetric peak, but the parameters carry no physical
meaning, are strongly correlated, do not transfer between
datasets, and can misbehave (over-correction, unphysical profile
shapes).
The FingerβCoxβJephcoat (FCJ) S_L/D_L model is physically based:
just two parameters tied to real instrument geometry (sample and
slit/detector heights over the goniometer radius), with the correct
built-in angular dependence β asymmetry that vanishes at 2ΞΈ = 90Β° and
reverses past it. Fewer parameters, better-conditioned, and
instrument-meaningful.
Two future considerations:
- Rename the empirical parameters so the name states what they are
β e.g.
asym_berar_baldinozzi_1β¦4(or aberar_baldinozzi_p*form) β rather than the genericasym_empir_*, which hides their origin and conflates "empirical asymmetry" with the specific BΓ©rarβBaldinozzi formula. - Add the FCJ model alongside the empirical one (not as a replacement), as a switchable asymmetry choice, so users can pick the physically-based two-parameter model when the instrument geometry is known and fall back to the empirical correction otherwise.
Relates to: the asymmetry discrepancy tracked on the
pd-neut-cwl_pv-asym_empir_pbso4 Verification page (currently in
docs/docs/verification/ci_skip.txt), and the TCH/FCJ work noted on the
pd-neut-cwl_tch-fcj_lab6 page.
Depends on: calculator-backend support for the FCJ asymmetry parameters (cryspy/crysfml) before the second item can be wired through.
| # | Issue | Severity | Type |
|---|---|---|---|
| 3 | Rebuild joint-fit weights | π‘ Med | Fragility |
| 5 | Analysis as DatablockItem |
π‘ Med | Consistency |
| 8 | Explicit create() signatures |
π‘ Med | API safety |
| 9 | Future enum extensions | π’ Low | Design |
| 10 | Unify update orchestration | π’ Low | Maintainability |
| 11 | Document _update contract |
π’ Low | Maintainability |
| 13 | Suppress redundant dirty-flag sets | π’ Low | Performance |
| 14 | Finer-grained change tracking | π’ Low | Performance |
| 15 | Validate joint-fit weights | π‘ Med | Correctness |
| 17 | Use PDF-specific CIF names | π’ Low | Naming |
| 18 | Move CIF v2βv1 conversion out of calculator | π’ Low | Maintainability |
| 19 | Debug-mode logging for calculator imports | π’ Low | Diagnostics |
| 20 | Redirect/suppress CrysPy stderr | π’ Low | UX |
| 21 | Clarify CrysPy TOF background CIF tags | π‘ Med | Correctness |
| 22 | Check SC instrument mapping in CrysPy | π’ Low | Correctness |
| 23 | Investigate PyCrysFML pattern length discrepancy | π’ Low | Correctness |
| 24 | Process defaults on experiment creation | π’ Low | Design |
| 25 | Refactor data _update methods |
π‘ Med | Maintainability |
| 26 | Clarify dtype usage in data arrays |
π’ Low | Cleanup |
| 27 | Handle zero uncertainty in Bragg PD | π’ Low | Correctness |
| 28 | Clarify Bragg PD data collection description | π’ Low | Cleanup |
| 29 | Standardise CIF ID validator pattern | π‘ Med | Consistency |
| 30 | Make refinement_status default an Enum |
π’ Low | Design |
| 31 | Rename PD data point mixins | π’ Low | Naming |
| 32 | Move common methods to DatablockCollection |
π‘ Med | Maintainability |
| 33 | Make _update_categories abstract |
π‘ Med | Design |
| 34 | Auto-extract PeakProfileTypeEnum |
π’ Low | Design |
| 35 | Rename BeamModeEnum members to CWL/TOF |
π’ Low | Naming |
| 36 | Common EnumBase class |
π’ Low | Design |
| 37 | Rename experiment .type property |
π’ Low | Naming |
| 38 | Fix @typechecked/gemmi in factories |
π‘ Med | Bug |
| 39 | Improve _update_priority handling |
π’ Low | Design |
| 40 | Reset .user_constrained to False |
π’ Low | Feature |
| 41 | Check _mark_dirty in _set_value |
π’ Low | Cleanup |
| 42 | MkDocs type unpacking in validation | π’ Low | Docs |
| 43 | Fix summary display inconsistencies | π’ Low | UX |
| 44 | Merge parameter record construction | π’ Low | Cleanup |
| 45 | Decide alias/constraint descriptor default | π’ Low | Design |
| 46 | Improve JointFitItem descriptions |
π’ Low | Naming |
| 47 | Improve error handling in crystallography | π’ Low | Diagnostics |
| 48 | Fix CrysPy TOF instrument default | π’ Low | Bug workaround |
| 49 | Automate space group CIF name variants | π’ Low | Maintainability |
| 50 | Clarify Cell._update minimizer param |
π’ Low | Cleanup |
| 52 | Rename line-segment y to intensity |
π’ Low | Naming |
| 53 | Move show() to CategoryCollection |
π’ Low | Maintainability |
| 54 | Add point_id to excluded regions |
π’ Low | Completeness |
| 55 | Fix Jupyter scroll disabling for MkDocs | π’ Low | Docs / UX |
| 56 | Make ASCII plot width configurable | π’ Low | UX |
| 57 | Clean up CIF deserialisation helpers | π’ Low | Maintainability |
| 58 | Move ProjectInfo CIF methods to serialize |
π’ Low | Maintainability |
| 59 | Add CIF name validation in parse | π’ Low | Robustness |
| 60 | Unify mkdir usage |
π’ Low | Cleanup |
| 61 | Clarify logger default reaction mode | π’ Low | Design |
| 62 | Complete render_table β TableRenderer |
π’ Low | Cleanup |
| 63 | Fix calculator calculate_pattern signature |
π’ Low | Design |
| 64 | Check unused-if-loading-from-CIF code | π’ Low | Cleanup |
| 65 | Replace all bare print() with logging |
π‘ Med | Code quality |
| 66 | Error-handling strategy: log.error vs raise |
π‘ Med | Design |
| 67 | Custom validation for params and category types | π‘ Med | Design |
| 68 | @typechecked on all public methods? |
π’ Low | Design |
| 69 | Shorter public API names via __init__ |
π’ Low | API ergonomics |
| 70 | Standardise class member ordering + headers | π‘ Med | Code style |
| 71 | _update_priority reference table |
π’ Low | Documentation |
| 73 | Unify setter parameter naming | π’ Low | Code style |
| 74 | Sync property type hints + custom lint rules | π‘ Med | Tooling |
| 75 | show_supported_calculators() on Analysis |
π’ Low | API completeness |
| 79 | Verify analysis CIF serialisation completeness | π’ Low | Correctness |
| 80 | Resolve Any vs object annotation policy |
π’ Low | Code style |
| 81 | Enforce docstrings on all public methods | π‘ Med | Code quality |
| 82 | Document param-docstring-fix workflow |
π’ Low | Documentation |
| 83 | Remove redundant parameter listing | π’ Low | Cleanup |
| 84 | Serialise None as . in CIF output |
π‘ Med | Correctness |
| 85 | Retain per-experiment fitted params for plotting | π‘ Med | Correctness |
| 86 | Auto-resolve plot_param x-axis + add units |
π’ Low | UX |
| 87 | Redesign tutorial grouping/categorisation | π’ Low | Documentation |
| 88 | Fix Dataset 26 description (47 not 57) | π’ Low | Data |
| 89 | Parallel independent fits for single mode | π‘ Med | Performance |
| 95 | Re-enable DREAM multiprocessing in direct scripts | π‘ Med | Performance / Script runtime |
| 90 | Show experiment number during sequential fitting | π’ Low | UX |
| 91 | Disable TODO checks in CodeFactor PRs | π’ Low | CI / Tooling |
| 92 | Make save() respect verbosity |
π’ Low | UX |
| 93 | Eliminate flicker in live progress tables | π‘ Med | UX |
| 105 | Remove orphaned fit-result reset helper | π’ Low | Cleanup |
| 106 | Document FitResultBase.result_kind default |
π’ Low | Code readability |
| 107 | Validate CIF report vs IUCr dictionaries | π‘ Med | Test coverage |
| 108 | Smarter automatic bond detection (near-neighbour) | π’ Low | UX / Visualization |
| 109 | Let more tables adapt to terminal width | π’ Low | UX / Display |
| 110 | Styled multi-line table cells in HTML backend | π’ Low | Display / Notebook parity |
| 111 | Test coverage for list_tutorials rendering |
π’ Low | Test coverage |
| 112 | Suppress redundant row-index column in tables | π’ Low | Display / UX |
| 113 | Cross-repository validation harness (nightly) | π‘ Med | Test infrastructure |
| 114 | External link checking in the docs gate | π’ Low | CI / Documentation |
| 115 | Expand cross-engine verification coverage | π’ Low | Test coverage |
| 116 | Add a static type checker to the quality gate | π‘ Med | Tooling / Correctness |
| 117 | Live-notebook Plotly: loader vs native mimetype | π’ Low | Display / Architecture |
| 118 | Plotly empty rows in the VISA JupyterLab | π’ Low | Display / Environment |
| 119 | Model sample absorption (DebyeβScherrer ΞΌR) | π‘ Med | Physics / Engine feature |