Top-Level API and Project Structure of the EasyScience-Based Python Libraries for Data Analysis, on the example of EasyDiffraction #29
Replies: 4 comments 3 replies
-
Points of preliminary agreement
Open questions for further discussion
|
Beta Was this translation helpful? Give feedback.
-
|
This shoudl really be split into separate ADRs/discussions:
This way, we can have more specific discussions on concrete topics rather than very long meetings touching everything., |
Beta Was this translation helpful? Give feedback.
-
One more question to discussI'll post it here for now until we split this discussion into smaller threads. While thinking about different types of refinement workflows, I’ve identified several use cases relevant to diffraction. These can be grouped into three broad categories, depending on the refinement strategy: single, combined, and sequential refinements. 1. Single Refinement
2. Combined Refinement
3. Sequential Refinement
Additional ConsiderationsIn all of the above cases, there doesn’t appear to be a strong need to specify an active experiment explicitly before the refinement begins. Instead, the user should only need to specify the type of refinement, which would automatically determine the appropriate strategy behind the scenes. There may be other types of refinement workflows, particularly for other techniques, so it would be great to explore and discuss those as well. |
Beta Was this translation helpful? Give feedback.
-
Next Portion of Agreed Points
Still Open Questions
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Proposed modifications of the names
I propose some minor modifications to the existing names for the data processing workflow steps, library classes, and GUI page titles:
Project&InfoSampleModels(collection) &SampleModel(individual)Measurements(collection) &Measurement(individual)Analysis(calculation, fitting/optimization)Summary(results & reporting)These names seem to be general-purpose and domain-agnostic, making them applicable across various scattering techniques while remaining intuitive for users.
Project vs. Job or Study
Job
Study
Project
Sample Model vs. Model, Sample, or Theory
Model
Sample
Theory
Sample Model
Measurement vs. Experiment
Experiment
Measurement
API example
Abbreviations
Experiment type
pd,scneut,xraycwl,tofunp,pol,lpa,snpExamples
pd-neut-cwl_PdNeutCwlpd-neut-tof_PdNeutTofpd-xray_PdXraysc-neut-cwl_ScNeutCwlsc-neut-tof_ScNeutTofClass name suffix usage in EasyDiffraction
Class name suffixes are used to distinguish between different types of measurements and logically group them within the library. While this naming convention may not strictly follow typical Pythonic style, it enhances clarity and consistency.
The suffixes are intentionally applied to provide clarity in complex hierarchical structures, where various types of measurements and calibrations must be easily distinguished—especially during introspection, auto-completion, and debugging.
For example:
InstrumentCalibrationPdNeutTofvs.InstrumentCalibration_PdNeutTofInstrumentCalibrationPdNeutTofUnp1dvs.InstrumentCalibration_PdNeutTofUnp1dThe use of suffixes clearly separates the measurement type while keeping the rest of the class name structured and understandable.
These suffixes are solely used to indicate the measurement type, ensuring logical grouping without compromising readability.
Top-level package structure
Overview diagram
This is a top-level diagram that illustrates the concept of the
Projectas a container for all other objects involved in the data analysis process. This approach enables a simple and intuitive API for data analysis, as demonstrated in the example script above.The
Projectobject maintains all relationships between its components, ensuring they are consistently connected, while allowing each object to remain independently usable when required.classDiagram namespace easycrystallography { class StructuralModel:::model { id: str space_group: SpaceGroup cell: Cell atom_sites: AtomSites from_cif_file(cif_path: str) from_cif_str(cif: str) as_cif() str } } class Project:::container { info: ProjectInfo sample_models: SampleModels measurements: Measurements analysis: Analysis summary: Summary %% Use the directory rather than a file %% This directory should have at least project.cif (or info.cif? or main.cif?) load(dir_path: str) save_as(dir_path: str) save() reset() as_cif() str } class ProjectInfo:::container { title: str description: str path: str as_cif() str _created: datetime _last_modified: datetime } class SampleModels:::collection { self: list[SampleModel] append(model_id: str) append(cif_path: str) append(cif: str) append(model: SampleModel) remove(model_id: str) show_ids() show_params() as_cif() str _get_params() dict } class SampleModel:::model { show_structure() } class Measurements:::collection { self: list[measurement_cls] append(id: str, type: str) append(id: str, type: str, file_path: str) append(cif_path: str) append(cif: str) append(meas: measurement_cls) remove(meas_id: str) show_ids() show_params() as_cif() str _get_params() dict } class Measurement_PdNeutTof:::measurement { instr_calib: InstrumentCalibration_PdNeutTof } class Measurement_PdNeutCwl:::measurement { instr_setup: InstrumentSetup_PdNeutCwl } class Measurement_PdXrayCwl:::measurement { instr_setup: InstrumentSetup_PdXrayCwl } class Measurement_PdTof:::baseclass { <<abstract>> instr_setup: InstrumentSetup_PdTof peak_profile: PeakProfile_PdTof peak_broad: PeakBroadening_PdTof peak_asym: PeakAsymmetry_PdTof } class Measurement_PdCwl:::baseclass { <<abstract>> instr_calib: InstrumentCalibration_PdCwl peak_profile: PeakProfile_PdCwl peak_broad: PeakBroadening_PdCwl peak_asym: PeakAsymmetry_PdCwl } class Measurement_Pd:::baseclass { <<abstract>> linked_samples: list[LinkedSample] background: Background_Pd meas_data: MeasuredData_Pd calc_data: CalculatedData_Pd show_data_chart() } class Measurement_Sc:::baseclass { <<abstract>> linked_sample: LinkedSample meas_data: MeasuredData_Sc calc_data: CalculatedData_Sc show_data_chart() } class BaseMeasurement:::baseclass { <<abstract>> id: str expt_type: str as_cif() str } class Summary:::container { +as_cif() str +as_html() str +show_report() } %% Relationships %% Project *-- ProjectInfo Project *-- SampleModels Project *-- Measurements Project *-- Analysis Project *-- Summary SampleModels "1" -- "*" SampleModel : contains SampleModel <|-- StructuralModel Measurements "1" -- "*" Measurement_PdNeutTof : contains Measurements "1" -- "*" Measurement_PdNeutCwl : contains Measurements "1" -- "*" Measurement_PdXrayCwl : contains Measurement_PdNeutTof <|-- Measurement_PdTof Measurement_PdNeutCwl <|-- Measurement_PdCwl Measurement_PdXrayCwl <|-- Measurement_PdCwl Measurement_PdTof <|-- Measurement_Pd Measurement_PdCwl <|-- Measurement_Pd Measurement_ScTof <|-- Measurement_Sc Measurement_ScCwl <|-- Measurement_Sc Measurement_Pd <|-- BaseMeasurement Measurement_Sc <|-- BaseMeasurement %%%%%%%%%%%%% %% STYLING %% %%%%%%%%%%%%% %% Abstract Base Classes (BaseMeasurement, Measurement_Pd, etc.) classDef baseclass fill:#6A5ACD,stroke:#333,stroke-width:1px,color:white; %% Containers (Project, ProjectInfo, Summary, Analysis) classDef container fill:#455A64,stroke:#333,stroke-width:1px,color:white; %% Collections (SampleModels, Measurements) classDef collection fill:#607D8B,stroke:#333,stroke-width:1px,color:white; %% Concrete Measurements (PdNeutCwl, PdNeutTof, PdXrayCwl) classDef measurement fill:#4682B4,stroke:#0D47A1,stroke-width:1px,color:white; %% Models (SampleModel, StructuralModel) classDef model fill:#009688,stroke:#004D40,stroke-width:1px,color:white;Analysis step diagram
In this diagram, both Minimizers and Calculators are assumed to use a factory-based approach to create their respective objects.
The minimization module is provided by EasyScience and imported into EasyDiffraction as-is. Potential simplifications or customizations can be considered in the future for tighter integration or improved usability.
The structure of the calculation component can follow a similar pattern to minimization in EasyScience and be reused across different techniques. The actual implementation, however, will be technique-specific.
For the calculation component, the abstract base class (
CalculatorBase) defines the interface for calculator wrappers. Concrete implementations, such asCryspyCalculator, wrap third-party calculation engine APIs. The term Wrapper is intentionally avoided in class names to prevent confusion when using the API, e.g.,proj.analysis.calculator = 'crysfml'instead ofproj.analysis.wrapper = 'crysfml'.classDiagram %%%%%%%%%%%%%%%%%%%%%%%%% %% easyscience package %% %%%%%%%%%%%%%%%%%%%%%%%%% namespace easyscience { class MultiFitter:::fitter { } %% Rename to SingleFitter? class Fitter:::fitter { ... %% Rename to _fitter: fitter_cls? _minimizer: minimizer_cls ... } %% Rename to AvailableFitters? %% Move to FitterFactory class instead? class AvailableMinimizers { ... } %% Rename to AvailableFitter? %% Move to FitterFactory class instead? class AvailableMinimizer { <<dataclass>> } %% Move to FitterFactory class instead? class factory:::factory { factory(...) minimizer_cls } %% Rename to LmfitFitter? class LMFit:::wrapper { } %% Rename to BumpsFitter? class Bumps:::wrapper { } %% Rename to DfolsFitter? class DFO:::wrapper { } %% Rename to FitterBase? class MinimizerBase:::baseclass { <<abstract>> /property/ all_constraints /property/ enum /property/ name _get_method_kwargs(...) _prepare_parameters(...) _fit_function(...) _create_signature(...)$ _error_from_jacobian(...)$ fit_constraints() set_fit_constraint(...) add_fit_constraint(...) remove_fit_constraint(...) evaluate(...) convert_to_pars_obj(...)* fit(...)* supported_methods()$ all_methods()$ convert_to_par_object(...)$ } } %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% easydiffraction package %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% namespace easydiffraction { class Project:::container { } class Analysis:::container { /property/ calculator: DiffractionCalculator /property/ fitter: DiffractionFitter %% "single" (default), "combined", or "sequential" /property/ refinement_mode: str available_calculators() list[str] available_minimizers() list[str] calculate_pattern(meas_id: str) ndarray fit(...) %% Shows all Float parameters that can be fitted (fixed & free) show_refinable_params() %% Shows only those parameters that are free to be fitted show_free_params() %% Shows only those parameters that are fixed and are not allowed to be fitted show_fixed_params() show_calc_chart(meas_id: str) show_meas_vs_calc_chart(meas_id: str) show_fit_results() as_cif() str } %% Fitting class DiffractionFitter:::fitter { fitter: MultiFitter } %%class Minimizer { %% -resid_func(fit_params: FitParameters, meas_data, calculator) ndarray %% -callback_func(fit_params: FitParameters, iter: int, resid_func: ndarray) %% +fit(resid_func, callback_func, fit_params: FitParameters, options: dict) %%} %% Calculation class DiffractionCalculator:::calculator { _calculator: calculator_cls set_calculator(type: str) calculate_hkl(sample_models: SampleModels, experiments: Experiments) ndarray calculate_pattern(sample_models: SampleModels, experiments: Experiments) ndarray } class CalculatorFactory:::factory { create_calculator(type: str) calculator_cls register_calculator(type: str, calculator_cls) available_calculators() list } class CrysfmlCalculator:::wrapper { } class CryspyCalculator:::wrapper { } class PdffitCalculator:::wrapper { } class CalculatorBase:::baseclass { <<abstract>> /property/ name: str calculate_hkl(...)* ndarray calculate_pattern(...)* ndarray } } %%%%%%%%%%%%%%% %% RELATIONS %% %%%%%%%%%%%%%%% %% Major %% Project *-- Analysis : contains Analysis *-- DiffractionFitter : contains Analysis *-- DiffractionCalculator : contains %% Refinement/Fitting/Minimization/Optimization %% DiffractionFitter *-- MultiFitter : contains MultiFitter --|> Fitter : inherit Fitter ..> factory : uses Fitter *-- LMFit : contains Fitter *-- Bumps : contains Fitter *-- DFO : contains factory ..> LMFit : creates factory ..> Bumps : creates factory ..> DFO : creates LMFit --|> MinimizerBase : inherit Bumps --|> MinimizerBase : inherit DFO --|> MinimizerBase : inherit Fitter .. AvailableMinimizers : uses factory .. AvailableMinimizers : uses LMFit .. AvailableMinimizers : uses Bumps .. AvailableMinimizers : uses DFO .. AvailableMinimizers : uses MinimizerBase .. AvailableMinimizers : uses AvailableMinimizers --|> AvailableMinimizer : inherit %% Calculations %% DiffractionCalculator ..> CalculatorFactory : uses DiffractionCalculator *-- CrysfmlCalculator : contains DiffractionCalculator *-- CryspyCalculator : contains DiffractionCalculator *-- PdffitCalculator : contains CalculatorFactory ..> CrysfmlCalculator : creates CalculatorFactory ..> CryspyCalculator : creates CalculatorFactory ..> PdffitCalculator : creates CrysfmlCalculator --|> CalculatorBase : inherit CryspyCalculator --|> CalculatorBase : inherit PdffitCalculator --|> CalculatorBase : inherit %%%%%%%%%%%%% %% STYLING %% %%%%%%%%%%%%% %% Core Containers (Project, Analysis) classDef container fill:#546E7A,stroke:#263238,stroke-width:1px,color:#FFFFFF; %% Diffraction Fitter classDef fitter fill:#FBC02D,stroke:#F57F17,stroke-width:1px,color:#000000; %% Diffraction Calculator (Core Calculation Logic) classDef calculator fill:#FBC02D,stroke:#F57F17,stroke-width:1px,color:#000000; %% Factories (Factory classes in Minimization/Calculation) classDef factory fill:#2E7D32,stroke:#1B5E20,stroke-width:1px,color:#FFFFFF; %% Wrappers (Crysfml, Cryspy, Pdffit Calculators) classDef wrapper fill:#FB8C00,stroke:#E65100,stroke-width:1px,color:#FFFFFF; %% Abstract Base Classes (MinimizerBase, CalculatorBase) classDef baseclass fill:#3949AB,stroke:#1A237E,stroke-width:1px,color:#FFFFFF;Beta Was this translation helpful? Give feedback.
All reactions