Skip to content

Commit 0fde514

Browse files
committed
Enhances documentation with docstrings
1 parent 7a6ca3e commit 0fde514

File tree

21 files changed

+401
-23
lines changed

21 files changed

+401
-23
lines changed

src/easydiffraction/analysis/calculators/factory.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717

1818

1919
class CalculatorFactory:
20+
"""Factory for creating calculation engine instances.
21+
22+
The factory exposes discovery helpers to list and show available
23+
calculators in the current environment and a creator that returns an
24+
instantiated calculator or ``None`` if the requested one is not
25+
available.
26+
"""
27+
2028
_potential_calculators: Dict[str, Dict[str, Union[str, Type[CalculatorBase]]]] = {
2129
'crysfml': {
2230
'description': 'CrysFML library for crystallographic calculations',
@@ -36,6 +44,14 @@ class CalculatorFactory:
3644
def _supported_calculators(
3745
cls,
3846
) -> Dict[str, Dict[str, Union[str, Type[CalculatorBase]]]]:
47+
"""Return calculators whose engines are importable.
48+
49+
This filters the list of potential calculators by instantiating
50+
their classes and checking the ``engine_imported`` property.
51+
52+
Returns:
53+
Mapping from calculator name to its config dict.
54+
"""
3955
return {
4056
name: cfg
4157
for name, cfg in cls._potential_calculators.items()
@@ -44,10 +60,16 @@ def _supported_calculators(
4460

4561
@classmethod
4662
def list_supported_calculators(cls) -> List[str]:
63+
"""List names of calculators available in the environment.
64+
65+
Returns:
66+
List of calculator identifiers, e.g. ``["crysfml", ...]``.
67+
"""
4768
return list(cls._supported_calculators().keys())
4869

4970
@classmethod
5071
def show_supported_calculators(cls) -> None:
72+
"""Pretty-print supported calculators and their descriptions."""
5173
columns_headers: List[str] = ['Calculator', 'Description']
5274
columns_alignment = ['left', 'left']
5375
columns_data: List[List[str]] = []
@@ -64,6 +86,14 @@ def show_supported_calculators(cls) -> None:
6486

6587
@classmethod
6688
def create_calculator(cls, calculator_name: str) -> Optional[CalculatorBase]:
89+
"""Create a calculator instance by name.
90+
91+
Args:
92+
calculator_name: Identifier of the calculator to create.
93+
94+
Returns:
95+
A calculator instance or ``None`` if unknown or unsupported.
96+
"""
6797
config = cls._supported_calculators().get(calculator_name)
6898
if not config:
6999
print(error(f"Unknown calculator '{calculator_name}'"))

src/easydiffraction/analysis/fit_helpers/reporting.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414

1515

1616
class FitResults:
17+
"""Container for results of a single optimization run.
18+
19+
Holds success flag, chi-square metrics, iteration counts, timing,
20+
and parameter objects. Provides a printer to summarize key
21+
indicators and a table of fitted parameters.
22+
"""
23+
1724
def __init__(
1825
self,
1926
success: bool = False,
@@ -27,6 +34,22 @@ def __init__(
2734
fitting_time: Optional[float] = None,
2835
**kwargs: Any,
2936
) -> None:
37+
"""Initialize FitResults with the given parameters.
38+
39+
Args:
40+
success: Indicates if the fit was successful.
41+
parameters: List of parameters used in the fit.
42+
chi_square: Chi-square value of the fit.
43+
reduced_chi_square: Reduced chi-square value of the fit.
44+
message: Message related to the fit.
45+
iterations: Number of iterations performed.
46+
engine_result: Result from the fitting engine.
47+
starting_parameters: Initial parameters for the fit.
48+
fitting_time: Time taken for the fitting process.
49+
**kwargs: Additional engine-specific fields. If ``redchi``
50+
is provided and ``reduced_chi_square`` is not set, it is
51+
used as the reduced chi-square value.
52+
"""
3053
self.success: bool = success
3154
self.parameters: List[Any] = parameters if parameters is not None else []
3255
self.chi_square: Optional[float] = chi_square
@@ -54,6 +77,15 @@ def display_results(
5477
f_obs: Optional[List[float]] = None,
5578
f_calc: Optional[List[float]] = None,
5679
) -> None:
80+
"""Render a human-readable summary of the fit.
81+
82+
Args:
83+
y_obs: Observed intensities for pattern R-factor metrics.
84+
y_calc: Calculated intensities for pattern R-factor metrics.
85+
y_err: Standard deviations of observed intensities for wR.
86+
f_obs: Observed structure-factor magnitudes for Bragg R.
87+
f_calc: Calculated structure-factor magnitudes for Bragg R.
88+
"""
5789
status_icon = '✅' if self.success else '❌'
5890
rf = rf2 = wr = br = None
5991
if y_obs is not None and y_calc is not None:

src/easydiffraction/analysis/fit_helpers/tracking.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ def format_cell(
4242

4343

4444
class FitProgressTracker:
45-
"""Tracks and reports the reduced chi-square during the optimization
46-
process.
45+
"""Track and report reduced chi-square during optimization.
46+
47+
The tracker keeps iteration counters, remembers the best observed
48+
reduced chi-square and when it occurred, and can display progress as
49+
a table in notebooks or a text UI in terminals.
4750
"""
4851

4952
def __init__(self) -> None:
@@ -59,6 +62,7 @@ def __init__(self) -> None:
5962
self._display_handle: Optional[DisplayHandle] = None
6063

6164
def reset(self) -> None:
65+
"""Reset internal state before a new optimization run."""
6266
self._iteration = 0
6367
self._previous_chi2 = None
6468
self._last_chi2 = None
@@ -72,15 +76,14 @@ def track(
7276
residuals: np.ndarray,
7377
parameters: List[float],
7478
) -> np.ndarray:
75-
"""Track chi-square progress during the optimization process.
79+
"""Update progress with current residuals and parameters.
7680
77-
Parameters:
78-
residuals (np.ndarray): Array of residuals between measured
79-
and calculated data.
80-
parameters (list): List of free parameters being fitted.
81+
Args:
82+
residuals: Residuals between measured and calculated data.
83+
parameters: Current free parameters being fitted.
8184
8285
Returns:
83-
np.ndarray: Residuals unchanged, for optimizer consumption.
86+
Residuals unchanged, for optimizer consumption.
8487
"""
8588
self._iteration += 1
8689

@@ -133,28 +136,39 @@ def track(
133136

134137
@property
135138
def best_chi2(self) -> Optional[float]:
139+
"""Best recorded reduced chi-square value or None."""
136140
return self._best_chi2
137141

138142
@property
139143
def best_iteration(self) -> Optional[int]:
144+
"""Iteration index at which the best chi-square was observed."""
140145
return self._best_iteration
141146

142147
@property
143148
def iteration(self) -> int:
149+
"""Current iteration counter."""
144150
return self._iteration
145151

146152
@property
147153
def fitting_time(self) -> Optional[float]:
154+
"""Elapsed time of the last run in seconds, if available."""
148155
return self._fitting_time
149156

150157
def start_timer(self) -> None:
158+
"""Begin timing of a fit run."""
151159
self._start_time = time.perf_counter()
152160

153161
def stop_timer(self) -> None:
162+
"""Stop timing and store elapsed time for the run."""
154163
self._end_time = time.perf_counter()
155164
self._fitting_time = self._end_time - self._start_time
156165

157166
def start_tracking(self, minimizer_name: str) -> None:
167+
"""Initialize display and headers and announce the minimizer.
168+
169+
Args:
170+
minimizer_name: Name of the minimizer used for the run.
171+
"""
158172
print(f"🚀 Starting fit process with '{minimizer_name}'...")
159173
print('📈 Goodness-of-fit (reduced χ²) change:')
160174

@@ -189,6 +203,11 @@ def start_tracking(self, minimizer_name: str) -> None:
189203
print('╞' + '╪'.join(['═' * FIXED_WIDTH for _ in DEFAULT_HEADERS]) + '╡')
190204

191205
def add_tracking_info(self, row: List[str]) -> None:
206+
"""Append a formatted row to the progress display.
207+
208+
Args:
209+
row: Columns corresponding to DEFAULT_HEADERS.
210+
"""
192211
if is_notebook() and display is not None:
193212
# Add row to DataFrame
194213
self._df_rows.append(row)
@@ -214,6 +233,7 @@ def add_tracking_info(self, row: List[str]) -> None:
214233
print(formatted_row)
215234

216235
def finish_tracking(self) -> None:
236+
"""Finalize progress display and print best result summary."""
217237
# Add last iteration as last row
218238
row: List[str] = [
219239
str(self._last_iteration),

src/easydiffraction/core/collection.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
11
# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors <https://github.com/easyscience/diffraction>
22
# SPDX-License-Identifier: BSD-3-Clause
3+
"""Lightweight container for guarded items with name-based indexing.
4+
5+
`CollectionBase` maintains an ordered list of items and a lazily rebuilt
6+
index by the item's identity key. It supports dict-like access for get,
7+
set and delete, along with iteration over the items.
8+
"""
39

410
from __future__ import annotations
511

612
from easydiffraction.core.guard import GuardedBase
713

814

915
class CollectionBase(GuardedBase):
16+
"""A minimal collection with stable iteration and name indexing.
17+
18+
Args:
19+
item_type: Type of items accepted by the collection. Used for
20+
validation and tooling; not enforced at runtime here.
21+
"""
22+
1023
def __init__(self, item_type) -> None:
1124
super().__init__()
1225
self._items: list = []
1326
self._index: dict = {}
1427
self._item_type = item_type
1528

1629
def __getitem__(self, name: str):
30+
"""Return an item by its identity key.
31+
32+
Rebuilds the internal index on a cache miss to stay consistent
33+
with recent mutations.
34+
"""
1735
try:
1836
return self._index[name]
1937
except KeyError:
2038
self._rebuild_index()
2139
return self._index[name]
2240

2341
def __setitem__(self, name: str, item) -> None:
42+
"""Insert or replace an item under the given identity key."""
2443
# Check if item with same identity exists; if so, replace it
2544
for i, existing_item in enumerate(self._items):
2645
if existing_item._identity.category_entry_name == name:
@@ -33,6 +52,7 @@ def __setitem__(self, name: str, item) -> None:
3352
self._rebuild_index()
3453

3554
def __delitem__(self, name: str) -> None:
55+
"""Delete an item by key or raise ``KeyError`` if missing."""
3656
# Remove from _items by identity entry name
3757
for i, item in enumerate(self._items):
3858
if item._identity.category_entry_name == name:
@@ -43,32 +63,40 @@ def __delitem__(self, name: str) -> None:
4363
raise KeyError(name)
4464

4565
def __iter__(self):
66+
"""Iterate over items in insertion order."""
4667
return iter(self._items)
4768

4869
def __len__(self) -> int:
70+
"""Return the number of items in the collection."""
4971
return len(self._items)
5072

5173
def _key_for(self, item):
52-
"""Private helper to get the key for an item."""
74+
"""Return the identity key for ``item`` (category or
75+
datablock).
76+
"""
5377
return item._identity.category_entry_name or item._identity.datablock_entry_name
5478

5579
def _rebuild_index(self) -> None:
80+
"""Rebuild the name-to-item index from the ordered item list."""
5681
self._index.clear()
5782
for item in self._items:
5883
key = self._key_for(item)
5984
if key:
6085
self._index[key] = item
6186

6287
def keys(self):
88+
"""Yield keys for all items in insertion order."""
6389
return (self._key_for(item) for item in self._items)
6490

6591
def values(self):
92+
"""Yield items in insertion order."""
6693
return (item for item in self._items)
6794

6895
def items(self):
96+
"""Yield ``(key, item)`` pairs in insertion order."""
6997
return ((self._key_for(item), item) for item in self._items)
7098

7199
@property
72100
def names(self):
73-
"""Return a list of all item keys in the collection."""
101+
"""List of all item keys in the collection."""
74102
return list(self.keys())

src/easydiffraction/experiments/categories/background/base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,25 @@
1010

1111

1212
class BackgroundBase(CategoryCollection):
13+
"""Abstract base for background subcategories in experiments.
14+
15+
Concrete implementations provide parameterized background models and
16+
compute background intensities on the experiment grid.
17+
"""
18+
1319
@abstractmethod
1420
def calculate(self, x_data: Any) -> Any:
21+
"""Compute background values for the provided x grid.
22+
23+
Args:
24+
x_data: X positions (e.g. 2θ, TOF) at which to evaluate.
25+
26+
Returns:
27+
Background intensity array aligned with ``x_data``.
28+
"""
1529
pass
1630

1731
@abstractmethod
1832
def show(self) -> None:
33+
"""Print a human-readable view of background components."""
1934
pass

src/easydiffraction/experiments/categories/instrument/base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77

88

99
class InstrumentBase(CategoryItem):
10+
"""Base class for instrument category items.
11+
12+
Provides the common identity code and serves as a parent for
13+
concrete instrument definitions (CWL/TOF).
14+
"""
15+
1016
def __init__(self) -> None:
1117
super().__init__()
1218
self._identity.category_code = 'instrument'

src/easydiffraction/experiments/categories/instrument/cwl.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,20 @@ def __init__(
5353

5454
@property
5555
def setup_wavelength(self):
56+
"""Incident wavelength parameter (Å)."""
5657
return self._setup_wavelength
5758

5859
@setup_wavelength.setter
5960
def setup_wavelength(self, value):
61+
"""Set incident wavelength value (Å)."""
6062
self._setup_wavelength.value = value
6163

6264
@property
6365
def calib_twotheta_offset(self):
66+
"""Instrument misalignment two-theta offset (deg)."""
6467
return self._calib_twotheta_offset
6568

6669
@calib_twotheta_offset.setter
6770
def calib_twotheta_offset(self, value):
71+
"""Set two-theta offset value (deg)."""
6872
self._calib_twotheta_offset.value = value

0 commit comments

Comments
 (0)