Skip to content

Commit d8d2a46

Browse files
committed
Adds comprehensive docstrings to Analysis classes and descriptors
1 parent 4c58965 commit d8d2a46

File tree

6 files changed

+232
-7
lines changed

6 files changed

+232
-7
lines changed

src/easydiffraction/analysis/analysis.py

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,34 @@
2424

2525

2626
class Analysis:
27+
"""High-level orchestration of analysis tasks for a Project.
28+
29+
This class wires calculators and minimizers, exposes a compact
30+
interface for parameters, constraints and results, and coordinates
31+
computations across the project's sample models and experiments.
32+
33+
Typical usage:
34+
35+
- Display or filter parameters to fit.
36+
- Select a calculator/minimizer implementation.
37+
- Calculate patterns and run single or joint fits.
38+
39+
Attributes:
40+
project: The parent Project object.
41+
aliases: A registry of human-friendly aliases for parameters.
42+
constraints: Symbolic constraints between parameters.
43+
calculator: Active calculator used for computations.
44+
fitter: Active fitter/minimizer driver.
45+
"""
46+
2747
_calculator = CalculatorFactory.create_calculator('cryspy')
2848

2949
def __init__(self, project) -> None:
50+
"""Create a new Analysis instance bound to a project.
51+
52+
Args:
53+
project: The project that owns models and experiments.
54+
"""
3055
self.project = project
3156
self.aliases = Aliases()
3257
self.constraints = Constraints()
@@ -79,6 +104,9 @@ def _get_params_as_dataframe(
79104
return dataframe
80105

81106
def show_all_params(self) -> None:
107+
"""Print a table with all parameters for sample models and
108+
experiments.
109+
"""
82110
sample_models_params = self.project.sample_models.parameters
83111
experiments_params = self.project.experiments.parameters
84112

@@ -126,6 +154,9 @@ def show_all_params(self) -> None:
126154
)
127155

128156
def show_fittable_params(self) -> None:
157+
"""Print a table with parameters that can be included in
158+
fitting.
159+
"""
129160
sample_models_params = self.project.sample_models.fittable_parameters
130161
experiments_params = self.project.experiments.fittable_parameters
131162

@@ -177,6 +208,9 @@ def show_fittable_params(self) -> None:
177208
)
178209

179210
def show_free_params(self) -> None:
211+
"""Print a table with only currently-free (varying)
212+
parameters.
213+
"""
180214
sample_models_params = self.project.sample_models.free_parameters
181215
experiments_params = self.project.experiments.free_parameters
182216
free_params = sample_models_params + experiments_params
@@ -225,6 +259,13 @@ def show_free_params(self) -> None:
225259
)
226260

227261
def how_to_access_parameters(self) -> None:
262+
"""Show Python access paths and CIF unique IDs for all
263+
parameters.
264+
265+
The output explains how to reference specific parameters in code
266+
and which unique identifiers are used when creating CIF-based
267+
constraints.
268+
"""
228269
sample_models_params = self.project.sample_models.parameters
229270
experiments_params = self.project.experiments.parameters
230271
all_params = {
@@ -289,19 +330,31 @@ def how_to_access_parameters(self) -> None:
289330
)
290331

291332
def show_current_calculator(self) -> None:
333+
"""Print the name of the currently selected calculator
334+
engine.
335+
"""
292336
print(paragraph('Current calculator'))
293337
print(self.current_calculator)
294338

295339
@staticmethod
296340
def show_supported_calculators() -> None:
341+
"""Print a table of available calculator backends on this
342+
system.
343+
"""
297344
CalculatorFactory.show_supported_calculators()
298345

299346
@property
300347
def current_calculator(self) -> str:
348+
"""The key/name of the active calculator backend."""
301349
return self._calculator_key
302350

303351
@current_calculator.setter
304352
def current_calculator(self, calculator_name: str) -> None:
353+
"""Switch to a different calculator backend.
354+
355+
Args:
356+
calculator_name: Calculator key to use (e.g. 'cryspy').
357+
"""
305358
calculator = CalculatorFactory.create_calculator(calculator_name)
306359
if calculator is None:
307360
return
@@ -311,29 +364,53 @@ def current_calculator(self, calculator_name: str) -> None:
311364
print(self.current_calculator)
312365

313366
def show_current_minimizer(self) -> None:
367+
"""Print the name of the currently selected minimizer."""
314368
print(paragraph('Current minimizer'))
315369
print(self.current_minimizer)
316370

317371
@staticmethod
318372
def show_available_minimizers() -> None:
373+
"""Print a table of available minimizer drivers on this
374+
system.
375+
"""
319376
MinimizerFactory.show_available_minimizers()
320377

321378
@property
322379
def current_minimizer(self) -> Optional[str]:
380+
"""The identifier of the active minimizer, if any."""
323381
return self.fitter.selection if self.fitter else None
324382

325383
@current_minimizer.setter
326384
def current_minimizer(self, selection: str) -> None:
385+
"""Switch to a different minimizer implementation.
386+
387+
Args:
388+
selection: Minimizer selection string, e.g.
389+
'lmfit (leastsq)'.
390+
"""
327391
self.fitter = Fitter(selection)
328392
print(paragraph('Current minimizer changed to'))
329393
print(self.current_minimizer)
330394

331395
@property
332396
def fit_mode(self) -> str:
397+
"""Current fitting strategy: either 'single' or 'joint'."""
333398
return self._fit_mode
334399

335400
@fit_mode.setter
336401
def fit_mode(self, strategy: str) -> None:
402+
"""Set the fitting strategy.
403+
404+
When set to 'joint', all experiments get default weights and
405+
are used together in a single optimization.
406+
407+
Args:
408+
strategy: Either 'single' or 'joint'.
409+
410+
Raises:
411+
ValueError: If an unsupported strategy value is
412+
provided.
413+
"""
337414
if strategy not in ['single', 'joint']:
338415
raise ValueError("Fit mode must be either 'single' or 'joint'")
339416
self._fit_mode = strategy
@@ -346,6 +423,9 @@ def fit_mode(self, strategy: str) -> None:
346423
print(self._fit_mode)
347424

348425
def show_available_fit_modes(self) -> None:
426+
"""Print all supported fitting strategies and their
427+
descriptions.
428+
"""
349429
strategies = [
350430
{
351431
'Strategy': 'single',
@@ -374,21 +454,25 @@ def show_available_fit_modes(self) -> None:
374454
)
375455

376456
def show_current_fit_mode(self) -> None:
457+
"""Print the currently active fitting strategy."""
377458
print(paragraph('Current fit mode'))
378459
print(self.fit_mode)
379460

380461
def calculate_pattern(self, expt_name: str) -> None:
381-
"""Calculate the diffraction pattern for a given experiment. The
382-
calculated pattern is stored within the experiment's datastore.
462+
"""Calculate and store the diffraction pattern for an
463+
experiment.
464+
465+
The pattern is stored in the target experiment's datastore.
383466
384467
Args:
385-
expt_name: The name of the experiment.
468+
expt_name: The identifier of the experiment to compute.
386469
"""
387470
experiment = self.project.experiments[expt_name]
388471
sample_models = self.project.sample_models
389472
self.calculator.calculate_pattern(sample_models, experiment)
390473

391474
def show_constraints(self) -> None:
475+
"""Print a table of all user-defined symbolic constraints."""
392476
constraints_dict = dict(self.constraints)
393477

394478
if not self.constraints._items:
@@ -416,6 +500,9 @@ def show_constraints(self) -> None:
416500
)
417501

418502
def apply_constraints(self):
503+
"""Apply the currently defined constraints to the active
504+
project.
505+
"""
419506
if not self.constraints._items:
420507
print(warning('No constraints defined.'))
421508
return
@@ -425,6 +512,14 @@ def apply_constraints(self):
425512
self.constraints_handler.apply()
426513

427514
def fit(self):
515+
"""Execute fitting using the selected mode, calculator and
516+
minimizer.
517+
518+
In 'single' mode, fits each experiment independently. In
519+
'joint' mode, performs a simultaneous fit across experiments
520+
with weights.
521+
Sets :attr:`fit_results` on success.
522+
"""
428523
sample_models = self.project.sample_models
429524
if not sample_models:
430525
print('No sample models found in the project. Cannot run fit.')
@@ -471,11 +566,19 @@ def fit(self):
471566
self.fit_results = self.fitter.results
472567

473568
def as_cif(self):
569+
"""Serialize the analysis section to a CIF string.
570+
571+
Returns:
572+
The analysis section represented as a CIF document string.
573+
"""
474574
from easydiffraction.io.cif.serialize import analysis_to_cif
475575

476576
return analysis_to_cif(self)
477577

478578
def show_as_cif(self) -> None:
579+
"""Render the analysis section as CIF in a formatted console
580+
view.
581+
"""
479582
cif_text: str = self.as_cif()
480583
paragraph_title: str = paragraph('Analysis 🧮 info as cif')
481584
render_cif(cif_text, paragraph_title)

0 commit comments

Comments
 (0)