diff --git a/docs/dev/adr_display-ux.md b/docs/dev/adr_display-ux.md new file mode 100644 index 000000000..264092e22 --- /dev/null +++ b/docs/dev/adr_display-ux.md @@ -0,0 +1,227 @@ +# ADR: Display UX Facade + +## Status + +Accepted. + +## Context + +The current user-facing display API mixes presentation actions, analysis +reports, and renderer configuration: + +```python +project.display.plotter.plot_meas(expt_name='hrpt') +project.display.plotter.plot_calc(expt_name='hrpt') +project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.plotter.plot_param_correlations() +project.display.plotter.plot_posterior_pairs() +project.display.plotter.plot_param_distribution(param) +project.display.plotter.plot_posterior_predictive(expt_name='hrpt') + +project.analysis.display.free_params() +project.analysis.display.fit_results() +``` + +This has several UX problems: + +- `plot_` is redundant below any display or chart object. +- `plotter` and `tabler` expose backend implementation language to + scientists using notebooks. +- `project.display` and `project.analysis.display` overlap without a + clear user-facing rule. +- `plot_meas`, `plot_calc`, and `plot_meas_vs_calc` force users to + choose a plot state that the project can often infer. +- Bayesian and deterministic chart names are not systematic. +- The existing `project.display` category is serialized to CIF, so it + should not also become a broad transient display facade. + +EasyDiffraction is aimed at scientists, often non-programmers, so the +display API should prioritize discoverability, clear names, and safe +defaults. + +## Decision + +Use `project.display` as the user-facing facade for display actions. +Move serialized renderer settings out of that facade and into a separate +project category named `project.rendering`. + +Renderer settings: + +```python +project.rendering.chart_engine = 'plotly' +project.rendering.table_engine = 'pandas' +project.rendering.show_chart_engines() +project.rendering.show_table_engines() +project.rendering.show_config() +``` + +Suggested CIF names: + +- `_rendering.chart_engine` +- `_rendering.table_engine` + +No legacy loader is required for `_display.plotter_type` or +`_display.tabler_type`. The project is in beta, so this cleanup may +break old project files rather than carrying compatibility code. + +The selected display API is grouped: + +```python +project.display.pattern(expt_name='hrpt') + +project.display.parameters.free() +project.display.parameters.fittable() +project.display.parameters.all() +project.display.parameters.access() +project.display.parameters.cif_uids() + +project.display.fit.results() +project.display.fit.correlations() +project.display.fit.series(param, versus=temperature) + +project.display.posterior.pairs() +project.display.posterior.distribution(param) +project.display.posterior.predictive(expt_name='hrpt') + +project.display.show_pattern_options(expt_name='hrpt') +``` + +`project.analysis.display` is removed from the primary public API. Its +current responsibilities move to clearer homes: + +| Current method | New home | +| ---------------------------- | -------------------------------------------------------------- | +| `all_params()` | `project.display.parameters.all()` | +| `fittable_params()` | `project.display.parameters.fittable()` | +| `free_params()` | `project.display.parameters.free()` | +| `how_to_access_parameters()` | `project.display.parameters.access()` | +| `parameter_cif_uids()` | `project.display.parameters.cif_uids()` | +| `fit_results()` | `project.display.fit.results()` | +| `constraints()` | `project.analysis.constraints.show()` | +| `as_cif()` | `project.analysis.as_cif` and `project.analysis.show_as_cif()` | + +`project.analysis` and `project.info` should follow the same CIF display +pattern as structures and experiments: + +- `as_cif` is a read-only property returning CIF text as a string. +- `show_as_cif()` pretty-prints the CIF text with a header. + +## Pattern Display + +Use `pattern()` as the main experiment chart: + +```python +project.display.pattern(expt_name='hrpt') +project.display.pattern(expt_name='hrpt', x_min=40, x_max=55) +``` + +By default, `pattern()` uses `include='auto'` and displays as much +useful information as the project state supports: + +- measured data if present +- calculated data if a model/calculation is available +- background if defined and relevant +- Bragg ticks if phases/reflections are available +- residual if both measured and calculated data are available and the + experiment type supports a residual panel +- excluded regions if available +- uncertainty bands where posterior predictive data exists + +Specific subsets are selected with `include`: + +```python +project.display.pattern(expt_name='hrpt', include='auto') +project.display.pattern(expt_name='hrpt', include='measured') +project.display.pattern(expt_name='hrpt', include='calculated') +project.display.pattern( + expt_name='hrpt', + include=('measured', 'calculated', 'background', 'residual', 'bragg'), +) +``` + +`include` was chosen over alternatives: + +| Name | Reason not selected | +| ------------- | ----------------------------------------------- | +| `layers` | Sounds graphical rather than user intent. | +| `components` | Precise, but longer. | +| `content` | Too broad. | +| `view` | Better for presets than arbitrary combinations. | +| `series` | Does not fit residual rows or Bragg ticks well. | +| boolean flags | Explicit, but scales poorly. | + +Add discovery for supported pattern content: + +```python +project.display.show_pattern_options(expt_name='hrpt') +``` + +The table should show option name, description, availability for the +experiment, whether `include='auto'` includes it, and the reason an +option is unavailable. + +Initial option names: + +- `auto` +- `measured` +- `calculated` +- `background` +- `residual` +- `bragg` +- `excluded` +- `uncertainty` + +`uncertainty` should be implemented immediately where posterior +predictive data exists. It should be unavailable, with a clear reason, +when no posterior predictive data is present. + +## Deterministic And Bayesian Consistency + +Use these naming rules: + +- `pattern()` shows the current point-estimate experiment view. +- `fit.results()` reports the latest fit result. +- `fit.correlations()` shows parameter relationships from the latest + fit. +- `fit.series(param, versus=...)` shows fitted parameter values across a + sequence of fit results or experiments. +- `posterior.*` names are used only when posterior samples are required. + +## Rejected Alternatives + +Flat display facade: + +```python +project.display.pattern(expt_name='hrpt') +project.display.parameters(scope='free') +project.display.fit_results() +project.display.correlations() +project.display.parameter_series(param, versus=temperature) +project.display.posterior_pairs() +project.display.posterior_distribution(param) +project.display.posterior_predictive(expt_name='hrpt') +``` + +This is shorter but would make `project.display` grow into a long flat +list. + +Separate `charts` and `tables` namespaces were also rejected because +users should not need to decide the output type before asking for +information. Some outputs may render as a chart or a table depending on +backend and state. + +Separate `measured()` and `calculated()` methods were rejected because +they duplicate `pattern(..., include=...)`. + +## Consequences + +- The main display workflow becomes more discoverable through grouped + namespaces and tab completion. +- Renderer configuration becomes clearly separate from display actions. +- Existing tutorials and public API docs must be updated to the selected + API. +- Constraints remain owned by the analysis constraints category. +- There is no legacy CIF compatibility path for `_display.plotter_type` + or `_display.tabler_type`. +- `project.analysis` and `project.info` need CIF access cleanup for + consistency with structure and experiment objects. diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 48a411619..85fc0ad4e 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -879,15 +879,16 @@ project = ed.Project(name='my_project') It owns and coordinates all components: -| Property | Type | Description | -| --------------------- | ------------- | ---------------------------------------- | -| `project.info` | `ProjectInfo` | Metadata: name, title, description, path | -| `project.structures` | `Structures` | Collection of structure datablocks | -| `project.experiments` | `Experiments` | Collection of experiment datablocks | -| `project.display` | `Display` | Plot/table engine selection and facades | -| `project.analysis` | `Analysis` | Minimiser, fitting, aliases, constraints | -| `project.summary` | `Summary` | Report generation | -| `project.verbosity` | `str` | Console output level (full/short/silent) | +| Property | Type | Description | +| --------------------- | ---------------- | ---------------------------------------- | +| `project.info` | `ProjectInfo` | Metadata: name, title, description, path | +| `project.structures` | `Structures` | Collection of structure datablocks | +| `project.experiments` | `Experiments` | Collection of experiment datablocks | +| `project.rendering` | `Rendering` | Plot/table engine selection | +| `project.display` | `ProjectDisplay` | Pattern/report facade | +| `project.analysis` | `Analysis` | Minimiser, fitting, aliases, constraints | +| `project.summary` | `Summary` | Report generation | +| `project.verbosity` | `str` | Console output level (full/short/silent) | ### 7.1 Data Flow @@ -922,7 +923,7 @@ project_dir/ ``` `project.cif` carries both the `_project.*` metadata and the -`_display.*` engine preferences (`plotter_type`, `tabler_type`), so a +`_rendering.*` engine preferences (`chart_engine`, `table_engine`), so a saved project re-opens with the same display backends. Per-experiment calculator selection (`_calculation.calculator_type`) lives in each experiment file, and fit configuration (`_fit.minimizer_type`, @@ -1064,7 +1065,7 @@ project.experiments['hrpt'].calculation.calculator_type = 'cryspy' project.analysis.fit.minimizer_type = 'lmfit' # Plot before fitting -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) +project.display.pattern(expt_name='hrpt') # Select free parameters project.structures['lbco'].cell.length_a.free = True @@ -1073,14 +1074,14 @@ project.experiments['hrpt'].instrument.calib_twotheta_offset.free = True project.experiments['hrpt'].background['10'].y.free = True # Inspect free parameters -project.analysis.display.free_params() +project.display.parameters.free() # Fit and show results project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # Plot after fitting -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) +project.display.pattern(expt_name='hrpt') # Save project.save() @@ -1100,11 +1101,11 @@ project.analysis.fit.minimizer.parallel = 0 project.analysis.fit(random_seed=11) # Runtime-only Bayesian summaries and plots -project.analysis.display.fit_results() -project.display.plotter.plot_param_correlations() -project.display.plotter.plot_posterior_pairs() -project.display.plotter.plot_param_distribution(param) -project.display.plotter.plot_posterior_predictive(expt_name='hrpt') +project.display.fit.results() +project.display.fit.correlations() +project.display.posterior.pairs() +project.display.posterior.distribution(param) +project.display.posterior.predictive(expt_name='hrpt') ``` ### 8.5 TOF Experiment (tutorial ed-7) @@ -1225,8 +1226,8 @@ that owns calculator selection — `experiment.calculation.calculator_type` and `experiment.calculation.show_calculator_types()` — instead of the selector being exposed at the experiment owner level. The same pattern -applies to `display` on `Project`, which owns `plotter_type` and -`tabler_type` (see §9.4.1). +applies to `display` on `Project`, which owns `chart_engine` and +`table_engine` (see §9.4.1). **Design decisions:** @@ -1248,14 +1249,14 @@ recognises three distinct selector families. They share a similar `_type` shape so the user can inspect and set them uniformly, but their intent and ownership differ: -| Family | User intent | Examples | CIF | -| ---------------------------------- | ------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -| Backend selector | Pick an execution backend | `fit.minimizer_type`, `calculation.calculator_type`, `display.plotter_type` | `_fit.minimizer_type`, `_calculation.calculator_type`, `_display.plotter_type` | -| Switchable-category impl. selector | Swap a category implementation | `experiment.background_type`, `experiment.peak_profile_type` | category-owned type tag such as `_peak.profile_type` | -| Semantic value selector | Pick a scientific/analysis mode | `fit.mode` | `_fit.mode` | +| Family | User intent | Examples | CIF | +| ---------------------------------- | ------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| Backend selector | Pick an execution backend | `fit.minimizer_type`, `calculation.calculator_type`, `rendering.chart_engine` | `_fit.minimizer_type`, `_calculation.calculator_type`, `_rendering.chart_engine` | +| Switchable-category impl. selector | Swap a category implementation | `experiment.background_type`, `experiment.peak_profile_type` | category-owned type tag such as `_peak.profile_type` | +| Semantic value selector | Pick a scientific/analysis mode | `fit.mode` | `_fit.mode` | Backend selectors and semantic value selectors live on a dedicated -configuration category (`fit`, `calculation`, `display`). Switchable- +configuration category (`fit`, `calculation`, `rendering`). Switchable- category implementation selectors are owned by the host (typically the experiment) because switching them replaces the category instance, as described in §9.3. @@ -1272,8 +1273,8 @@ expt.calculation.show_calculator_types() expt.show_extinction_types() project.analysis.fit.show_minimizer_types() project.analysis.fit.show_modes() -project.display.show_plotter_types() -project.display.show_tabler_types() +project.rendering.show_chart_engines() +project.rendering.show_table_engines() ``` Available calculators are filtered by `engine_imported` (whether the diff --git a/docs/dev/package-structure-full.md b/docs/dev/package-structure-full.md index 8083f39b0..e71f099de 100644 --- a/docs/dev/package-structure-full.md +++ b/docs/dev/package-structure-full.md @@ -402,14 +402,20 @@ │ └── 📄 ascii.py ├── 📁 project │ ├── 📁 categories -│ │ ├── 📁 display +│ │ ├── 📁 rendering │ │ │ ├── 📄 __init__.py │ │ │ ├── 📄 default.py -│ │ │ │ └── 🏷️ class Display +│ │ │ │ └── 🏷️ class Rendering │ │ │ └── 📄 factory.py -│ │ │ └── 🏷️ class DisplayFactory +│ │ │ └── 🏷️ class RenderingFactory │ │ └── 📄 __init__.py │ ├── 📄 __init__.py +│ ├── 📄 display.py +│ │ ├── 🏷️ class PatternOptionStatus +│ │ ├── 🏷️ class ParameterDisplay +│ │ ├── 🏷️ class FitDisplay +│ │ ├── 🏷️ class PosteriorDisplay +│ │ └── 🏷️ class ProjectDisplay │ ├── 📄 project.py │ │ └── 🏷️ class Project │ └── 📄 project_info.py diff --git a/docs/dev/package-structure-short.md b/docs/dev/package-structure-short.md index 9ce283eb5..573142dae 100644 --- a/docs/dev/package-structure-short.md +++ b/docs/dev/package-structure-short.md @@ -198,12 +198,13 @@ │ └── 📄 ascii.py ├── 📁 project │ ├── 📁 categories -│ │ ├── 📁 display +│ │ ├── 📁 rendering │ │ │ ├── 📄 __init__.py │ │ │ ├── 📄 default.py │ │ │ └── 📄 factory.py │ │ └── 📄 __init__.py │ ├── 📄 __init__.py +│ ├── 📄 display.py │ ├── 📄 project.py │ └── 📄 project_info.py ├── 📁 summary diff --git a/docs/dev/plan_display-ux.md b/docs/dev/plan_display-ux.md new file mode 100644 index 000000000..4088ac657 --- /dev/null +++ b/docs/dev/plan_display-ux.md @@ -0,0 +1,238 @@ +# Plan: Display UX Facade + +## Goal + +Implement the display UX approach described in +`docs/dev/adr_display-ux.md`. + +The end-user API should become: + +```python +project.display.pattern(expt_name='hrpt') + +project.display.parameters.free() +project.display.parameters.fittable() +project.display.parameters.all() +project.display.parameters.access() +project.display.parameters.cif_uids() + +project.display.fit.results() +project.display.fit.correlations() +project.display.fit.series(param, versus=temperature) + +project.display.posterior.pairs() +project.display.posterior.distribution(param) +project.display.posterior.predictive(expt_name='hrpt') + +project.display.show_pattern_options(expt_name='hrpt') +``` + +Renderer configuration should move to: + +```python +project.rendering.chart_engine = 'plotly' +project.rendering.table_engine = 'pandas' +project.rendering.show_chart_engines() +project.rendering.show_table_engines() +project.rendering.show_config() +``` + +## Branch + +Use implementation branch: + +```text +feature/display-ux +``` + +## Decisions + +- Use grouped display namespaces under `project.display`. +- Use `project.display.fit.series(param, versus=...)` for sequential fit + parameter plots. +- Use `pattern(..., include='auto')` as the default experiment chart. +- Use `include` rather than `layers`, `components`, `content`, `view`, + `series`, or boolean flags. +- Do not add `project.display.constraints()`. +- Move constraint reporting to `project.analysis.constraints.show()`. +- Rename the serialized project display category to `rendering`. +- Do not add legacy CIF loading for `_display.plotter_type` or + `_display.tabler_type`. +- Standardize CIF display helpers on `project.analysis` and + `project.info`: `as_cif` should be a read-only property returning CIF + text, and `show_as_cif()` should pretty-print CIF text with a header. +- Implement `include='uncertainty'` immediately where posterior + predictive data exists. +- No compatibility aliases or deprecation warnings are required unless + release policy separately requires them. + +## Likely Files To Change + +- `src/easydiffraction/project/project.py` +- `src/easydiffraction/project/project_info.py` +- `src/easydiffraction/project/categories/display/default.py` +- `src/easydiffraction/project/categories/display/factory.py` +- `src/easydiffraction/project/categories/display/__init__.py` +- new `src/easydiffraction/project/categories/rendering/...` +- `src/easydiffraction/display/plotting.py` +- `src/easydiffraction/display/tables.py` +- `src/easydiffraction/analysis/analysis.py` +- `src/easydiffraction/analysis/categories/constraints/default.py` +- `src/easydiffraction/__main__.py` +- `docs/dev/architecture.md` +- `docs/docs/user-guide/**/*.md` +- `docs/docs/tutorials/*.py` +- tests under `tests/unit/easydiffraction/` +- tests under `tests/integration/fitting/` + +Do not edit generated tutorial notebooks directly. Update tutorial `.py` +files and regenerate notebooks in Phase 2 if required. + +## Resolved Questions + +- Legacy CIF `_display.plotter_type` and `_display.tabler_type` do not + need to load into `project.rendering`. No legacy code is required. +- `project.analysis` and `project.info` need consistency with structure + and experiment objects: `as_cif` should be a read-only property that + returns CIF text, and `show_as_cif()` should pretty-print CIF text + with a header. +- `include='uncertainty'` should be implemented immediately where + posterior predictive data exists. + +## Phase 1 - Implementation + +Do not create or run tests in Phase 1 unless explicitly requested. Every +completed Phase 1 implementation step must be staged with explicit paths +and committed locally before moving to the next implementation step or +the Phase 1 review gate. Use atomic commits, inspect the worktree before +each commit, and stage only the files changed for that step. + +- [x] Rename the serialized project display category to rendering. + - Move or recreate the category package as + `src/easydiffraction/project/categories/rendering/`. + - Rename user-facing settings from `plotter_type` and `tabler_type` to + `chart_engine` and `table_engine`. + - Add `show_chart_engines()`, `show_table_engines()`, and + `show_config()`. + - Update CIF names to `_rendering.chart_engine` and + `_rendering.table_engine`. + - Do not add legacy loading for `_display.plotter_type` or + `_display.tabler_type`. + +- [x] Add the new `project.display` facade. + - Add a facade object that is not the serialized rendering category. + - Add `pattern(...)` and `show_pattern_options(...)`. + - Add `parameters`, `fit`, and `posterior` namespace objects. + +- [x] Implement `pattern(..., include='auto')`. + - Replace common user-facing calls to `plot_meas`, `plot_calc`, and + `plot_meas_vs_calc` with one state-aware method. + - Support explicit includes for `measured`, `calculated`, + `background`, `residual`, `bragg`, `excluded`, and `uncertainty` + where data is available. + - Implement `uncertainty` immediately for experiments with posterior + predictive data. + - Render a clear warning or error when a requested include is not + available. + +- [x] Move parameter table displays under `project.display.parameters`. + - Implement `all()`, `fittable()`, `free()`, `access()`, and + `cif_uids()`. + - Remove the primary public need for `project.analysis.display`. + +- [x] Move fit displays under `project.display.fit`. + - Implement `results()`. + - Implement `correlations()`. + - Implement `series(param, versus=...)`. + +- [x] Move Bayesian displays under `project.display.posterior`. + - Implement `pairs()`. + - Implement `distribution(param)`. + - Implement `predictive(expt_name=...)`. + +- [x] Move constraint reporting to + `project.analysis.constraints.show()`. + - Do not add `project.display.constraints()`. + +- [x] Standardize CIF display helpers. + - Convert `project.analysis.as_cif()` to a read-only + `project.analysis.as_cif` property. + - Ensure `project.analysis.show_as_cif()` pretty-prints CIF text with + a header. + - Convert `project.info.as_cif()` to a read-only `project.info.as_cif` + property. + - Ensure `project.info.show_as_cif()` pretty-prints CIF text with a + header. + +- [x] Update docs, tutorials, and architecture text. + - Replace old public display examples with the selected API. + - Update `docs/dev/architecture.md`. + - Update tutorial `.py` files only; regenerate notebooks in Phase 2 if + required. + +- [x] Stop at the Phase 1 review gate. + - Summarize changed files and open questions. + - Suggest next verification commands. + - Wait for user approval before Phase 2. + +Suggested Phase 1 commit messages: + +```text +Rename display settings category to rendering +Add grouped display facade +Implement state-aware pattern display +Move analysis display reports to display facade +Standardize CIF display helpers +Update display UX documentation +``` + +## Phase 2 - Verification + +After Phase 1 is reviewed and approved: + +- [x] Add or update unit tests for the rendering category. +- [x] Add or update unit tests for the display facade namespaces. +- [x] Add or update plotting integration tests for `pattern(...)`. +- [x] Add or update analysis display integration tests for parameter and + fit report methods. +- [x] Regenerate tutorial notebooks if tutorial `.py` files changed. +- [x] Run formatting and checks. +- [x] Run unit tests. +- [x] Run integration tests. +- [x] Run script tests. + +Verification commands: + +```sh +pixi run notebook-prepare +pixi run fix +pixi run check +pixi run unit-tests +pixi run integration-tests +pixi run script-tests +``` + +Run `pixi run notebook-prepare` only if tutorial `.py` files changed. +Run `pixi run integration-tests` only after the relevant unit and +focused integration tests are passing. + +## Suggested Commit Message + +```text +Plan display UX facade implementation +``` + +## Suggested Pull Request + +Title: + +```text +Improve chart and table display API +``` + +Description: + +This change makes display commands easier to discover and use in +notebooks. Experiment patterns, parameter tables, fit reports, and +Bayesian plots are grouped under `project.display`, while renderer +settings move to `project.rendering`. diff --git a/docs/docs/tutorials/ed-1.ipynb b/docs/docs/tutorials/ed-1.ipynb index 53635a4c0..f108a808b 100644 --- a/docs/docs/tutorials/ed-1.ipynb +++ b/docs/docs/tutorials/ed-1.ipynb @@ -167,7 +167,7 @@ "outputs": [], "source": [ "# Show fit results summary\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -178,7 +178,7 @@ "outputs": [], "source": [ "# Show parameter correlations\n", - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -234,7 +234,7 @@ "outputs": [], "source": [ "# Show fit results summary\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -245,7 +245,7 @@ "outputs": [], "source": [ "# Show parameter correlations\n", - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -267,7 +267,7 @@ "outputs": [], "source": [ "# Plot measured vs. calculated diffraction patterns\n", - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] } ], diff --git a/docs/docs/tutorials/ed-1.py b/docs/docs/tutorials/ed-1.py index 0e2a5e553..e6312d4ac 100644 --- a/docs/docs/tutorials/ed-1.py +++ b/docs/docs/tutorials/ed-1.py @@ -61,11 +61,11 @@ # %% # Show fit results summary -project.analysis.display.fit_results() +project.display.fit.results() # %% # Show parameter correlations -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% [markdown] # ## Step 5: Perform Analysis (with constraints) @@ -95,11 +95,11 @@ # %% # Show fit results summary -project.analysis.display.fit_results() +project.display.fit.results() # %% # Show parameter correlations -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% # Show defined experiment names @@ -107,4 +107,4 @@ # %% # Plot measured vs. calculated diffraction patterns -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') diff --git a/docs/docs/tutorials/ed-10.ipynb b/docs/docs/tutorials/ed-10.ipynb index 1e14322b2..2b23af884 100644 --- a/docs/docs/tutorials/ed-10.ipynb +++ b/docs/docs/tutorials/ed-10.ipynb @@ -207,8 +207,8 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()\n", - "project.display.plotter.plot_param_correlations(threshold=0.75)" + "project.display.fit.results()\n", + "project.display.fit.correlations(threshold=0.75)" ] }, { @@ -226,7 +226,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='pdf', show_residual=True)" + "project.display.pattern(expt_name='pdf')" ] } ], diff --git a/docs/docs/tutorials/ed-10.py b/docs/docs/tutorials/ed-10.py index 08f33f0ce..9fb0b9c61 100644 --- a/docs/docs/tutorials/ed-10.py +++ b/docs/docs/tutorials/ed-10.py @@ -82,11 +82,11 @@ # %% project.analysis.fit() -project.analysis.display.fit_results() -project.display.plotter.plot_param_correlations(threshold=0.75) +project.display.fit.results() +project.display.fit.correlations(threshold=0.75) # %% [markdown] # ## Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='pdf', show_residual=True) +project.display.pattern(expt_name='pdf') diff --git a/docs/docs/tutorials/ed-11.ipynb b/docs/docs/tutorials/ed-11.ipynb index ff9669a26..963894d86 100644 --- a/docs/docs/tutorials/ed-11.ipynb +++ b/docs/docs/tutorials/ed-11.ipynb @@ -82,8 +82,8 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.show_supported_engines()\n", - "project.display.plotter.show_current_engine()" + "project.rendering.show_chart_engines()\n", + "project.rendering.show_config()" ] }, { @@ -94,7 +94,7 @@ "outputs": [], "source": [ "# Set global plot range for plots\n", - "project.display.plotter.x_max = 40" + "project.rendering.plotter.x_max = 40" ] }, { @@ -238,8 +238,8 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()\n", - "project.display.plotter.plot_param_correlations()" + "project.display.fit.results()\n", + "project.display.fit.correlations()" ] }, { @@ -257,7 +257,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='nomad', show_residual=False)" + "project.display.pattern(expt_name='nomad', include=('measured', 'calculated'))" ] } ], diff --git a/docs/docs/tutorials/ed-11.py b/docs/docs/tutorials/ed-11.py index a48d2fd68..e4a1ec991 100644 --- a/docs/docs/tutorials/ed-11.py +++ b/docs/docs/tutorials/ed-11.py @@ -21,12 +21,12 @@ # ## Set Plotting Engine # %% -project.display.plotter.show_supported_engines() -project.display.plotter.show_current_engine() +project.rendering.show_chart_engines() +project.rendering.show_config() # %% # Set global plot range for plots -project.display.plotter.x_max = 40 +project.rendering.plotter.x_max = 40 # %% [markdown] # ## Add Structure @@ -94,11 +94,11 @@ # %% project.analysis.fit() -project.analysis.display.fit_results() -project.display.plotter.plot_param_correlations() +project.display.fit.results() +project.display.fit.correlations() # %% [markdown] # ## Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='nomad', show_residual=False) +project.display.pattern(expt_name='nomad', include=('measured', 'calculated')) diff --git a/docs/docs/tutorials/ed-12.ipynb b/docs/docs/tutorials/ed-12.ipynb index 77e648e0b..176ab1475 100644 --- a/docs/docs/tutorials/ed-12.ipynb +++ b/docs/docs/tutorials/ed-12.ipynb @@ -87,7 +87,7 @@ "source": [ "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", "# line below to explicitly set the engine to the required one.\n", - "# project.display.plotter.engine = 'plotly'" + "# project.rendering.chart_engine = 'plotly'" ] }, { @@ -98,8 +98,8 @@ "outputs": [], "source": [ "# Set global plot range for plots\n", - "project.display.plotter.x_min = 2.0\n", - "project.display.plotter.x_max = 30.0" + "project.rendering.plotter.x_min = 2.0\n", + "project.rendering.plotter.x_max = 30.0" ] }, { @@ -278,8 +278,8 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()\n", - "project.display.plotter.plot_param_correlations()" + "project.display.fit.results()\n", + "project.display.fit.correlations()" ] }, { @@ -297,7 +297,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='xray_pdf')" + "project.display.pattern(expt_name='xray_pdf')" ] } ], diff --git a/docs/docs/tutorials/ed-12.py b/docs/docs/tutorials/ed-12.py index 78c722ced..090f948b6 100644 --- a/docs/docs/tutorials/ed-12.py +++ b/docs/docs/tutorials/ed-12.py @@ -26,12 +26,12 @@ # %% # Keep the auto-selected engine. Alternatively, you can uncomment the # line below to explicitly set the engine to the required one. -# project.display.plotter.engine = 'plotly' +# project.rendering.chart_engine = 'plotly' # %% # Set global plot range for plots -project.display.plotter.x_min = 2.0 -project.display.plotter.x_max = 30.0 +project.rendering.plotter.x_min = 2.0 +project.rendering.plotter.x_max = 30.0 # %% [markdown] # ## Add Structure @@ -113,11 +113,11 @@ # %% project.analysis.fit() -project.analysis.display.fit_results() -project.display.plotter.plot_param_correlations() +project.display.fit.results() +project.display.fit.correlations() # %% [markdown] # ## Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='xray_pdf') +project.display.pattern(expt_name='xray_pdf') diff --git a/docs/docs/tutorials/ed-13.ipynb b/docs/docs/tutorials/ed-13.ipynb index 69eae9aa9..616c536c5 100644 --- a/docs/docs/tutorials/ed-13.ipynb +++ b/docs/docs/tutorials/ed-13.ipynb @@ -266,8 +266,8 @@ "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#measured-data-category)\n", "for more details about the measured data and its format.\n", "\n", - "To visualize the measured data, we can use the `plot_meas` method of\n", - "the project." + "To visualize the measured data, we can use the `pattern` method of\n", + "the project's `display` facade with `include='measured'`." ] }, { @@ -277,7 +277,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_1.display.plotter.plot_meas(expt_name='sim_si')" + "project_1.display.pattern(expt_name='sim_si', include='measured')" ] }, { @@ -330,8 +330,8 @@ "metadata": {}, "source": [ "To visualize the effect of excluding the high TOF region, we can plot\n", - "the measured data again. The excluded region will be omitted from the\n", - "plot and is not used in the fitting process." + "the measured data again. The excluded region will be highlighted on\n", + "the plot and is not used in the fitting process." ] }, { @@ -341,7 +341,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_1.display.plotter.plot_meas(expt_name='sim_si')" + "project_1.display.pattern(expt_name='sim_si', include=('measured', 'excluded'))" ] }, { @@ -979,7 +979,7 @@ "#### Show Free Parameters\n", "\n", "We can check which parameters are free to be refined by calling the\n", - "`free_params` method of the `analysis.display` object of the project." + "`free` method of the `display.parameters` object of the project." ] }, { @@ -1002,7 +1002,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_1.analysis.display.free_params()" + "project_1.display.parameters.free()" ] }, { @@ -1016,8 +1016,8 @@ "diffraction pattern with the calculated diffraction pattern based on\n", "the initial parameters of the structure and the instrument. This\n", "provides an indication of how well the initial parameters match the\n", - "measured data. The `plot_meas_vs_calc` method of the project allows\n", - "this comparison." + "measured data. The `pattern` method of the project's `display`\n", + "facade allows this comparison." ] }, { @@ -1027,7 +1027,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_1.display.plotter.plot_meas_vs_calc(expt_name='sim_si')" + "project_1.display.pattern(expt_name='sim_si')" ] }, { @@ -1059,7 +1059,7 @@ "outputs": [], "source": [ "project_1.analysis.fit()\n", - "project_1.analysis.display.fit_results()" + "project_1.display.fit.results()" ] }, { @@ -1101,7 +1101,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_1.display.plotter.plot_meas_vs_calc(expt_name='sim_si')" + "project_1.display.pattern(expt_name='sim_si')" ] }, { @@ -1132,9 +1132,9 @@ "`quad` terms were not part of the data reduction and are therefore set\n", "to 0 by default.\n", "\n", - "The `plot_meas_vs_calc` method of the project allows us to plot the\n", - "measured and calculated diffraction patterns in the d-spacing axis by\n", - "setting the `d_spacing` parameter to `True`." + "The `pattern` method of the project's `display` facade allows us to\n", + "plot the measured and calculated diffraction patterns in the\n", + "d-spacing axis by setting `x='d_spacing'`." ] }, { @@ -1144,7 +1144,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_1.display.plotter.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing')" + "project_1.display.pattern(expt_name='sim_si', x='d_spacing')" ] }, { @@ -1348,12 +1348,12 @@ "metadata": {}, "outputs": [], "source": [ - "project_2.display.plotter.plot_meas(expt_name='sim_lbco')\n", + "project_2.display.pattern(expt_name='sim_lbco', include='measured')\n", "\n", "project_2.experiments['sim_lbco'].excluded_regions.create(id='1', start=0, end=55000)\n", "project_2.experiments['sim_lbco'].excluded_regions.create(id='2', start=105500, end=200000)\n", "\n", - "project_2.display.plotter.plot_meas(expt_name='sim_lbco')" + "project_2.display.pattern(expt_name='sim_lbco', include=('measured', 'excluded'))" ] }, { @@ -1938,10 +1938,10 @@ "id": "146", "metadata": {}, "source": [ - "Use the `plot_meas_vs_calc` method of the project to visualize the\n", - "measured and calculated diffraction patterns before fitting. Then, use\n", - "the `fit` method of the `analysis` object of the project to perform\n", - "the fitting process." + "Use the `pattern` method of the project's `display` facade to\n", + "visualize the measured and calculated diffraction patterns before\n", + "fitting. Then, use the `fit` method of the `analysis` object of the\n", + "project to perform the fitting process." ] }, { @@ -1959,10 +1959,10 @@ "metadata": {}, "outputs": [], "source": [ - "project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco')\n", + "project_2.display.pattern(expt_name='sim_lbco')\n", "\n", "project_2.analysis.fit()\n", - "project_2.analysis.display.fit_results()" + "project_2.display.fit.results()" ] }, { @@ -2036,7 +2036,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco')" + "project_2.display.pattern(expt_name='sim_lbco')" ] }, { @@ -2090,9 +2090,9 @@ "project_2.structures['lbco'].cell.length_a.free = True\n", "\n", "project_2.analysis.fit()\n", - "project_2.analysis.display.fit_results()\n", + "project_2.display.fit.results()\n", "\n", - "project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco')" + "project_2.display.pattern(expt_name='sim_lbco')" ] }, { @@ -2132,8 +2132,8 @@ "id": "163", "metadata": {}, "source": [ - "Use the `plot_meas_vs_calc` method of the project and set the\n", - "`d_spacing` parameter to `True`." + "Use the `pattern` method of the project's `display` facade and set\n", + "`x='d_spacing'`." ] }, { @@ -2151,7 +2151,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing')" + "project_2.display.pattern(expt_name='sim_lbco', x='d_spacing')" ] }, { @@ -2180,9 +2180,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_2.display.plotter.plot_meas_vs_calc(\n", - " expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40\n", - ")" + "project_2.display.pattern(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40)" ] }, { @@ -2242,11 +2240,9 @@ "project_2.experiments['sim_lbco'].peak.exp_rise_alpha_1.free = True\n", "\n", "project_2.analysis.fit()\n", - "project_2.analysis.display.fit_results()\n", + "project_2.display.fit.results()\n", "\n", - "project_2.display.plotter.plot_meas_vs_calc(\n", - " expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40\n", - ")" + "project_2.display.pattern(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40)" ] }, { @@ -2296,9 +2292,7 @@ "metadata": {}, "outputs": [], "source": [ - "project_2.display.plotter.plot_meas_vs_calc(\n", - " expt_name='sim_lbco', x='d_spacing', x_min=1.53, x_max=1.7\n", - ")" + "project_2.display.pattern(expt_name='sim_lbco', x='d_spacing', x_min=1.53, x_max=1.7)" ] }, { @@ -2420,10 +2414,8 @@ "metadata": {}, "outputs": [], "source": [ - "project_1.display.plotter.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing', x_min=1, x_max=1.7)\n", - "project_2.display.plotter.plot_meas_vs_calc(\n", - " expt_name='sim_lbco', x='d_spacing', x_min=1, x_max=1.7\n", - ")" + "project_1.display.pattern(expt_name='sim_si', x='d_spacing', x_min=1, x_max=1.7)\n", + "project_2.display.pattern(expt_name='sim_lbco', x='d_spacing', x_min=1, x_max=1.7)" ] }, { @@ -2520,10 +2512,10 @@ "id": "196", "metadata": {}, "source": [ - "You can use the `plot_meas_vs_calc` method of the project to visualize\n", - "the patterns. Then, set the `free` attribute of the `scale` parameter\n", - "of the Si phase to `True` to allow the fitting process to adjust the\n", - "scale factor." + "You can use the `pattern` method of the project's `display` facade to\n", + "visualize the patterns. Then, set the `free` attribute of the `scale`\n", + "parameter of the Si phase to `True` to allow the fitting process to\n", + "adjust the scale factor." ] }, { @@ -2544,7 +2536,7 @@ "# Before optimizing the parameters, we can visualize the measured\n", "# diffraction pattern and the calculated diffraction pattern based on\n", "# the two phases: LBCO and Si.\n", - "project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco')\n", + "project_2.display.pattern(expt_name='sim_lbco')\n", "\n", "# As you can see, the calculated pattern is now the sum of both phases,\n", "# and Si peaks are visible in the calculated pattern. However, their\n", @@ -2554,14 +2546,14 @@ "\n", "# Now we can perform the fit with both phases included.\n", "project_2.analysis.fit()\n", - "project_2.analysis.display.fit_results()\n", + "project_2.display.fit.results()\n", "\n", "# Let's plot the measured diffraction pattern and the calculated\n", "# diffraction pattern both for the full range and for a zoomed-in region\n", "# around the previously unexplained peak near 95,000 μs. The calculated\n", "# pattern will be the sum of the two phases.\n", - "project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco')\n", - "project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco', x_min=88000, x_max=101000)" + "project_2.display.pattern(expt_name='sim_lbco')\n", + "project_2.display.pattern(expt_name='sim_lbco', x_min=88000, x_max=101000)" ] }, { @@ -2665,7 +2657,7 @@ ], "metadata": { "jupytext": { - "cell_metadata_filter": "title,tags,-all", + "cell_metadata_filter": "tags,title,-all", "main_language": "python", "notebook_metadata_filter": "-all" } diff --git a/docs/docs/tutorials/ed-13.py b/docs/docs/tutorials/ed-13.py index e89f42887..436c13b93 100644 --- a/docs/docs/tutorials/ed-13.py +++ b/docs/docs/tutorials/ed-13.py @@ -144,11 +144,11 @@ # [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#measured-data-category) # for more details about the measured data and its format. # -# To visualize the measured data, we can use the `plot_meas` method of -# the project. +# To visualize the measured data, we can use the `pattern` method of +# the project's `display` facade with `include='measured'`. # %% -project_1.display.plotter.plot_meas(expt_name='sim_si') +project_1.display.pattern(expt_name='sim_si', include='measured') # %% [markdown] # If you zoom in on the highest TOF peak (around 120,000 μs), you will @@ -179,11 +179,11 @@ # %% [markdown] # To visualize the effect of excluding the high TOF region, we can plot -# the measured data again. The excluded region will be omitted from the -# plot and is not used in the fitting process. +# the measured data again. The excluded region will be highlighted on +# the plot and is not used in the fitting process. # %% -project_1.display.plotter.plot_meas(expt_name='sim_si') +project_1.display.pattern(expt_name='sim_si', include=('measured', 'excluded')) # %% [markdown] # #### Set Instrument Parameters @@ -583,7 +583,7 @@ # #### Show Free Parameters # # We can check which parameters are free to be refined by calling the -# `free_params` method of the `analysis.display` object of the project. +# `free` method of the `display.parameters` object of the project. # %% [markdown] tags=["doc-link"] # 📖 See @@ -594,7 +594,7 @@ # - show only free parameters of the project. # %% -project_1.analysis.display.free_params() +project_1.display.parameters.free() # %% [markdown] # #### Visualize Diffraction Patterns @@ -603,11 +603,11 @@ # diffraction pattern with the calculated diffraction pattern based on # the initial parameters of the structure and the instrument. This # provides an indication of how well the initial parameters match the -# measured data. The `plot_meas_vs_calc` method of the project allows -# this comparison. +# measured data. The `pattern` method of the project's `display` +# facade allows this comparison. # %% -project_1.display.plotter.plot_meas_vs_calc(expt_name='sim_si') +project_1.display.pattern(expt_name='sim_si') # %% [markdown] # #### Run Fitting @@ -622,7 +622,7 @@ # %% project_1.analysis.fit() -project_1.analysis.display.fit_results() +project_1.display.fit.results() # %% [markdown] # #### Check Fit Results @@ -647,7 +647,7 @@ # pattern is now based on the refined parameters. # %% -project_1.display.plotter.plot_meas_vs_calc(expt_name='sim_si') +project_1.display.pattern(expt_name='sim_si') # %% [markdown] # #### TOF vs d-spacing @@ -673,12 +673,12 @@ # `quad` terms were not part of the data reduction and are therefore set # to 0 by default. # -# The `plot_meas_vs_calc` method of the project allows us to plot the -# measured and calculated diffraction patterns in the d-spacing axis by -# setting the `d_spacing` parameter to `True`. +# The `pattern` method of the project's `display` facade allows us to +# plot the measured and calculated diffraction patterns in the +# d-spacing axis by setting `x='d_spacing'`. # %% -project_1.display.plotter.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing') +project_1.display.pattern(expt_name='sim_si', x='d_spacing') # %% [markdown] # As you can see, the calculated diffraction pattern now matches the @@ -789,12 +789,12 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.display.plotter.plot_meas(expt_name='sim_lbco') +project_2.display.pattern(expt_name='sim_lbco', include='measured') project_2.experiments['sim_lbco'].excluded_regions.create(id='1', start=0, end=55000) project_2.experiments['sim_lbco'].excluded_regions.create(id='2', start=105500, end=200000) -project_2.display.plotter.plot_meas(expt_name='sim_lbco') +project_2.display.pattern(expt_name='sim_lbco', include=('measured', 'excluded')) # %% [markdown] # #### Exercise 2.2: Set Instrument Parameters @@ -1106,19 +1106,19 @@ # **Hint:** # %% [markdown] tags=["dmsc-school-hint"] -# Use the `plot_meas_vs_calc` method of the project to visualize the -# measured and calculated diffraction patterns before fitting. Then, use -# the `fit` method of the `analysis` object of the project to perform -# the fitting process. +# Use the `pattern` method of the project's `display` facade to +# visualize the measured and calculated diffraction patterns before +# fitting. Then, use the `fit` method of the `analysis` object of the +# project to perform the fitting process. # %% [markdown] # **Solution:** # %% tags=["solution", "hide-input"] -project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco') +project_2.display.pattern(expt_name='sim_lbco') project_2.analysis.fit() -project_2.analysis.display.fit_results() +project_2.display.fit.results() # %% [markdown] # #### Exercise 5.3: Find the Misfit in the Fit @@ -1160,7 +1160,7 @@ # peak positions. # %% tags=["solution", "hide-input"] -project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco') +project_2.display.pattern(expt_name='sim_lbco') # %% [markdown] # #### Exercise 5.4: Refine the LBCO Lattice Parameter @@ -1187,9 +1187,9 @@ project_2.structures['lbco'].cell.length_a.free = True project_2.analysis.fit() -project_2.analysis.display.fit_results() +project_2.display.fit.results() -project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco') +project_2.display.pattern(expt_name='sim_lbco') # %% [markdown] # One of the main goals of this study was to refine the lattice @@ -1209,14 +1209,14 @@ # **Hint:** # %% [markdown] tags=["dmsc-school-hint"] -# Use the `plot_meas_vs_calc` method of the project and set the -# `d_spacing` parameter to `True`. +# Use the `pattern` method of the project's `display` facade and set +# `x='d_spacing'`. # %% [markdown] # **Solution:** # %% tags=["solution", "hide-input"] -project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing') +project_2.display.pattern(expt_name='sim_lbco', x='d_spacing') # %% [markdown] # #### Exercise 5.6: Refine the Peak Profile Parameters @@ -1233,9 +1233,7 @@ # perfectly describe the peak at about 1.38 Å, as can be seen below: # %% -project_2.display.plotter.plot_meas_vs_calc( - expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40 -) +project_2.display.pattern(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40) # %% [markdown] # The peak profile parameters are determined based on both the @@ -1268,11 +1266,9 @@ project_2.experiments['sim_lbco'].peak.exp_rise_alpha_1.free = True project_2.analysis.fit() -project_2.analysis.display.fit_results() +project_2.display.fit.results() -project_2.display.plotter.plot_meas_vs_calc( - expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40 -) +project_2.display.pattern(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40) # %% [markdown] # #### Exercise 5.7: Find Undefined Features @@ -1295,9 +1291,7 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.display.plotter.plot_meas_vs_calc( - expt_name='sim_lbco', x='d_spacing', x_min=1.53, x_max=1.7 -) +project_2.display.pattern(expt_name='sim_lbco', x='d_spacing', x_min=1.53, x_max=1.7) # %% [markdown] # #### Exercise 5.8: Identify the Cause of the Unexplained Peaks @@ -1362,10 +1356,8 @@ # confirm this hypothesis. # %% tags=["solution", "hide-input"] -project_1.display.plotter.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing', x_min=1, x_max=1.7) -project_2.display.plotter.plot_meas_vs_calc( - expt_name='sim_lbco', x='d_spacing', x_min=1, x_max=1.7 -) +project_1.display.pattern(expt_name='sim_si', x='d_spacing', x_min=1, x_max=1.7) +project_2.display.pattern(expt_name='sim_lbco', x='d_spacing', x_min=1, x_max=1.7) # %% [markdown] # #### Exercise 5.10: Create a Second Structure – Si as Impurity @@ -1420,10 +1412,10 @@ # **Hint:** # %% [markdown] tags=["dmsc-school-hint"] -# You can use the `plot_meas_vs_calc` method of the project to visualize -# the patterns. Then, set the `free` attribute of the `scale` parameter -# of the Si phase to `True` to allow the fitting process to adjust the -# scale factor. +# You can use the `pattern` method of the project's `display` facade to +# visualize the patterns. Then, set the `free` attribute of the `scale` +# parameter of the Si phase to `True` to allow the fitting process to +# adjust the scale factor. # %% [markdown] # **Solution:** @@ -1432,7 +1424,7 @@ # Before optimizing the parameters, we can visualize the measured # diffraction pattern and the calculated diffraction pattern based on # the two phases: LBCO and Si. -project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco') +project_2.display.pattern(expt_name='sim_lbco') # As you can see, the calculated pattern is now the sum of both phases, # and Si peaks are visible in the calculated pattern. However, their @@ -1442,14 +1434,14 @@ # Now we can perform the fit with both phases included. project_2.analysis.fit() -project_2.analysis.display.fit_results() +project_2.display.fit.results() # Let's plot the measured diffraction pattern and the calculated # diffraction pattern both for the full range and for a zoomed-in region # around the previously unexplained peak near 95,000 μs. The calculated # pattern will be the sum of the two phases. -project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco') -project_2.display.plotter.plot_meas_vs_calc(expt_name='sim_lbco', x_min=88000, x_max=101000) +project_2.display.pattern(expt_name='sim_lbco') +project_2.display.pattern(expt_name='sim_lbco', x_min=88000, x_max=101000) # %% [markdown] # All previously unexplained peaks are now accounted for in the pattern, diff --git a/docs/docs/tutorials/ed-14.ipynb b/docs/docs/tutorials/ed-14.ipynb index 75e6805b0..48665fa53 100644 --- a/docs/docs/tutorials/ed-14.ipynb +++ b/docs/docs/tutorials/ed-14.ipynb @@ -254,7 +254,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='heidi')" + "project.display.pattern(expt_name='heidi')" ] }, { @@ -307,7 +307,7 @@ "outputs": [], "source": [ "# Show fit results summary\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -337,7 +337,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='heidi')" + "project.display.pattern(expt_name='heidi')" ] }, { @@ -405,7 +405,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -415,7 +415,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -425,7 +425,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='heidi')" + "project.display.pattern(expt_name='heidi')" ] }, { diff --git a/docs/docs/tutorials/ed-14.py b/docs/docs/tutorials/ed-14.py index c29d3eb88..3ff5f25f0 100644 --- a/docs/docs/tutorials/ed-14.py +++ b/docs/docs/tutorials/ed-14.py @@ -85,7 +85,7 @@ # ## Step 4: Perform Analysis I (ADP iso) # %% -project.display.plotter.plot_meas_vs_calc(expt_name='heidi') +project.display.pattern(expt_name='heidi') # %% structure.atom_sites['O1'].fract_x.free = True @@ -110,7 +110,7 @@ # %% # Show fit results summary -project.analysis.display.fit_results() +project.display.fit.results() # %% structure.show_as_cif() @@ -119,7 +119,7 @@ project.experiments.show_names() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='heidi') +project.display.pattern(expt_name='heidi') # %% [markdown] # ## Step 5: Perform Analysis (ADP aniso) @@ -147,13 +147,13 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='heidi') +project.display.pattern(expt_name='heidi') # %% structure.show_as_cif() diff --git a/docs/docs/tutorials/ed-15.ipynb b/docs/docs/tutorials/ed-15.ipynb index d1ece4eb6..ab93cd536 100644 --- a/docs/docs/tutorials/ed-15.ipynb +++ b/docs/docs/tutorials/ed-15.ipynb @@ -208,7 +208,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='senju')" + "project.display.pattern(expt_name='senju')" ] }, { @@ -262,7 +262,7 @@ "outputs": [], "source": [ "# Show fit results summary\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -292,7 +292,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='senju')" + "project.display.pattern(expt_name='senju')" ] }, { @@ -344,7 +344,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -364,7 +364,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -374,7 +374,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -384,7 +384,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='senju')" + "project.display.pattern(expt_name='senju')" ] }, { diff --git a/docs/docs/tutorials/ed-15.py b/docs/docs/tutorials/ed-15.py index c198fc234..659af7214 100644 --- a/docs/docs/tutorials/ed-15.py +++ b/docs/docs/tutorials/ed-15.py @@ -67,7 +67,7 @@ # ## Step 4: Perform Analysis I (ADP iso) # %% -project.display.plotter.plot_meas_vs_calc(expt_name='senju') +project.display.pattern(expt_name='senju') # %% experiment.linked_crystal.scale.free = True @@ -86,7 +86,7 @@ # %% # Show fit results summary -project.analysis.display.fit_results() +project.display.fit.results() # %% structure.show_as_cif() @@ -95,7 +95,7 @@ project.experiments.show_names() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='senju') +project.display.pattern(expt_name='senju') # %% [markdown] # ## Step 5: Perform Analysis (ADP aniso) @@ -114,19 +114,19 @@ structure.show_as_cif() # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='senju') +project.display.pattern(expt_name='senju') # %% structure.show_as_cif() diff --git a/docs/docs/tutorials/ed-16.ipynb b/docs/docs/tutorials/ed-16.ipynb index f7a839dae..5da587e9d 100644 --- a/docs/docs/tutorials/ed-16.ipynb +++ b/docs/docs/tutorials/ed-16.ipynb @@ -455,7 +455,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd')" + "project.display.pattern(expt_name='sepd')" ] }, { @@ -465,7 +465,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='nomad')" + "project.display.pattern(expt_name='nomad')" ] }, { @@ -551,7 +551,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -570,8 +570,8 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()\n", - "project.display.plotter.plot_param_correlations()" + "project.display.fit.results()\n", + "project.display.fit.correlations()" ] }, { @@ -589,7 +589,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd')" + "project.display.pattern(expt_name='sepd')" ] }, { @@ -599,7 +599,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='nomad')" + "project.display.pattern(expt_name='nomad')" ] } ], diff --git a/docs/docs/tutorials/ed-16.py b/docs/docs/tutorials/ed-16.py index 91d48b1e6..bde9af470 100644 --- a/docs/docs/tutorials/ed-16.py +++ b/docs/docs/tutorials/ed-16.py @@ -190,10 +190,10 @@ # #### Plot Measured vs Calculated (Before Fit) # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd') +project.display.pattern(expt_name='sepd') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='nomad') +project.display.pattern(expt_name='nomad') # %% [markdown] # #### Set Fitting Parameters @@ -231,21 +231,21 @@ # #### Show Free Parameters # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() -project.display.plotter.plot_param_correlations() +project.display.fit.results() +project.display.fit.correlations() # %% [markdown] # #### Plot Measured vs Calculated (After Fit) # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd') +project.display.pattern(expt_name='sepd') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='nomad') +project.display.pattern(expt_name='nomad') diff --git a/docs/docs/tutorials/ed-17.ipynb b/docs/docs/tutorials/ed-17.ipynb index d2ee917d1..d370ca274 100644 --- a/docs/docs/tutorials/ed-17.ipynb +++ b/docs/docs/tutorials/ed-17.ipynb @@ -569,7 +569,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -587,7 +587,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='d20')" + "project.display.pattern(expt_name='d20')" ] }, { @@ -678,7 +678,7 @@ "outputs": [], "source": [ "project.apply_params_from_csv(row_index=0)\n", - "project.display.plotter.plot_meas_vs_calc(expt_name='d20')" + "project.display.pattern(expt_name='d20')" ] }, { @@ -698,7 +698,7 @@ "outputs": [], "source": [ "project.apply_params_from_csv(row_index=-1)\n", - "project.display.plotter.plot_meas_vs_calc(expt_name='d20')" + "project.display.pattern(expt_name='d20')" ] }, { @@ -736,9 +736,9 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_series(structure.cell.length_a, versus=temperature)\n", - "project.display.plotter.plot_param_series(structure.cell.length_b, versus=temperature)\n", - "project.display.plotter.plot_param_series(structure.cell.length_c, versus=temperature)" + "project.display.fit.series(structure.cell.length_a, versus=temperature)\n", + "project.display.fit.series(structure.cell.length_b, versus=temperature)\n", + "project.display.fit.series(structure.cell.length_c, versus=temperature)" ] }, { @@ -756,23 +756,23 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['Co1'].adp_iso,\n", " versus=temperature,\n", ")\n", - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['Si'].adp_iso,\n", " versus=temperature,\n", ")\n", - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['O1'].adp_iso,\n", " versus=temperature,\n", ")\n", - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['O2'].adp_iso,\n", " versus=temperature,\n", ")\n", - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['O3'].adp_iso,\n", " versus=temperature,\n", ")" @@ -793,23 +793,23 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['Co2'].fract_x,\n", " versus=temperature,\n", ")\n", - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['Co2'].fract_z,\n", " versus=temperature,\n", ")\n", - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['O1'].fract_z,\n", " versus=temperature,\n", ")\n", - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['O2'].fract_z,\n", " versus=temperature,\n", ")\n", - "project.display.plotter.plot_param_series(\n", + "project.display.fit.series(\n", " structure.atom_sites['O3'].fract_z,\n", " versus=temperature,\n", ")" diff --git a/docs/docs/tutorials/ed-17.py b/docs/docs/tutorials/ed-17.py index f929cd47c..c1dc3a79a 100644 --- a/docs/docs/tutorials/ed-17.py +++ b/docs/docs/tutorials/ed-17.py @@ -280,13 +280,13 @@ # #### Show parameter correlations # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% [markdown] # #### Compare measured and calculated patterns for the first fit. # %% -project.display.plotter.plot_meas_vs_calc(expt_name='d20') +project.display.pattern(expt_name='d20') # %% [markdown] # #### Run Sequential Fitting @@ -329,7 +329,7 @@ def extract_diffrn(file_path): # %% project.apply_params_from_csv(row_index=0) -project.display.plotter.plot_meas_vs_calc(expt_name='d20') +project.display.pattern(expt_name='d20') # %% [markdown] # @@ -337,7 +337,7 @@ def extract_diffrn(file_path): # %% project.apply_params_from_csv(row_index=-1) -project.display.plotter.plot_meas_vs_calc(expt_name='d20') +project.display.pattern(expt_name='d20') # %% [markdown] # #### Plot Parameter Evolution @@ -351,31 +351,31 @@ def extract_diffrn(file_path): # Plot unit cell parameters vs. temperature. # %% -project.display.plotter.plot_param_series(structure.cell.length_a, versus=temperature) -project.display.plotter.plot_param_series(structure.cell.length_b, versus=temperature) -project.display.plotter.plot_param_series(structure.cell.length_c, versus=temperature) +project.display.fit.series(structure.cell.length_a, versus=temperature) +project.display.fit.series(structure.cell.length_b, versus=temperature) +project.display.fit.series(structure.cell.length_c, versus=temperature) # %% [markdown] # Plot isotropic displacement parameters vs. temperature. # %% -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['Co1'].adp_iso, versus=temperature, ) -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['Si'].adp_iso, versus=temperature, ) -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['O1'].adp_iso, versus=temperature, ) -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['O2'].adp_iso, versus=temperature, ) -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['O3'].adp_iso, versus=temperature, ) @@ -384,23 +384,23 @@ def extract_diffrn(file_path): # Plot selected fractional coordinates vs. temperature. # %% -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['Co2'].fract_x, versus=temperature, ) -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['Co2'].fract_z, versus=temperature, ) -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['O1'].fract_z, versus=temperature, ) -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['O2'].fract_z, versus=temperature, ) -project.display.plotter.plot_param_series( +project.display.fit.series( structure.atom_sites['O3'].fract_z, versus=temperature, ) diff --git a/docs/docs/tutorials/ed-18.ipynb b/docs/docs/tutorials/ed-18.ipynb index 47a25154a..c1200010a 100644 --- a/docs/docs/tutorials/ed-18.ipynb +++ b/docs/docs/tutorials/ed-18.ipynb @@ -144,7 +144,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -154,7 +154,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -164,7 +164,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] } ], diff --git a/docs/docs/tutorials/ed-18.py b/docs/docs/tutorials/ed-18.py index fb44972fa..7c4fff9fc 100644 --- a/docs/docs/tutorials/ed-18.py +++ b/docs/docs/tutorials/ed-18.py @@ -47,10 +47,10 @@ # ## Show Results # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') diff --git a/docs/docs/tutorials/ed-2.ipynb b/docs/docs/tutorials/ed-2.ipynb index 4a52b569c..927087e9d 100644 --- a/docs/docs/tutorials/ed-2.ipynb +++ b/docs/docs/tutorials/ed-2.ipynb @@ -345,7 +345,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -355,7 +355,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -365,7 +365,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -429,7 +429,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -439,7 +439,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -449,7 +449,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -497,7 +497,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -507,7 +507,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -517,7 +517,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] } ], diff --git a/docs/docs/tutorials/ed-2.py b/docs/docs/tutorials/ed-2.py index fa4918c05..c2772ba6b 100644 --- a/docs/docs/tutorials/ed-2.py +++ b/docs/docs/tutorials/ed-2.py @@ -162,13 +162,13 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% [markdown] # ## Step 5: Perform Analysis (with constraints) @@ -199,13 +199,13 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% [markdown] # ## Step 6: Switch calculator engine @@ -220,10 +220,10 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') diff --git a/docs/docs/tutorials/ed-20.ipynb b/docs/docs/tutorials/ed-20.ipynb index b9a47a281..4ad0e666c 100644 --- a/docs/docs/tutorials/ed-20.ipynb +++ b/docs/docs/tutorials/ed-20.ipynb @@ -515,7 +515,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='expt_s2')" + "project.display.pattern(expt_name='expt_s2')" ] }, { @@ -525,7 +525,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='expt_n2')" + "project.display.pattern(expt_name='expt_n2')" ] }, { @@ -576,7 +576,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fittable_params()" + "project.display.parameters.fittable()" ] }, { @@ -731,8 +731,8 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()\n", - "project.display.plotter.plot_param_correlations()" + "project.display.fit.results()\n", + "project.display.fit.correlations()" ] }, { @@ -752,7 +752,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='expt_s2')" + "project.display.pattern(expt_name='expt_s2')" ] }, { @@ -762,7 +762,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='expt_n2')" + "project.display.pattern(expt_name='expt_n2')" ] }, { @@ -780,7 +780,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(\n", + "project.display.pattern(\n", " expt_name='expt_s2',\n", " x='d_spacing',\n", " x_min=2.08,\n", @@ -795,7 +795,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(\n", + "project.display.pattern(\n", " expt_name='expt_n2',\n", " x='d_spacing',\n", " x_min=2.08,\n", diff --git a/docs/docs/tutorials/ed-20.py b/docs/docs/tutorials/ed-20.py index 0ac55fdbf..e48e860ba 100644 --- a/docs/docs/tutorials/ed-20.py +++ b/docs/docs/tutorials/ed-20.py @@ -245,10 +245,10 @@ # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='expt_s2') +project.display.pattern(expt_name='expt_s2') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='expt_n2') +project.display.pattern(expt_name='expt_n2') # %% [markdown] # ## Perform Analysis @@ -268,7 +268,7 @@ # #### Set Free Parameters # %% -project.analysis.display.fittable_params() +project.display.parameters.fittable() # %% ferrite.atom_sites['Fe'].adp_iso.free = True @@ -347,8 +347,8 @@ # Show fit results and parameter correlations. # %% -project.analysis.display.fit_results() -project.display.plotter.plot_param_correlations() +project.display.fit.results() +project.display.fit.correlations() # %% [markdown] # #### Plot Measured vs Calculated @@ -356,16 +356,16 @@ # Show full range in TOF. # %% -project.display.plotter.plot_meas_vs_calc(expt_name='expt_s2') +project.display.pattern(expt_name='expt_s2') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='expt_n2') +project.display.pattern(expt_name='expt_n2') # %% [markdown] # Show selected peaks in d-spacing. # %% -project.display.plotter.plot_meas_vs_calc( +project.display.pattern( expt_name='expt_s2', x='d_spacing', x_min=2.08, @@ -373,7 +373,7 @@ ) # %% -project.display.plotter.plot_meas_vs_calc( +project.display.pattern( expt_name='expt_n2', x='d_spacing', x_min=2.08, diff --git a/docs/docs/tutorials/ed-21.ipynb b/docs/docs/tutorials/ed-21.ipynb index 405bc8589..33f77196f 100644 --- a/docs/docs/tutorials/ed-21.ipynb +++ b/docs/docs/tutorials/ed-21.ipynb @@ -218,7 +218,9 @@ "id": "14", "metadata": {}, "source": [ - "#### Download the Measured Data" + "Download the measured data from the repository. Alternatively, you\n", + "could use your own data file by providing the path to it instead of\n", + "downloading from the repository." ] }, { @@ -236,7 +238,8 @@ "id": "16", "metadata": {}, "source": [ - "#### Create the Experiment Object" + "Create the experiment object and specify the sample form, beam mode,\n", + "and radiation probe." ] }, { @@ -270,7 +273,25 @@ "id": "19", "metadata": {}, "source": [ - "#### Set Instrument and Peak-Profile Parameters\n", + "Link the structural phase to the experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_phases.create(id='lbco', scale=9.1351)" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "Set instrument and peak profile parameters.\n", "\n", "These values provide the initial instrument description for the local\n", "refinement. Later, a subset of them will be refined." @@ -279,7 +300,7 @@ { "cell_type": "code", "execution_count": null, - "id": "20", + "id": "22", "metadata": {}, "outputs": [], "source": [ @@ -290,7 +311,7 @@ { "cell_type": "code", "execution_count": null, - "id": "21", + "id": "23", "metadata": {}, "outputs": [], "source": [ @@ -302,10 +323,10 @@ }, { "cell_type": "markdown", - "id": "22", + "id": "24", "metadata": {}, "source": [ - "#### Add Background Points and Excluded Regions\n", + "Add background points and excluded regions.\n", "\n", "The line-segment background is defined by a few anchor points. We also\n", "exclude regions that are not intended to contribute to the fit." @@ -314,7 +335,7 @@ { "cell_type": "code", "execution_count": null, - "id": "23", + "id": "25", "metadata": {}, "outputs": [], "source": [ @@ -327,7 +348,7 @@ { "cell_type": "code", "execution_count": null, - "id": "24", + "id": "26", "metadata": {}, "outputs": [], "source": [ @@ -335,24 +356,6 @@ "experiment.excluded_regions.create(id='2', start=100, end=180)" ] }, - { - "cell_type": "markdown", - "id": "25", - "metadata": {}, - "source": [ - "#### Link the Structural Phase to the Experiment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "26", - "metadata": {}, - "outputs": [], - "source": [ - "experiment.linked_phases.create(id='lbco', scale=9.1351)" - ] - }, { "cell_type": "markdown", "id": "27", @@ -442,7 +445,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -463,7 +466,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -473,7 +476,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -491,8 +494,10 @@ "on the current parameter value and expands them by a chosen multiple of\n", "the reported uncertainty.\n", "\n", - "Default `multiplier` is 8 to give a wide range for the sampler to\n", - "explore, but here we use 3 to speed up the tutorial." + "The default `multiplier` is 4. If the local refinement is very tight,\n", + "or if you expect a broader posterior, increase it explicitly.\n", + "\n", + "Show unset fit bounds before setting them from the local refinement uncertainties." ] }, { @@ -502,23 +507,34 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" + ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "Set fit bounds for all free parameters using the default multiplier of\n", + "4. In this tutorial that means the posterior pair plot will later\n", + "refer to a `±4 × uncertainty` region in its title. To use a different\n", + "region, pass another value, for example `multiplier=6`." ] }, { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "41", "metadata": {}, "outputs": [], "source": [ "for param in project.free_parameters:\n", - " param.set_fit_bounds_from_uncertainty(multiplier=3.5)" + " param.set_fit_bounds_from_uncertainty()" ] }, { "cell_type": "markdown", - "id": "41", + "id": "42", "metadata": {}, "source": [ "Displaying the free parameters again is a convenient way to confirm\n", @@ -529,16 +545,16 @@ { "cell_type": "code", "execution_count": null, - "id": "42", + "id": "43", "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { "cell_type": "markdown", - "id": "43", + "id": "44", "metadata": {}, "source": [ "## Step 6: Configure and Run DREAM\n", @@ -550,20 +566,21 @@ "of steps (`steps`) and often the burn-in (`burn`) as well. When\n", "needed, the DREAM API also lets you tune how chains are initialized\n", "through the `init` setting. Other sampler settings such as `thin` and\n", - "`pop` can be adjusted as well. The current EasyDiffraction default\n", - "also uses `parallel=0`, which tells BUMPS DREAM to use all available\n", - "CPUs for population evaluations.\n", + "`pop` can be adjusted as well. The current EasyDiffraction defaults\n", + "use `steps=3000`, `init='lhs'`, and `parallel=0`, which tells\n", + "BUMPS-DREAM to use all available CPUs for population evaluations.\n", "\n", - "The default `steps` value is 1000, and real analyses often need more\n", - "to achieve good convergence and posterior sampling. Here we use a much\n", - "smaller value to keep the tutorial fast, but this is not recommended\n", - "for production analysis." + "The `burn` setting is auto-resolved when left unset. With the default\n", + "`steps=3000` this gives `burn=600`, but if you override `steps` and\n", + "keep `burn=None`, the effective burn-in is recomputed automatically.\n", + "Here we use a much smaller step count to keep the tutorial fast, but\n", + "this is not recommended for production analysis." ] }, { "cell_type": "code", "execution_count": null, - "id": "44", + "id": "45", "metadata": {}, "outputs": [], "source": [ @@ -573,7 +590,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "46", "metadata": {}, "outputs": [], "source": [ @@ -583,17 +600,17 @@ { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "47", "metadata": {}, "outputs": [], "source": [ - "project.analysis.fit.minimizer.steps = 100 # lower than the default 1000" + "project.analysis.fit.minimizer.steps = 300 # lower than the default 3000" ] }, { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "48", "metadata": {}, "outputs": [], "source": [ @@ -602,7 +619,7 @@ }, { "cell_type": "markdown", - "id": "48", + "id": "49", "metadata": {}, "source": [ "## Step 7: Inspect Bayesian Results\n", @@ -615,16 +632,16 @@ { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "50", "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { "cell_type": "markdown", - "id": "50", + "id": "51", "metadata": {}, "source": [ "The correlation and posterior-pair plots are complementary:\n", @@ -632,32 +649,35 @@ "- `plot_param_correlations` summarizes pairwise structure in a compact\n", " matrix.\n", "- `plot_posterior_pairs` shows marginal densities on the diagonal and\n", - " posterior contours off-diagonal." + " posterior contours off-diagonal. In this tutorial its title also\n", + " reminds you that the display region follows the `±4 × uncertainty`\n", + " bounds defined above, while numeric subplot ranges are omitted to\n", + " keep the grid readable." ] }, { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "52", "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { "cell_type": "code", "execution_count": null, - "id": "52", + "id": "53", "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_posterior_pairs()" + "project.display.posterior.pairs()" ] }, { "cell_type": "markdown", - "id": "53", + "id": "54", "metadata": {}, "source": [ "The one-dimensional posterior distributions below make it easier to\n", @@ -668,17 +688,17 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "55", "metadata": {}, "outputs": [], "source": [ "for param in project.free_parameters:\n", - " project.display.plotter.plot_param_distribution(param)" + " project.display.posterior.distribution(param)" ] }, { "cell_type": "markdown", - "id": "55", + "id": "56", "metadata": {}, "source": [ "Finally, the posterior predictive plot propagates the sampled parameter\n", @@ -690,16 +710,16 @@ { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "57", "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_posterior_predictive(expt_name='hrpt')" + "project.display.posterior.predictive(expt_name='hrpt')" ] }, { "cell_type": "markdown", - "id": "57", + "id": "58", "metadata": {}, "source": [ "A final zoomed measured-vs-calculated plot is useful for checking how\n", @@ -710,11 +730,15 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "59", "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_posterior_predictive(expt_name='hrpt', x_min=92, x_max=93)" + "project.display.posterior.predictive(\n", + " expt_name='hrpt',\n", + " x_min=92,\n", + " x_max=93,\n", + ")" ] } ], diff --git a/docs/docs/tutorials/ed-21.py b/docs/docs/tutorials/ed-21.py index d91173c37..e890f55bc 100644 --- a/docs/docs/tutorials/ed-21.py +++ b/docs/docs/tutorials/ed-21.py @@ -213,7 +213,7 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # The correlation plot shows how strongly the fitted parameters move @@ -222,10 +222,10 @@ # region. # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% [markdown] # ## Step 5: Prepare for Bayesian Sampling @@ -244,7 +244,7 @@ # Show unset fit bounds before setting them from the local refinement uncertainties. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # Set fit bounds for all free parameters using the default multiplier of @@ -262,7 +262,7 @@ # sampler. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # ## Step 6: Configure and Run DREAM @@ -304,7 +304,7 @@ # statistics. # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # The correlation and posterior-pair plots are complementary: @@ -318,10 +318,10 @@ # keep the grid readable. # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_posterior_pairs() +project.display.posterior.pairs() # %% [markdown] # The one-dimensional posterior distributions below make it easier to @@ -330,7 +330,7 @@ # %% for param in project.free_parameters: - project.display.plotter.plot_param_distribution(param) + project.display.posterior.distribution(param) # %% [markdown] # Finally, the posterior predictive plot propagates the sampled parameter @@ -339,7 +339,7 @@ # model family explains the data in the region of interest. # %% -project.display.plotter.plot_posterior_predictive(expt_name='hrpt') +project.display.posterior.predictive(expt_name='hrpt') # %% [markdown] # A final zoomed measured-vs-calculated plot is useful for checking how @@ -347,7 +347,7 @@ # after the Bayesian run. # %% -project.display.plotter.plot_posterior_predictive( +project.display.posterior.predictive( expt_name='hrpt', x_min=92, x_max=93, diff --git a/docs/docs/tutorials/ed-22.ipynb b/docs/docs/tutorials/ed-22.ipynb index cc880314e..965e4ed82 100644 --- a/docs/docs/tutorials/ed-22.ipynb +++ b/docs/docs/tutorials/ed-22.ipynb @@ -35,7 +35,16 @@ "distribution with BUMPS-DREAM.\n", "\n", "The example uses constant-wavelength neutron single-crystal diffraction data\n", - "for Tb2TiO7 measured on HEiDi at FRM II." + "for Tb2TiO7 measured on HEiDi at FRM II.\n", + "\n", + "The goal is not only to obtain a good fit, but also to answer Bayesian\n", + "questions such as:\n", + "\n", + "- Which parameter values are most probable?\n", + "- How broad are the credible intervals?\n", + "- Which parameters are strongly correlated?\n", + "- How much uncertainty propagates into the calculated reflection\n", + " intensities?" ] }, { @@ -61,7 +70,11 @@ "id": "4", "metadata": {}, "source": [ - "## Step 1: Create a Project Container" + "## Step 1: Create a Project Container\n", + "\n", + "The project object keeps structures, experiments, fit settings, and\n", + "plotting utilities together in a single place. We will build the full\n", + "workflow inside this object." ] }, { @@ -79,7 +92,12 @@ "id": "6", "metadata": {}, "source": [ - "## Step 2: Build the Structural Model" + "## Step 2: Build the Structural Model\n", + "\n", + "For this example we start from a CIF file describing the Tb2TiO7\n", + "pyrochlore structure. Loading the structure from CIF is convenient\n", + "because it preserves a realistic starting\n", + "model without rebuilding the full structure by hand." ] }, { @@ -117,7 +135,11 @@ "id": "10", "metadata": {}, "source": [ - "## Step 3: Define the Diffraction Experiment" + "## Step 3: Define the Diffraction Experiment\n", + "\n", + "Next we download the measured reflection data, create a neutron\n", + "single-crystal experiment, and configure the crystal link,\n", + "wavelength, and extinction model." ] }, { @@ -156,10 +178,18 @@ "experiment = project.experiments['heidi']" ] }, + { + "cell_type": "markdown", + "id": "14", + "metadata": {}, + "source": [ + "Link the crystal structure to the experiment and set its scale factor." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "14", + "id": "15", "metadata": {}, "outputs": [], "source": [ @@ -167,10 +197,20 @@ "experiment.linked_crystal.scale = 1.0" ] }, + { + "cell_type": "markdown", + "id": "16", + "metadata": {}, + "source": [ + "Set the instrument wavelength and starting extinction parameters.\n", + "These values provide the initial experiment description for the local\n", + "refinement." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "15", + "id": "17", "metadata": {}, "outputs": [], "source": [ @@ -180,7 +220,7 @@ { "cell_type": "code", "execution_count": null, - "id": "16", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -190,16 +230,27 @@ }, { "cell_type": "markdown", - "id": "17", + "id": "19", "metadata": {}, "source": [ - "## Step 4: Run an Initial Local Refinement" + "## Step 4: Run an Initial Local Refinement\n", + "\n", + "Before Bayesian sampling, it is useful to run a deterministic fit. This\n", + "gives us:\n", + "\n", + "- a good point estimate near the best-fit region,\n", + "- uncertainties from the local optimizer,\n", + "- a quick check that the model and experiment are configured\n", + " sensibly.\n", + "\n", + "In this tutorial we refine a small set of structural and extinction\n", + "parameters while keeping occupancies fixed." ] }, { "cell_type": "code", "execution_count": null, - "id": "18", + "id": "20", "metadata": {}, "outputs": [], "source": [ @@ -218,7 +269,7 @@ { "cell_type": "code", "execution_count": null, - "id": "19", + "id": "21", "metadata": {}, "outputs": [], "source": [ @@ -226,10 +277,20 @@ "experiment.extinction.radius.free = True" ] }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "We keep using the default LMFIT Levenberg-Marquardt minimizer as a fast local\n", + "optimizer. Its main purpose here is to provide a stable starting point\n", + "and uncertainty estimates for the Bayesian run." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "20", + "id": "23", "metadata": {}, "outputs": [], "source": [ @@ -239,65 +300,111 @@ { "cell_type": "code", "execution_count": null, - "id": "21", + "id": "24", "metadata": {}, "outputs": [], "source": [ "project.analysis.fit()" ] }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "The fit-results display summarizes the locally refined values and their\n", + "estimated uncertainties." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "22", + "id": "26", "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "The correlation plot shows how strongly the refined parameters move\n", + "together in the local refinement. The measured-vs-calculated plot shows\n", + "how well the refined crystal model reproduces the measured reflection\n", + "intensities." ] }, { "cell_type": "code", "execution_count": null, - "id": "23", + "id": "28", "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { "cell_type": "code", "execution_count": null, - "id": "24", + "id": "29", "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='heidi')" + "project.display.pattern(expt_name='heidi')" ] }, { "cell_type": "markdown", - "id": "25", + "id": "30", "metadata": {}, "source": [ - "## Step 5: Prepare for Bayesian Sampling" + "## Step 5: Prepare for Bayesian Sampling\n", + "\n", + "DREAM requires finite bounds for the free parameters. Instead of\n", + "setting them manually, we derive them from the uncertainties estimated\n", + "in the local refinement.\n", + "\n", + "The helper method `set_fit_bounds_from_uncertainty` centers the bounds\n", + "on the current parameter value and expands them by a chosen multiple of\n", + "the reported uncertainty.\n", + "\n", + "The default `multiplier` is 4. In this single-crystal tutorial we use\n", + "a tighter value of `1.5` to keep the sampling window closer to the\n", + "locally refined solution.\n", + "\n", + "Show unset fit bounds before setting them from the local refinement\n", + "uncertainties." ] }, { "cell_type": "code", "execution_count": null, - "id": "26", + "id": "31", "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "Set fit bounds for all free parameters using `multiplier=1.5`. In this\n", + "tutorial that means the posterior pair plot will later refer to a\n", + "`±1.5 × uncertainty` region in its title. To widen the sampling window,\n", + "increase the multiplier explicitly." ] }, { "cell_type": "code", "execution_count": null, - "id": "27", + "id": "33", "metadata": {}, "outputs": [], "source": [ @@ -305,28 +412,53 @@ " param.set_fit_bounds_from_uncertainty(multiplier=1.5)" ] }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "Displaying the free parameters again is a convenient way to confirm\n", + "that the fit bounds have been assigned as expected before launching the\n", + "sampler." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "28", + "id": "35", "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { "cell_type": "markdown", - "id": "29", + "id": "36", "metadata": {}, "source": [ - "## Step 6: Configure and Run BUMPS-DREAM" + "## Step 6: Configure and Run DREAM\n", + "\n", + "We now switch from the local minimizer to the Bayesian DREAM sampler.\n", + "\n", + "The settings below are intentionally small so the tutorial runs\n", + "quickly. For production analysis you would usually increase the number\n", + "of steps (`steps`) and often the burn-in (`burn`) as well. When\n", + "needed, the DREAM API also lets you tune how chains are initialized\n", + "through the `init` setting. Other sampler settings such as `thin` and\n", + "`pop` can be adjusted as well. The current EasyDiffraction defaults\n", + "use `steps=3000`, `init='lhs'`, and `parallel=0`, which tells\n", + "BUMPS-DREAM to use all available CPUs for population evaluations.\n", + "\n", + "The `burn` setting is auto-resolved when left unset. Here we override\n", + "`steps` with a smaller value to keep the tutorial fast, and the\n", + "effective burn-in is recomputed automatically." ] }, { "cell_type": "code", "execution_count": null, - "id": "30", + "id": "37", "metadata": {}, "outputs": [], "source": [ @@ -336,7 +468,7 @@ { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "38", "metadata": {}, "outputs": [], "source": [ @@ -346,72 +478,120 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "39", "metadata": {}, "outputs": [], "source": [ - "project.analysis.fit.minimizer.steps = 500" + "project.analysis.fit.minimizer.steps = 500 # lower than the default 3000" ] }, { "cell_type": "code", "execution_count": null, - "id": "33", + "id": "40", "metadata": {}, "outputs": [], "source": [ "project.analysis.fit()" ] }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "## Step 7: Inspect Bayesian Results\n", + "\n", + "The fit-results display now includes sampler settings, convergence\n", + "diagnostics, committed parameter values, and posterior summary\n", + "statistics." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "42", "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "The correlation and posterior-pair plots are complementary:\n", + "\n", + "- `plot_param_correlations` summarizes pairwise structure in a compact\n", + " matrix.\n", + "- `plot_posterior_pairs` shows marginal densities on the diagonal and\n", + " posterior contours off-diagonal. In this tutorial its title also\n", + " reminds you that the display region follows the `±1.5 × uncertainty`\n", + " bounds defined above, while numeric subplot ranges are omitted to\n", + " keep the grid readable." ] }, { "cell_type": "code", "execution_count": null, - "id": "35", + "id": "44", "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "45", "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_posterior_pairs()" + "project.display.posterior.pairs()" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "The one-dimensional posterior distributions below make it easier to\n", + "inspect individual parameters in isolation, including asymmetry or\n", + "multimodality." ] }, { "cell_type": "code", "execution_count": null, - "id": "37", + "id": "47", "metadata": {}, "outputs": [], "source": [ "for param in project.free_parameters:\n", - " project.display.plotter.plot_param_distribution(param)" + " project.display.posterior.distribution(param)" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "Finally, the posterior predictive plot propagates the sampled\n", + "parameter uncertainty into the calculated single-crystal reflection\n", + "intensities." ] }, { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "49", "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_posterior_predictive(expt_name='heidi')" + "project.display.posterior.predictive(expt_name='heidi')" ] } ], diff --git a/docs/docs/tutorials/ed-22.py b/docs/docs/tutorials/ed-22.py index aaa727180..bf0f2e19e 100644 --- a/docs/docs/tutorials/ed-22.py +++ b/docs/docs/tutorials/ed-22.py @@ -141,7 +141,7 @@ # estimated uncertainties. # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # The correlation plot shows how strongly the refined parameters move @@ -150,10 +150,10 @@ # intensities. # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_meas_vs_calc(expt_name='heidi') +project.display.pattern(expt_name='heidi') # %% [markdown] # ## Step 5: Prepare for Bayesian Sampling @@ -174,7 +174,7 @@ # uncertainties. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # Set fit bounds for all free parameters using `multiplier=1.5`. In this @@ -192,7 +192,7 @@ # sampler. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # ## Step 6: Configure and Run DREAM @@ -232,7 +232,7 @@ # statistics. # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # The correlation and posterior-pair plots are complementary: @@ -246,10 +246,10 @@ # keep the grid readable. # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% -project.display.plotter.plot_posterior_pairs() +project.display.posterior.pairs() # %% [markdown] # The one-dimensional posterior distributions below make it easier to @@ -258,7 +258,7 @@ # %% for param in project.free_parameters: - project.display.plotter.plot_param_distribution(param) + project.display.posterior.distribution(param) # %% [markdown] # Finally, the posterior predictive plot propagates the sampled @@ -266,4 +266,4 @@ # intensities. # %% -project.display.plotter.plot_posterior_predictive(expt_name='heidi') +project.display.posterior.predictive(expt_name='heidi') diff --git a/docs/docs/tutorials/ed-3.ipynb b/docs/docs/tutorials/ed-3.ipynb index f99298a2f..03fba5148 100644 --- a/docs/docs/tutorials/ed-3.ipynb +++ b/docs/docs/tutorials/ed-3.ipynb @@ -175,7 +175,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.show_supported_engines()" + "project.rendering.show_chart_engines()" ] }, { @@ -193,7 +193,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.show_config()" + "project.rendering.show_config()" ] }, { @@ -492,7 +492,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt', include='measured')" ] }, { @@ -774,7 +774,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt', include='calculated')" ] }, { @@ -792,7 +792,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -802,7 +802,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41)" + "project.display.pattern(expt_name='hrpt', x_min=38, x_max=41)" ] }, { @@ -822,7 +822,7 @@ "metadata": {}, "outputs": [], "source": [ - "# project.analysis.display.all_params()" + "# project.display.parameters.all()" ] }, { @@ -840,7 +840,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fittable_params()" + "project.display.parameters.fittable()" ] }, { @@ -858,7 +858,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -876,7 +876,7 @@ "metadata": {}, "outputs": [], "source": [ - "# project.analysis.display.how_to_access_parameters()" + "# project.display.parameters.access()" ] }, { @@ -1014,7 +1014,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -1033,7 +1033,7 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -1051,7 +1051,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -1061,7 +1061,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41)" + "project.display.pattern(expt_name='hrpt', x_min=38, x_max=41)" ] }, { @@ -1120,7 +1120,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -1139,7 +1139,7 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -1157,7 +1157,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -1167,7 +1167,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41)" + "project.display.pattern(expt_name='hrpt', x_min=38, x_max=41)" ] }, { @@ -1226,7 +1226,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -1245,7 +1245,7 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -1263,7 +1263,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -1273,7 +1273,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41)" + "project.display.pattern(expt_name='hrpt', x_min=38, x_max=41)" ] }, { @@ -1356,7 +1356,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.constraints()" + "project.analysis.constraints.show()" ] }, { @@ -1374,7 +1374,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -1393,7 +1393,7 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -1411,7 +1411,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -1421,7 +1421,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41)" + "project.display.pattern(expt_name='hrpt', x_min=38, x_max=41)" ] }, { @@ -1508,7 +1508,7 @@ }, "outputs": [], "source": [ - "project.analysis.display.constraints()" + "project.analysis.constraints.show()" ] }, { @@ -1544,7 +1544,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -1563,8 +1563,8 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()\n", - "project.display.plotter.plot_param_correlations()" + "project.display.fit.results()\n", + "project.display.fit.correlations()" ] }, { @@ -1582,7 +1582,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -1592,7 +1592,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41)" + "project.display.pattern(expt_name='hrpt', x_min=38, x_max=41)" ] }, { diff --git a/docs/docs/tutorials/ed-3.py b/docs/docs/tutorials/ed-3.py index de1f74c63..271b8e8f0 100644 --- a/docs/docs/tutorials/ed-3.py +++ b/docs/docs/tutorials/ed-3.py @@ -68,13 +68,13 @@ # Show supported plotting engines. # %% -project.display.plotter.show_supported_engines() +project.rendering.show_chart_engines() # %% [markdown] # Show current plotting configuration. # %% -project.display.plotter.show_config() +project.rendering.show_config() # %% [markdown] # ## Step 2: Define Structure @@ -219,7 +219,7 @@ # #### Show Measured Data # %% -project.display.plotter.plot_meas(expt_name='hrpt') +project.display.pattern(expt_name='hrpt', include='measured') # %% [markdown] # #### Set Instrument @@ -328,16 +328,16 @@ # #### Show Calculated Data # %% -project.display.plotter.plot_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt', include='calculated') # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) +project.display.pattern(expt_name='hrpt', x_min=38, x_max=41) # %% [markdown] # #### Show Parameters @@ -345,25 +345,25 @@ # Show all parameters of the project. # %% -# project.analysis.display.all_params() +# project.display.parameters.all() # %% [markdown] # Show all fittable parameters. # %% -project.analysis.display.fittable_params() +project.display.parameters.fittable() # %% [markdown] # Show only free parameters. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # Show how to access parameters in the code. # %% -# project.analysis.display.how_to_access_parameters() +# project.display.parameters.access() # %% [markdown] # #### Set Fit Mode @@ -417,23 +417,23 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) +project.display.pattern(expt_name='hrpt', x_min=38, x_max=41) # %% [markdown] # #### Save Project State @@ -456,23 +456,23 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) +project.display.pattern(expt_name='hrpt', x_min=38, x_max=41) # %% [markdown] # #### Save Project State @@ -495,23 +495,23 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) +project.display.pattern(expt_name='hrpt', x_min=38, x_max=41) # %% [markdown] # #### Save Project State @@ -546,29 +546,29 @@ # Show defined constraints. # %% -project.analysis.display.constraints() +project.analysis.constraints.show() # %% [markdown] # Show free parameters. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) +project.display.pattern(expt_name='hrpt', x_min=38, x_max=41) # %% [markdown] # #### Save Project State @@ -605,7 +605,7 @@ # Show defined constraints. # %% -project.analysis.display.constraints() +project.analysis.constraints.show() # %% [markdown] @@ -618,24 +618,24 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() -project.display.plotter.plot_param_correlations() +project.display.fit.results() +project.display.fit.correlations() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) +project.display.pattern(expt_name='hrpt', x_min=38, x_max=41) # %% [markdown] # #### Save Project State diff --git a/docs/docs/tutorials/ed-4.ipynb b/docs/docs/tutorials/ed-4.ipynb index 3a77ab8bb..bdf0343c4 100644 --- a/docs/docs/tutorials/ed-4.ipynb +++ b/docs/docs/tutorials/ed-4.ipynb @@ -687,7 +687,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='npd', x_min=35.5, x_max=38.3)" + "project.display.pattern(expt_name='npd', x_min=35.5, x_max=38.3)" ] }, { @@ -697,7 +697,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='xrd', x_min=29.0, x_max=30.4)" + "project.display.pattern(expt_name='xrd', x_min=29.0, x_max=30.4)" ] } ], diff --git a/docs/docs/tutorials/ed-4.py b/docs/docs/tutorials/ed-4.py index 8a868277f..a0ea3f623 100644 --- a/docs/docs/tutorials/ed-4.py +++ b/docs/docs/tutorials/ed-4.py @@ -315,7 +315,7 @@ # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='npd', x_min=35.5, x_max=38.3) +project.display.pattern(expt_name='npd', x_min=35.5, x_max=38.3) # %% -project.display.plotter.plot_meas_vs_calc(expt_name='xrd', x_min=29.0, x_max=30.4) +project.display.pattern(expt_name='xrd', x_min=29.0, x_max=30.4) diff --git a/docs/docs/tutorials/ed-5.ipynb b/docs/docs/tutorials/ed-5.ipynb index 02ef6045c..3fa6294b9 100644 --- a/docs/docs/tutorials/ed-5.ipynb +++ b/docs/docs/tutorials/ed-5.ipynb @@ -426,7 +426,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='d20')" + "project.display.pattern(expt_name='d20')" ] }, { @@ -436,7 +436,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='d20', x_min=41, x_max=54)" + "project.display.pattern(expt_name='d20', x_min=41, x_max=54)" ] }, { @@ -507,7 +507,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -582,7 +582,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -592,7 +592,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -610,7 +610,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='d20')" + "project.display.pattern(expt_name='d20')" ] }, { @@ -620,7 +620,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='d20', x_min=42, x_max=52)" + "project.display.pattern(expt_name='d20', x_min=42, x_max=52)" ] }, { diff --git a/docs/docs/tutorials/ed-5.py b/docs/docs/tutorials/ed-5.py index 72e54feb9..a82990a32 100644 --- a/docs/docs/tutorials/ed-5.py +++ b/docs/docs/tutorials/ed-5.py @@ -200,10 +200,10 @@ # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='d20') +project.display.pattern(expt_name='d20') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='d20', x_min=41, x_max=54) +project.display.pattern(expt_name='d20', x_min=41, x_max=54) # %% [markdown] # #### Set Free Parameters @@ -243,7 +243,7 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Set Constraints @@ -274,19 +274,19 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='d20') +project.display.pattern(expt_name='d20') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='d20', x_min=42, x_max=52) +project.display.pattern(expt_name='d20', x_min=42, x_max=52) # %% [markdown] # ## Summary diff --git a/docs/docs/tutorials/ed-6.ipynb b/docs/docs/tutorials/ed-6.ipynb index ae8f5a84d..3c89362d1 100644 --- a/docs/docs/tutorials/ed-6.ipynb +++ b/docs/docs/tutorials/ed-6.ipynb @@ -385,7 +385,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -395,7 +395,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51)" + "project.display.pattern(expt_name='hrpt', x_min=48, x_max=51)" ] }, { @@ -437,7 +437,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -465,7 +465,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -483,7 +483,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -493,7 +493,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51)" + "project.display.pattern(expt_name='hrpt', x_min=48, x_max=51)" ] }, { @@ -537,7 +537,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -565,7 +565,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -583,7 +583,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -593,7 +593,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51)" + "project.display.pattern(expt_name='hrpt', x_min=48, x_max=51)" ] }, { @@ -635,7 +635,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -663,7 +663,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -681,7 +681,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -691,7 +691,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51)" + "project.display.pattern(expt_name='hrpt', x_min=48, x_max=51)" ] }, { @@ -738,7 +738,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -766,7 +766,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -776,7 +776,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -794,7 +794,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt')" + "project.display.pattern(expt_name='hrpt')" ] }, { @@ -804,7 +804,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51)" + "project.display.pattern(expt_name='hrpt', x_min=48, x_max=51)" ] }, { diff --git a/docs/docs/tutorials/ed-6.py b/docs/docs/tutorials/ed-6.py index 341178e61..14d93515e 100644 --- a/docs/docs/tutorials/ed-6.py +++ b/docs/docs/tutorials/ed-6.py @@ -179,10 +179,10 @@ # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51) +project.display.pattern(expt_name='hrpt', x_min=48, x_max=51) # %% [markdown] # ### Perform Fit 1/4 @@ -200,7 +200,7 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting @@ -209,16 +209,16 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51) +project.display.pattern(expt_name='hrpt', x_min=48, x_max=51) # %% [markdown] # ### Perform Fit 2/4 @@ -238,7 +238,7 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting @@ -247,16 +247,16 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51) +project.display.pattern(expt_name='hrpt', x_min=48, x_max=51) # %% [markdown] # ### Perform Fit 3/4 @@ -274,7 +274,7 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting @@ -283,16 +283,16 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51) +project.display.pattern(expt_name='hrpt', x_min=48, x_max=51) # %% [markdown] # ### Perform Fit 4/4 @@ -315,7 +315,7 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting @@ -324,19 +324,19 @@ project.analysis.fit() # %% -project.analysis.display.fit_results() +project.display.fit.results() # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') +project.display.pattern(expt_name='hrpt') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51) +project.display.pattern(expt_name='hrpt', x_min=48, x_max=51) # %% [markdown] # ## Summary diff --git a/docs/docs/tutorials/ed-7.ipynb b/docs/docs/tutorials/ed-7.ipynb index 7a66c6f66..31294b586 100644 --- a/docs/docs/tutorials/ed-7.ipynb +++ b/docs/docs/tutorials/ed-7.ipynb @@ -345,8 +345,8 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd')\n", - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700)" + "project.display.pattern(expt_name='sepd')\n", + "project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700)" ] }, { @@ -387,7 +387,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -406,7 +406,7 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -424,7 +424,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd')" + "project.display.pattern(expt_name='sepd')" ] }, { @@ -434,7 +434,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700)" + "project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700)" ] }, { @@ -473,7 +473,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -492,7 +492,7 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -510,7 +510,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd')" + "project.display.pattern(expt_name='sepd')" ] }, { @@ -520,7 +520,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700)" + "project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700)" ] }, { @@ -579,7 +579,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -598,7 +598,7 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -616,7 +616,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd')" + "project.display.pattern(expt_name='sepd')" ] }, { @@ -626,7 +626,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700)" + "project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700)" ] }, { @@ -668,7 +668,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.display.free_params()" + "project.display.parameters.free()" ] }, { @@ -687,7 +687,7 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -705,7 +705,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -723,7 +723,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd')" + "project.display.pattern(expt_name='sepd')" ] }, { @@ -733,7 +733,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700)" + "project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700)" ] }, { @@ -745,7 +745,7 @@ }, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x='d_spacing')" + "project.display.pattern(expt_name='sepd', x='d_spacing')" ] }, { @@ -860,7 +860,7 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()" + "project.display.fit.results()" ] }, { @@ -878,7 +878,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_param_correlations()" + "project.display.fit.correlations()" ] }, { @@ -896,7 +896,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700)" + "project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700)" ] }, { @@ -906,7 +906,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x='d_spacing')" + "project.display.pattern(expt_name='sepd', x='d_spacing')" ] } ], diff --git a/docs/docs/tutorials/ed-7.py b/docs/docs/tutorials/ed-7.py index 5305549a7..ed3dcdaec 100644 --- a/docs/docs/tutorials/ed-7.py +++ b/docs/docs/tutorials/ed-7.py @@ -140,8 +140,8 @@ # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd') -project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700) +project.display.pattern(expt_name='sepd') +project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700) # %% [markdown] # ### Perform Fit 1/5 @@ -158,23 +158,23 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd') +project.display.pattern(expt_name='sepd') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700) +project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700) # %% [markdown] # ### Perform Fit 2/5 @@ -189,23 +189,23 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd') +project.display.pattern(expt_name='sepd') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700) +project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700) # %% [markdown] # ### Perform Fit 3/5 @@ -228,23 +228,23 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd') +project.display.pattern(expt_name='sepd') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700) +project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700) # %% [markdown] # ### Perform Fit 4/5 @@ -262,32 +262,32 @@ # Show free parameters after selection. # %% -project.analysis.display.free_params() +project.display.parameters.free() # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Show parameter correlations # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd') +project.display.pattern(expt_name='sepd') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700) +project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700) # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x='d_spacing') +project.display.pattern(expt_name='sepd', x='d_spacing') # %% [markdown] @@ -334,19 +334,19 @@ # %% project.analysis.fit() -project.analysis.display.fit_results() +project.display.fit.results() # %% [markdown] # #### Show parameter correlations # %% -project.display.plotter.plot_param_correlations() +project.display.fit.correlations() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700) +project.display.pattern(expt_name='sepd', x_min=23200, x_max=23700) # %% -project.display.plotter.plot_meas_vs_calc(expt_name='sepd', x='d_spacing') +project.display.pattern(expt_name='sepd', x='d_spacing') diff --git a/docs/docs/tutorials/ed-8.ipynb b/docs/docs/tutorials/ed-8.ipynb index 5df563205..248d4f59e 100644 --- a/docs/docs/tutorials/ed-8.ipynb +++ b/docs/docs/tutorials/ed-8.ipynb @@ -630,7 +630,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='wish_5_6')" + "project.display.pattern(expt_name='wish_5_6')" ] }, { @@ -640,7 +640,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='wish_4_7')" + "project.display.pattern(expt_name='wish_4_7')" ] }, { @@ -659,8 +659,8 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()\n", - "project.display.plotter.plot_param_correlations()" + "project.display.fit.results()\n", + "project.display.fit.correlations()" ] }, { @@ -678,7 +678,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='wish_5_6')" + "project.display.pattern(expt_name='wish_5_6')" ] }, { @@ -688,7 +688,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='wish_4_7')" + "project.display.pattern(expt_name='wish_4_7')" ] }, { diff --git a/docs/docs/tutorials/ed-8.py b/docs/docs/tutorials/ed-8.py index a9adb7c8f..82beedd7b 100644 --- a/docs/docs/tutorials/ed-8.py +++ b/docs/docs/tutorials/ed-8.py @@ -333,27 +333,27 @@ # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='wish_5_6') +project.display.pattern(expt_name='wish_5_6') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='wish_4_7') +project.display.pattern(expt_name='wish_4_7') # %% [markdown] # #### Run Fitting # %% project.analysis.fit() -project.analysis.display.fit_results() -project.display.plotter.plot_param_correlations() +project.display.fit.results() +project.display.fit.correlations() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='wish_5_6') +project.display.pattern(expt_name='wish_5_6') # %% -project.display.plotter.plot_meas_vs_calc(expt_name='wish_4_7') +project.display.pattern(expt_name='wish_4_7') # %% [markdown] # ## Summary diff --git a/docs/docs/tutorials/ed-9.ipynb b/docs/docs/tutorials/ed-9.ipynb index 4c4d7eb5b..0bfd09490 100644 --- a/docs/docs/tutorials/ed-9.ipynb +++ b/docs/docs/tutorials/ed-9.ipynb @@ -509,7 +509,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas(expt_name='mcstas')" + "project.display.pattern(expt_name='mcstas', include='measured')" ] }, { @@ -564,7 +564,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas(expt_name='mcstas')" + "project.display.pattern(expt_name='mcstas', include=('measured', 'excluded'))" ] }, { @@ -660,8 +660,8 @@ "outputs": [], "source": [ "project.analysis.fit()\n", - "project.analysis.display.fit_results()\n", - "project.display.plotter.plot_param_correlations()" + "project.display.fit.results()\n", + "project.display.fit.correlations()" ] }, { @@ -679,7 +679,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.plotter.plot_meas_vs_calc(expt_name='mcstas')" + "project.display.pattern(expt_name='mcstas')" ] } ], diff --git a/docs/docs/tutorials/ed-9.py b/docs/docs/tutorials/ed-9.py index c9108b5f0..ffae09108 100644 --- a/docs/docs/tutorials/ed-9.py +++ b/docs/docs/tutorials/ed-9.py @@ -227,7 +227,7 @@ # Show measured data as loaded from the file. # %% -project.display.plotter.plot_meas(expt_name='mcstas') +project.display.pattern(expt_name='mcstas', include='measured') # %% [markdown] # Add excluded regions. @@ -246,7 +246,7 @@ # Show measured data after adding excluded regions. # %% -project.display.plotter.plot_meas(expt_name='mcstas') +project.display.pattern(expt_name='mcstas', include=('measured', 'excluded')) # %% [markdown] # Show experiment as CIF. @@ -294,11 +294,11 @@ # %% project.analysis.fit() -project.analysis.display.fit_results() -project.display.plotter.plot_param_correlations() +project.display.fit.results() +project.display.fit.correlations() # %% [markdown] # #### Plot Measured vs Calculated # %% -project.display.plotter.plot_meas_vs_calc(expt_name='mcstas') +project.display.pattern(expt_name='mcstas') diff --git a/docs/docs/user-guide/analysis-workflow/analysis.md b/docs/docs/user-guide/analysis-workflow/analysis.md index bf1d4ee87..b3c9e3d53 100644 --- a/docs/docs/user-guide/analysis-workflow/analysis.md +++ b/docs/docs/user-guide/analysis-workflow/analysis.md @@ -247,11 +247,11 @@ Now, you can inspect the fitted parameters to see how they have changed during the refinement process, select more parameters to be refined, and perform additional fits as needed. -To plot the measured vs calculated data after the fit, you can use the -`plot_meas_vs_calc` method of the `analysis` object: +To plot the measured and calculated data after the fit, you can use the +`pattern` method of the `display` object: ```python -project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) +project.display.pattern(expt_name='hrpt') ``` ## Constraints @@ -319,7 +319,7 @@ To view the defined constraints, you can use the `show_constraints` method: ```python -project.analysis.display.constraints() +project.analysis.constraints.show() ``` The example of the output is: diff --git a/docs/docs/user-guide/analysis-workflow/project.md b/docs/docs/user-guide/analysis-workflow/project.md index 7959f9f85..5e8e8b417 100644 --- a/docs/docs/user-guide/analysis-workflow/project.md +++ b/docs/docs/user-guide/analysis-workflow/project.md @@ -115,8 +115,8 @@ data_La0.5Ba0.5CoO3 _project.title "La0.5Ba0.5CoO3 from neutron diffraction at HRPT@PSI" _project.description "neutrons, powder, constant wavelength, HRPT@PSI" -_display.plotter_type asciichartpy -_display.tabler_type rich +_display.chart_engine asciichartpy +_display.table_engine rich diff --git a/docs/docs/user-guide/first-steps.md b/docs/docs/user-guide/first-steps.md index b8f69070d..66f96e908 100644 --- a/docs/docs/user-guide/first-steps.md +++ b/docs/docs/user-guide/first-steps.md @@ -125,23 +125,22 @@ project.analysis.fit.show_minimizer_types() EasyDiffraction provides several methods for showing the available parameters grouped in different categories. For example, you can use: -- `project.analysis.display.all_params()` – to display all available +- `project.display.parameters.all()` – to display all available parameters for the analysis step. -- `project.analysis.display.fittable_params()` – to display only the +- `project.display.parameters.fittable()` – to display only the parameters that can be fitted during the analysis. -- `project.analysis.display.free_params()` – to display the parameters - that are currently free to be adjusted during the fitting process. +- `project.display.parameters.free()` – to display the parameters that + are currently free to be adjusted during the fitting process. -Finally, you can use the -`project.analysis.display.how_to_access_parameters()` method to get a -brief overview of how to access and modify parameters in the analysis -step, along with their unique identifiers in the CIF format. This can be -particularly useful for users who are new to the EasyDiffraction API or -those who want to quickly understand how to work with parameters in -their projects. +Finally, you can use the `project.display.parameters.access()` method to +get a brief overview of how to access and modify parameters in the +analysis step, along with their unique identifiers in the CIF format. +This can be particularly useful for users who are new to the +EasyDiffraction API or those who want to quickly understand how to work +with parameters in their projects. -An example of the output for the -`project.analysis.display.how_to_access_parameters()` method is: +An example of the output for the `project.display.parameters.access()` +method is: | | Code variable | Unique ID for CIF | | --- | --------------------------------------------------- | -------------------------- | @@ -160,7 +159,7 @@ To see the available plotters, you can use the `display` category on the `Project` instance: ```python -project.display.show_plotter_types() +project.rendering.show_chart_engines() ``` An example of the output is: diff --git a/src/easydiffraction/__main__.py b/src/easydiffraction/__main__.py index bd5ae9be5..18220904f 100644 --- a/src/easydiffraction/__main__.py +++ b/src/easydiffraction/__main__.py @@ -99,10 +99,10 @@ def fit( if dry: project.info._path = None project.analysis.fit() - project.analysis.display.fit_results() - project.display.plotter.plot_param_correlations() + project.display.fit.results() + project.display.fit.correlations() for expt in project.experiments: - project.display.plotter.plot_meas_vs_calc(expt_name=expt.name, show_residual=True) + project.display.pattern(expt_name=expt.name) # project.summary.show_report() diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 23b9fcf3f..e3c01607f 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from __future__ import annotations + from contextlib import suppress import numpy as np @@ -102,7 +104,7 @@ class AnalysisDisplay: Accessed via ``analysis.display``. """ - def __init__(self, analysis: 'Analysis') -> None: + def __init__(self, analysis: Analysis) -> None: self._analysis = analysis def _flush_structure_categories(self) -> None: @@ -342,20 +344,7 @@ def parameter_cif_uids(self) -> None: def constraints(self) -> None: """Print a table of all user-defined symbolic constraints.""" - analysis = self._analysis - if not analysis.constraints._items: - log.warning('No constraints defined.') - return - - rows = [[constraint.expression.value] for constraint in analysis.constraints] - - console.paragraph('User defined constraints') - render_table( - columns_headers=['expression'], - columns_alignment=['left'], - columns_data=rows, - ) - console.print(f'Constraints enabled: {analysis.constraints.enabled}') + self._analysis.constraints.show() def fit_results(self) -> None: """ @@ -381,10 +370,7 @@ def fit_results(self) -> None: def as_cif(self) -> None: """Render the analysis section as CIF in console.""" - cif_text: str = self._analysis.as_cif() - paragraph_title: str = 'Analysis 🧮 info as cif' - console.paragraph(paragraph_title) - render_cif(cif_text) + self._analysis.show_as_cif() class Analysis: @@ -900,6 +886,7 @@ def _update_categories( self.constraints_handler.set_constraints(self.constraints) self.constraints_handler.apply() + @property def as_cif(self) -> str: """ Serialize the analysis section to a CIF string. @@ -911,3 +898,8 @@ def as_cif(self) -> str: """ self._update_categories() return analysis_to_cif(self) + + def show_as_cif(self) -> None: + """Pretty-print the analysis section as CIF text.""" + console.paragraph('Analysis info as CIF') + render_cif(self.as_cif) diff --git a/src/easydiffraction/analysis/categories/constraints/default.py b/src/easydiffraction/analysis/categories/constraints/default.py index 63a4264d4..fc1d74fde 100644 --- a/src/easydiffraction/analysis/categories/constraints/default.py +++ b/src/easydiffraction/analysis/categories/constraints/default.py @@ -18,6 +18,9 @@ from easydiffraction.core.validation import RegexValidator from easydiffraction.core.variable import StringDescriptor from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.utils.logging import console +from easydiffraction.utils.logging import log +from easydiffraction.utils.utils import render_table class Constraint(CategoryItem): @@ -132,3 +135,19 @@ def create(self, *, expression: str) -> None: item.expression = expression self.add(item) self._enabled = True + + def show(self) -> None: + """Print a table of all user-defined symbolic constraints.""" + if not self._items: + log.warning('No constraints defined.') + return + + rows = [[constraint.expression.value] for constraint in self] + + console.paragraph('User defined constraints') + render_table( + columns_headers=['expression'], + columns_alignment=['left'], + columns_data=rows, + ) + console.print(f'Constraints enabled: {self.enabled}') diff --git a/src/easydiffraction/display/plotters/ascii.py b/src/easydiffraction/display/plotters/ascii.py index 4aeb5577a..c81af0b0e 100644 --- a/src/easydiffraction/display/plotters/ascii.py +++ b/src/easydiffraction/display/plotters/ascii.py @@ -61,6 +61,7 @@ def plot_powder( axes_labels: object, title: str, height: int | None = None, + excluded_ranges: tuple[tuple[float, float], ...] = (), ) -> None: """ Render a line plot for powder diffraction data. @@ -83,6 +84,8 @@ def plot_powder( Figure title printed above the chart. height : int | None, default=None Number of text rows to allocate for the chart. + excluded_ranges : tuple[tuple[float, float], ...], default=() + Excluded x-ranges to print below the selected x-range. """ # Intentionally unused; kept for a consistent display API del axes_labels @@ -100,6 +103,11 @@ def plot_powder( console.print( f'Displaying data for selected x-range from {x[0]} to {x[-1]} ({len(x)} points)' ) + if excluded_ranges: + formatted_ranges = ', '.join( + f'[{start:,.2f}, {end:,.2f}]' for start, end in excluded_ranges + ) + console.print(f'Excluded regions: {formatted_ranges}') console.print(f'Legend:\n{legend}') padded = '\n'.join(' ' + line for line in chart.splitlines()) @@ -130,6 +138,7 @@ def plot_powder_meas_vs_calc( axes_labels=plot_spec.axes_labels, title=plot_spec.title, height=plot_spec.height, + excluded_ranges=plot_spec.excluded_ranges, ) if plot_spec.predictive_lower_95 is not None and plot_spec.predictive_upper_95 is not None: console.print('Posterior predictive bands are available with the Plotly engine only.') diff --git a/src/easydiffraction/display/plotters/base.py b/src/easydiffraction/display/plotters/base.py index d4be0d67a..4cf43d669 100644 --- a/src/easydiffraction/display/plotters/base.py +++ b/src/easydiffraction/display/plotters/base.py @@ -65,6 +65,7 @@ class PowderMeasVsCalcSpec: predictive_draws: np.ndarray | None = None y_calc_name: str | None = None y_calc_line_dash: str | None = None + excluded_ranges: tuple[tuple[float, float], ...] = () class XAxisType(StrEnum): @@ -229,6 +230,7 @@ def plot_powder( axes_labels: object, title: str, height: int | None, + excluded_ranges: tuple[tuple[float, float], ...] = (), ) -> None: """ Render a line plot for powder diffraction data. @@ -250,6 +252,8 @@ def plot_powder( Figure title. height : int | None Backend-specific height (text rows or pixels). + excluded_ranges : tuple[tuple[float, float], ...], default=() + Closed x-intervals to highlight as excluded regions. """ @abstractmethod diff --git a/src/easydiffraction/display/plotters/plotly.py b/src/easydiffraction/display/plotters/plotly.py index cb4914c3f..d41c1f9eb 100644 --- a/src/easydiffraction/display/plotters/plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -70,6 +70,7 @@ PREDICTIVE_BAND_COLOR = 'rgba(214, 39, 40, 0.14)' PREDICTIVE_BAND_EDGE_COLOR = 'rgba(214, 39, 40, 0.45)' PREDICTIVE_DRAW_COLOR = 'rgba(140, 140, 140, 0.18)' +EXCLUDED_REGION_FILL_COLOR = 'rgba(120, 120, 120, 0.16)' PREDICTIVE_DRAW_PLOT_CAP = 50 PREDICTIVE_DRAW_ARRAY_NDIM = 2 FIXED_ASPECT_WRAPPER_META_KEY = 'fixed_aspect_wrapper' @@ -1038,6 +1039,7 @@ def plot_powder( axes_labels: object, title: str, height: int | None = None, + excluded_ranges: tuple[tuple[float, float], ...] = (), ) -> None: """ Render a line plot for powder diffraction data. @@ -1059,6 +1061,8 @@ def plot_powder( Figure title. height : int | None, default=None Ignored; Plotly auto-sizes based on renderer. + excluded_ranges : tuple[tuple[float, float], ...], default=() + Excluded x-ranges to shade on the figure. """ # Intentionally unused; accepted for API compatibility del height @@ -1075,8 +1079,33 @@ def plot_powder( ) fig = self._get_figure(data, layout) + self._add_excluded_region_vrects(fig=fig, excluded_ranges=excluded_ranges) self._show_figure(fig) + @staticmethod + def _add_excluded_region_vrects( + *, + fig: object, + excluded_ranges: tuple[tuple[float, float], ...], + row: object | None = None, + col: int | None = None, + ) -> None: + """Shade excluded x-ranges on a Plotly figure.""" + for start, end in excluded_ranges: + add_kwargs = { + 'x0': start, + 'x1': end, + 'fillcolor': EXCLUDED_REGION_FILL_COLOR, + 'opacity': 1.0, + 'line_width': 0, + 'layer': 'below', + } + if row is not None: + add_kwargs['row'] = row + if col is not None: + add_kwargs['col'] = col + fig.add_vrect(**add_kwargs) + @staticmethod def _get_bragg_tick_trace( tick_set: BraggTickSet, @@ -1386,6 +1415,12 @@ def plot_powder_meas_vs_calc( hover_data=hover_data, hover_template=hover_template, ) + self._add_excluded_region_vrects( + fig=fig, + excluded_ranges=plot_spec.excluded_ranges, + row='all', + col=1, + ) self._configure_powder_composite_layout(fig=fig, plot_spec=plot_spec, layout=layout) self._configure_powder_composite_axes( fig=fig, diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index 34b0da107..a258579e8 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -179,6 +179,9 @@ class _MeasVsCalcPlotOptions: x_min: float | None = None x_max: float | None = None show_residual: bool | None = None + show_background: bool | None = None + show_bragg: bool | None = None + show_excluded: bool = False x: object | None = None @@ -373,6 +376,18 @@ def _filtered_y_array( mask = (x_array >= lower_bound) & (x_array <= upper_bound) return y_array[mask] + def _filtered_optional_y_array( + self, + y_array: object | None, + x_array: object, + x_min: object, + x_max: object, + ) -> object | None: + """Filter an optional y-array by inclusive x-range limits.""" + if y_array is None: + return None + return self._filtered_y_array(y_array, x_array, x_min, x_max) + @staticmethod def _get_axes_labels( sample_form: object, @@ -574,6 +589,8 @@ def plot_meas( x_min: float | None = None, x_max: float | None = None, x: object | None = None, + *, + show_excluded: bool = False, ) -> None: """ Plot measured diffraction data for an experiment. @@ -588,16 +605,23 @@ def plot_meas( Upper bound for the x-axis range. x : object | None, default=None Optional explicit x-axis data to override stored values. + show_excluded : bool, default=False + Whether to show excluded fitting regions on supported plots. """ self._update_project_categories(expt_name) experiment = self._project.experiments[expt_name] + plot_options = _MeasVsCalcPlotOptions( + x_min=x_min, + x_max=x_max, + show_excluded=show_excluded, + x=x, + ) self._plot_meas_data( + experiment, intensity_category_for(experiment), expt_name, experiment.type, - x_min=x_min, - x_max=x_max, - x=x, + plot_options, ) def plot_calc( @@ -606,6 +630,8 @@ def plot_calc( x_min: float | None = None, x_max: float | None = None, x: object | None = None, + *, + show_excluded: bool = False, ) -> None: """ Plot calculated diffraction pattern for an experiment. @@ -620,16 +646,23 @@ def plot_calc( Upper bound for the x-axis range. x : object | None, default=None Optional explicit x-axis data to override stored values. + show_excluded : bool, default=False + Whether to show excluded fitting regions on supported plots. """ self._update_project_categories(expt_name) experiment = self._project.experiments[expt_name] + plot_options = _MeasVsCalcPlotOptions( + x_min=x_min, + x_max=x_max, + show_excluded=show_excluded, + x=x, + ) self._plot_calc_data( + experiment, intensity_category_for(experiment), expt_name, experiment.type, - x_min=x_min, - x_max=x_max, - x=x, + plot_options, ) def plot_meas_vs_calc( @@ -639,6 +672,7 @@ def plot_meas_vs_calc( x_max: float | None = None, *, show_residual: bool | None = None, + show_excluded: bool = False, x: object | None = None, ) -> None: """ @@ -656,17 +690,29 @@ def plot_meas_vs_calc( When ``None``, powder Bragg plots include the residual by default while other measured-vs-calculated plots keep the historical no-residual default. + show_excluded : bool, default=False + Whether to show excluded fitting regions on supported plots. x : object | None, default=None Optional explicit x-axis data to override stored values. """ - self._update_project_categories(expt_name) - experiment = self._project.experiments[expt_name] plot_options = _MeasVsCalcPlotOptions( x_min=x_min, x_max=x_max, show_residual=show_residual, + show_excluded=show_excluded, x=x, ) + self._plot_meas_vs_calc_request(expt_name=expt_name, plot_options=plot_options) + + def _plot_meas_vs_calc_request( + self, + *, + expt_name: str, + plot_options: _MeasVsCalcPlotOptions, + ) -> None: + """Render a measured-vs-calculated request from plot options.""" + self._update_project_categories(expt_name) + experiment = self._project.experiments[expt_name] self._plot_meas_vs_calc_data( experiment=experiment, expt_name=expt_name, @@ -990,6 +1036,7 @@ def plot_posterior_predictive( x_max: float | None = None, *, show_residual: bool | None = None, + show_excluded: bool = False, x: object | None = None, ) -> None: """ @@ -1011,6 +1058,8 @@ def plot_posterior_predictive( show_residual : bool | None, default=None Whether to include the residual row in supported powder composite plots. + show_excluded : bool, default=False + Whether to show excluded fitting regions on supported plots. x : object | None, default=None Optional explicit x-axis data to override stored values. @@ -1024,6 +1073,28 @@ def plot_posterior_predictive( msg = "style must be 'band', 'draws', or 'band+draws'." raise ValueError(msg) + plot_options = _MeasVsCalcPlotOptions( + x_min=x_min, + x_max=x_max, + show_residual=show_residual, + show_excluded=show_excluded, + x=x, + ) + + self._plot_posterior_predictive_request( + expt_name=expt_name, + style=style, + plot_options=plot_options, + ) + + def _plot_posterior_predictive_request( + self, + *, + expt_name: str, + style: str, + plot_options: _MeasVsCalcPlotOptions, + ) -> None: + """Render a posterior predictive request from plot options.""" if self._project is None: log.warning('Plotter is not attached to a project.') return @@ -1034,13 +1105,9 @@ def plot_posterior_predictive( self._update_project_categories(expt_name) experiment = self._project.experiments[expt_name] - x_axis, _, sample_form, scattering_type, _ = self._resolve_x_axis(experiment.type, x) - - plot_options = _MeasVsCalcPlotOptions( - x_min=x_min, - x_max=x_max, - show_residual=show_residual, - x=x, + x_axis, _, sample_form, scattering_type, _ = self._resolve_x_axis( + experiment.type, + plot_options.x, ) if sample_form == SampleFormEnum.SINGLE_CRYSTAL: @@ -1221,6 +1288,15 @@ def _plot_non_bragg_posterior_predictive( ctx['x_min'], ctx['x_max'], ) + excluded_ranges = ( + self._excluded_ranges( + experiment=experiment, + x_min=ctx['x_min'], + x_max=ctx['x_max'], + ) + if plot_options.show_excluded + else () + ) axes_labels = self._get_axes_labels(sample_form, scattering_type, x_axis) self._plot_posterior_predictive_summary( @@ -1230,6 +1306,7 @@ def _plot_non_bragg_posterior_predictive( axes_labels=axes_labels, show_band=style in {'band', 'band+draws'}, show_draws=style in {'draws', 'band+draws'}, + excluded_ranges=excluded_ranges, ) @staticmethod @@ -3377,6 +3454,7 @@ def _plot_posterior_predictive_summary( axes_labels: list[str], show_band: bool, show_draws: bool, + excluded_ranges: tuple[tuple[float, float], ...] = (), ) -> None: """Render posterior predictive summaries using Plotly.""" go = __import__('plotly.graph_objects', fromlist=['Figure', 'Scatter']) @@ -3452,6 +3530,15 @@ def _plot_posterior_predictive_summary( legendrank=20, ) ) + for start, end in excluded_ranges: + fig.add_vrect( + x0=start, + x1=end, + fillcolor='rgba(120, 120, 120, 0.16)', + opacity=1.0, + line_width=0, + layer='below', + ) fig.update_layout( title={ 'text': f"Posterior predictive for experiment 🔬 '{expt_name}'", @@ -3627,12 +3714,14 @@ def _plot_posterior_predictive_data( ctx['x_min'], ctx['x_max'], ) - y_bkg_raw = getattr(pattern, 'intensity_bkg', None) - y_bkg = ( - self._filtered_y_array(y_bkg_raw, ctx['x_array'], ctx['x_min'], ctx['x_max']) - if y_bkg_raw is not None - else None + y_bkg = self._filtered_optional_y_array( + getattr(pattern, 'intensity_bkg', None), + ctx['x_array'], + ctx['x_min'], + ctx['x_max'], ) + if not self._show_background_enabled(plot_options, background_available=y_bkg is not None): + y_bkg = None y_calc = self._filtered_y_array( summary.map_prediction, summary.x, ctx['x_min'], ctx['x_max'] ) @@ -3669,7 +3758,7 @@ def _plot_posterior_predictive_data( dtype=float, ) - if np.asarray(ctx['x_filtered']).size == 0: + if np.asarray(ctx['x_filtered']).size == 0 or not self._show_bragg_enabled(plot_options): bragg_tick_sets = () else: bragg_tick_sets = self._extract_bragg_tick_sets( @@ -3679,6 +3768,15 @@ def _plot_posterior_predictive_data( x_min=ctx['x_min'], x_max=ctx['x_max'], ) + excluded_ranges = ( + self._excluded_ranges( + experiment=experiment, + x_min=ctx['x_min'], + x_max=ctx['x_max'], + ) + if plot_options.show_excluded + else () + ) plot_spec = PowderMeasVsCalcSpec( x=ctx['x_filtered'], @@ -3697,6 +3795,7 @@ def _plot_posterior_predictive_data( predictive_draws=predictive_draws, y_calc_name=POSTERIOR_POINT_ESTIMATE_TRACE_NAME, y_calc_line_dash=POSTERIOR_POINT_ESTIMATE_LINE_DASH, + excluded_ranges=excluded_ranges, ) self._backend.plot_powder_meas_vs_calc(plot_spec=plot_spec) @@ -4646,18 +4745,19 @@ def _format_correlation_table_dataframe( def _plot_meas_data( self, + experiment: object, pattern: object, expt_name: str, expt_type: object, - x_min: object = None, - x_max: object = None, - x: object = None, + plot_options: _MeasVsCalcPlotOptions, ) -> None: """ Plot measured pattern using the current engine. Parameters ---------- + experiment : object + Experiment object used for excluded-range extraction. pattern : object Object with x-axis arrays (``two_theta``, ``time_of_flight``, ``d_spacing``) and ``meas`` array. @@ -4665,20 +4765,16 @@ def _plot_meas_data( Experiment name for the title. expt_type : object Experiment type with scattering/beam enums. - x_min : object, default=None - Optional minimum x-axis limit. - x_max : object, default=None - Optional maximum x-axis limit. - x : object, default=None - X-axis type. If ``None``, auto-detected from beam mode. + plot_options : _MeasVsCalcPlotOptions + X-range, excluded-region, and x-axis selection options. """ ctx = self._prepare_powder_context( pattern, expt_name, expt_type, - x_min, - x_max, - x, + plot_options.x_min, + plot_options.x_max, + plot_options.x, ) if ctx is None: return @@ -4689,6 +4785,15 @@ def _plot_meas_data( y_meas = self._filtered_y_array( pattern.intensity_meas, ctx['x_array'], ctx['x_min'], ctx['x_max'] ) + excluded_ranges = ( + self._excluded_ranges( + experiment=experiment, + x_min=ctx['x_min'], + x_max=ctx['x_max'], + ) + if plot_options.show_excluded + else () + ) self._backend.plot_powder( x=ctx['x_filtered'], @@ -4697,22 +4802,24 @@ def _plot_meas_data( axes_labels=ctx['axes_labels'], title=f"Measured data for experiment 🔬 '{expt_name}'", height=self.height, + excluded_ranges=excluded_ranges, ) def _plot_calc_data( self, + experiment: object, pattern: object, expt_name: str, expt_type: object, - x_min: object = None, - x_max: object = None, - x: object = None, + plot_options: _MeasVsCalcPlotOptions, ) -> None: """ Plot calculated pattern using the current engine. Parameters ---------- + experiment : object + Experiment object used for excluded-range extraction. pattern : object Object with x-axis arrays (``two_theta``, ``time_of_flight``, ``d_spacing``) and ``calc`` array. @@ -4720,20 +4827,16 @@ def _plot_calc_data( Experiment name for the title. expt_type : object Experiment type with scattering/beam enums. - x_min : object, default=None - Optional minimum x-axis limit. - x_max : object, default=None - Optional maximum x-axis limit. - x : object, default=None - X-axis type. If ``None``, auto-detected from beam mode. + plot_options : _MeasVsCalcPlotOptions + X-range, excluded-region, and x-axis selection options. """ ctx = self._prepare_powder_context( pattern, expt_name, expt_type, - x_min, - x_max, - x, + plot_options.x_min, + plot_options.x_max, + plot_options.x, ) if ctx is None: return @@ -4744,6 +4847,15 @@ def _plot_calc_data( y_calc = self._filtered_y_array( pattern.intensity_calc, ctx['x_array'], ctx['x_min'], ctx['x_max'] ) + excluded_ranges = ( + self._excluded_ranges( + experiment=experiment, + x_min=ctx['x_min'], + x_max=ctx['x_max'], + ) + if plot_options.show_excluded + else () + ) self._backend.plot_powder( x=ctx['x_filtered'], @@ -4752,6 +4864,7 @@ def _plot_calc_data( axes_labels=ctx['axes_labels'], title=f"Calculated data for experiment 🔬 '{expt_name}'", height=self.height, + excluded_ranges=excluded_ranges, ) def _plot_meas_vs_calc_data( @@ -4837,12 +4950,23 @@ def _plot_meas_vs_calc_data( if y_bkg_raw is not None else None ) + if not self._show_background_enabled(plot_options, background_available=y_bkg is not None): + y_bkg = None powder_series = _PowderMeasVsCalcSeries( y_meas=y_meas, y_calc=y_calc, y_bkg=y_bkg, ) + excluded_ranges = ( + self._excluded_ranges( + experiment=experiment, + x_min=ctx['x_min'], + x_max=ctx['x_max'], + ) + if plot_options.show_excluded + else () + ) if sample_form == SampleFormEnum.POWDER and scattering_type == ScatteringTypeEnum.BRAGG: self._plot_powder_bragg_meas_vs_calc( @@ -4852,6 +4976,7 @@ def _plot_meas_vs_calc_data( series=powder_series, plot_options=plot_options, title=title, + excluded_ranges=excluded_ranges, ) return @@ -4863,6 +4988,7 @@ def _plot_meas_vs_calc_data( if plot_options.show_residual is None else plot_options.show_residual, title=title, + excluded_ranges=excluded_ranges, ) def _plot_single_crystal_meas_vs_calc( @@ -4901,13 +5027,14 @@ def _plot_powder_bragg_meas_vs_calc( series: _PowderMeasVsCalcSeries, plot_options: _MeasVsCalcPlotOptions, title: str, + excluded_ranges: tuple[tuple[float, float], ...], ) -> None: """ Render the composite powder Bragg measured-vs-calculated plot. """ show_residual = True if plot_options.show_residual is None else plot_options.show_residual y_resid = series.y_meas - series.y_calc if show_residual else None - if np.asarray(ctx['x_filtered']).size == 0: + if np.asarray(ctx['x_filtered']).size == 0 or not self._show_bragg_enabled(plot_options): bragg_tick_sets = () else: bragg_tick_sets = self._extract_bragg_tick_sets( @@ -4930,9 +5057,30 @@ def _plot_powder_bragg_meas_vs_calc( bragg_peaks_height_fraction=DEFAULT_BRAGG_ROW, height=self._composite_plot_height(), y_bkg=series.y_bkg, + excluded_ranges=excluded_ranges, ) self._backend.plot_powder_meas_vs_calc(plot_spec=plot_spec) + @staticmethod + def _show_background_enabled( + plot_options: object, + *, + background_available: bool, + ) -> bool: + """Return whether the background curve should be shown.""" + show_background = getattr(plot_options, 'show_background', None) + if show_background is None: + return background_available + return show_background and background_available + + @staticmethod + def _show_bragg_enabled(plot_options: object) -> bool: + """Return whether Bragg reflection rows should be shown.""" + show_bragg = getattr(plot_options, 'show_bragg', None) + if show_bragg is None: + return True + return show_bragg + def _plot_line_meas_vs_calc( self, ctx: dict[str, object], @@ -4941,6 +5089,7 @@ def _plot_line_meas_vs_calc( *, show_residual: bool, title: str, + excluded_ranges: tuple[tuple[float, float], ...] = (), ) -> None: """ Render the non-composite line version of measured-vs-calculated. @@ -4958,8 +5107,36 @@ def _plot_line_meas_vs_calc( axes_labels=ctx['axes_labels'], title=title, height=self.height, + excluded_ranges=excluded_ranges, ) + @staticmethod + def _excluded_ranges( + *, + experiment: object, + x_min: float | None, + x_max: float | None, + ) -> tuple[tuple[float, float], ...]: + """Return excluded x-ranges clipped to the current view.""" + excluded_regions = getattr(experiment, 'excluded_regions', None) + if excluded_regions is None: + return () + + clipped_ranges: list[tuple[float, float]] = [] + lower_bound = -np.inf if x_min is None else float(x_min) + upper_bound = np.inf if x_max is None else float(x_max) + + for region in excluded_regions: + start = float(region.start.value) + end = float(region.end.value) + clipped_start = max(start, lower_bound) + clipped_end = min(end, upper_bound) + if clipped_start > clipped_end: + continue + clipped_ranges.append((clipped_start, clipped_end)) + + return tuple(clipped_ranges) + @staticmethod def _extract_bragg_tick_sets( experiment: object, diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py index 0b868b6c1..b71e69e22 100644 --- a/src/easydiffraction/io/cif/serialize.py +++ b/src/easydiffraction/io/cif/serialize.py @@ -334,9 +334,9 @@ def _as_cif_text(section: object) -> str: def project_config_to_cif(project: object) -> str: """Render project-level configuration to ``project.cif`` text.""" lines: list[str] = [_as_cif_text(project.info)] - display = getattr(project, 'display', None) - if display is not None: - lines.extend(('', _as_cif_text(display))) + rendering = getattr(project, 'rendering', None) + if rendering is not None: + lines.extend(('', _as_cif_text(rendering))) return '\n'.join(lines) @@ -350,7 +350,7 @@ def project_to_cif(project: object) -> str: if getattr(project, 'experiments', None): parts.append(_as_cif_text(project.experiments)) if getattr(project, 'analysis', None): - parts.append(project.analysis.as_cif()) + parts.append(_as_cif_text(project.analysis)) if getattr(project, 'summary', None): parts.append(project.summary.as_cif()) return '\n\n'.join([p for p in parts if p]) @@ -455,9 +455,9 @@ def project_config_from_cif(project: object, cif_text: str) -> None: _populate_project_info_from_block(project.info, block) - display = getattr(project, 'display', None) - if display is not None: - display.from_cif(block) + rendering = getattr(project, 'rendering', None) + if rendering is not None: + rendering.from_cif(block) def analysis_from_cif(analysis: object, cif_text: str) -> None: diff --git a/src/easydiffraction/project/categories/display/__init__.py b/src/easydiffraction/project/categories/display/__init__.py deleted file mode 100644 index d0862c2e0..000000000 --- a/src/easydiffraction/project/categories/display/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause -"""Project display category exports.""" - -from __future__ import annotations - -from easydiffraction.project.categories.display.default import Display -from easydiffraction.project.categories.display.factory import DisplayFactory diff --git a/src/easydiffraction/project/categories/display/default.py b/src/easydiffraction/project/categories/display/default.py deleted file mode 100644 index d8287b09b..000000000 --- a/src/easydiffraction/project/categories/display/default.py +++ /dev/null @@ -1,117 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause -"""Project display category.""" - -from __future__ import annotations - -from easydiffraction.core.category import CategoryItem -from easydiffraction.core.metadata import TypeInfo -from easydiffraction.core.validation import AttributeSpec -from easydiffraction.core.validation import MembershipValidator -from easydiffraction.core.variable import StringDescriptor -from easydiffraction.display.plotting import Plotter -from easydiffraction.display.plotting import PlotterEngineEnum -from easydiffraction.display.tables import TableEngineEnum -from easydiffraction.display.tables import TableRenderer -from easydiffraction.io.cif.handler import CifHandler -from easydiffraction.io.cif.parse import read_cif_str -from easydiffraction.project.categories.display.factory import DisplayFactory - - -@DisplayFactory.register -class Display(CategoryItem): - """Display engine selection and access for a project.""" - - type_info = TypeInfo( - tag='default', - description='Project display category', - ) - - def __init__(self) -> None: - super().__init__() - - self._plotter = Plotter() - self._tabler = TableRenderer.get() - - self._plotter_type = StringDescriptor( - name='plotter_type', - description='Plot renderer backend type', - value_spec=AttributeSpec( - default=self._plotter.engine, - validator=MembershipValidator( - allowed=[member.value for member in PlotterEngineEnum], - ), - ), - cif_handler=CifHandler(names=['_display.plotter_type']), - ) - self._tabler_type = StringDescriptor( - name='tabler_type', - description='Table renderer backend type', - value_spec=AttributeSpec( - default=self._tabler.engine, - validator=MembershipValidator( - allowed=[member.value for member in TableEngineEnum], - ), - ), - cif_handler=CifHandler(names=['_display.tabler_type']), - ) - - self._identity.category_code = 'display' - - @property - def plotter_type(self) -> StringDescriptor: - """Plot renderer backend type.""" - return self._plotter_type - - @plotter_type.setter - def plotter_type(self, value: str) -> None: - self._plotter.engine = value - self._plotter_type.value = self._plotter.engine - - @property - def tabler_type(self) -> StringDescriptor: - """Table renderer backend type.""" - return self._tabler_type - - @tabler_type.setter - def tabler_type(self, value: str) -> None: - self._tabler.engine = value - self._tabler_type.value = self._tabler.engine - - @property - def plotter(self) -> Plotter: - """Live plotting facade bound to the owning project.""" - parent = getattr(self, '_parent', None) - if parent is not None: - self._plotter._set_project(parent) - return self._plotter - - @property - def tabler(self) -> TableRenderer: - """Live table-rendering facade.""" - return self._tabler - - def show_plotter_types(self) -> None: - """Print supported plot renderer backends.""" - self.plotter.show_supported_engines() - - def show_tabler_types(self) -> None: - """Print supported table renderer backends.""" - self.tabler.show_supported_engines() - - def from_cif(self, block: object, idx: int = 0) -> None: - """Populate this display category from a CIF block.""" - del idx - plotter_type = read_cif_str(block, '_display.plotter_type') - if plotter_type is not None: - if plotter_type == self._plotter.engine: - self._plotter_type.value = plotter_type - else: - self.plotter_type = plotter_type - - tabler_type = read_cif_str(block, '_display.tabler_type') - if tabler_type is not None: - if tabler_type == self._tabler.engine: - self._tabler_type.value = tabler_type - else: - self.tabler_type = tabler_type diff --git a/src/easydiffraction/project/categories/rendering/__init__.py b/src/easydiffraction/project/categories/rendering/__init__.py new file mode 100644 index 000000000..3e9889159 --- /dev/null +++ b/src/easydiffraction/project/categories/rendering/__init__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Project rendering category exports.""" + +from __future__ import annotations + +from easydiffraction.project.categories.rendering.default import Rendering +from easydiffraction.project.categories.rendering.factory import RenderingFactory diff --git a/src/easydiffraction/project/categories/rendering/default.py b/src/easydiffraction/project/categories/rendering/default.py new file mode 100644 index 000000000..f8970cb55 --- /dev/null +++ b/src/easydiffraction/project/categories/rendering/default.py @@ -0,0 +1,131 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Project rendering category.""" + +from __future__ import annotations + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.metadata import TypeInfo +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import MembershipValidator +from easydiffraction.core.variable import StringDescriptor +from easydiffraction.display.plotting import Plotter +from easydiffraction.display.plotting import PlotterEngineEnum +from easydiffraction.display.tables import TableEngineEnum +from easydiffraction.display.tables import TableRenderer +from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.io.cif.parse import read_cif_str +from easydiffraction.project.categories.rendering.factory import RenderingFactory +from easydiffraction.utils.logging import console +from easydiffraction.utils.utils import render_table + + +@RenderingFactory.register +class Rendering(CategoryItem): + """Chart and table engine selection for a project.""" + + type_info = TypeInfo( + tag='default', + description='Project rendering category', + ) + + def __init__(self) -> None: + super().__init__() + + self._plotter = Plotter() + self._tabler = TableRenderer.get() + + self._chart_engine = StringDescriptor( + name='chart_engine', + description='Chart renderer backend type', + value_spec=AttributeSpec( + default=self._plotter.engine, + validator=MembershipValidator( + allowed=[member.value for member in PlotterEngineEnum], + ), + ), + cif_handler=CifHandler(names=['_rendering.chart_engine']), + ) + self._table_engine = StringDescriptor( + name='table_engine', + description='Table renderer backend type', + value_spec=AttributeSpec( + default=self._tabler.engine, + validator=MembershipValidator( + allowed=[member.value for member in TableEngineEnum], + ), + ), + cif_handler=CifHandler(names=['_rendering.table_engine']), + ) + + self._identity.category_code = 'rendering' + + @property + def chart_engine(self) -> StringDescriptor: + """Chart renderer backend type.""" + return self._chart_engine + + @chart_engine.setter + def chart_engine(self, value: str) -> None: + self._plotter.engine = value + self._chart_engine.value = self._plotter.engine + + @property + def table_engine(self) -> StringDescriptor: + """Table renderer backend type.""" + return self._table_engine + + @table_engine.setter + def table_engine(self, value: str) -> None: + self._tabler.engine = value + self._table_engine.value = self._tabler.engine + + @property + def plotter(self) -> Plotter: + """Live plotting facade bound to the owning project.""" + parent = getattr(self, '_parent', None) + if parent is not None: + self._plotter._set_project(parent) + return self._plotter + + @property + def tabler(self) -> TableRenderer: + """Live table-rendering facade.""" + return self._tabler + + def show_chart_engines(self) -> None: + """Print supported chart renderer backends.""" + self.plotter.show_supported_engines() + + def show_table_engines(self) -> None: + """Print supported table renderer backends.""" + self.tabler.show_supported_engines() + + def show_config(self) -> None: + """Print the current rendering configuration.""" + console.paragraph('Current rendering configuration') + render_table( + columns_headers=['Setting', 'Value'], + columns_alignment=['left', 'left'], + columns_data=[ + ['Chart engine', self.chart_engine.value], + ['Table engine', self.table_engine.value], + ], + ) + + def from_cif(self, block: object, idx: int = 0) -> None: + """Populate this rendering category from a CIF block.""" + del idx + chart_engine = read_cif_str(block, '_rendering.chart_engine') + if chart_engine is not None: + if chart_engine == self._plotter.engine: + self._chart_engine.value = chart_engine + else: + self.chart_engine = chart_engine + + table_engine = read_cif_str(block, '_rendering.table_engine') + if table_engine is not None: + if table_engine == self._tabler.engine: + self._table_engine.value = table_engine + else: + self.table_engine = table_engine diff --git a/src/easydiffraction/project/categories/display/factory.py b/src/easydiffraction/project/categories/rendering/factory.py similarity index 70% rename from src/easydiffraction/project/categories/display/factory.py rename to src/easydiffraction/project/categories/rendering/factory.py index 0d1be80a9..c2bdf7c5f 100644 --- a/src/easydiffraction/project/categories/display/factory.py +++ b/src/easydiffraction/project/categories/rendering/factory.py @@ -1,6 +1,6 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Factory for project display categories.""" +"""Factory for project rendering categories.""" from __future__ import annotations @@ -9,8 +9,8 @@ from easydiffraction.core.factory import FactoryBase -class DisplayFactory(FactoryBase): - """Create project display category instances.""" +class RenderingFactory(FactoryBase): + """Create project rendering category instances.""" _default_rules: ClassVar[dict] = { frozenset(): 'default', diff --git a/src/easydiffraction/project/display.py b/src/easydiffraction/project/display.py new file mode 100644 index 000000000..74c510fac --- /dev/null +++ b/src/easydiffraction/project/display.py @@ -0,0 +1,689 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Project display facade grouping charts and reports.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from easydiffraction.datablocks.experiment.item.base import intensity_category_for +from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum +from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum +from easydiffraction.display.plotting import PlotterEngineEnum +from easydiffraction.display.plotting import PosteriorPairPlotStyleEnum +from easydiffraction.display.plotting import _MeasVsCalcPlotOptions +from easydiffraction.utils.utils import render_table + +if TYPE_CHECKING: + from easydiffraction.project.project import Project + + +_PATTERN_OPTION_DESCRIPTIONS: dict[str, str] = { + 'auto': 'Show the most informative available pattern view.', + 'measured': 'Measured diffraction intensities.', + 'calculated': 'Calculated diffraction intensities.', + 'background': 'Calculated background intensities when present.', + 'residual': 'Measured minus calculated residuals when supported.', + 'bragg': 'Bragg reflection tick marks when reflection data exists.', + 'excluded': 'Excluded fitting regions when defined on the experiment.', + 'uncertainty': 'Posterior predictive uncertainty bands when available.', +} + + +@dataclass(frozen=True, slots=True) +class PatternOptionStatus: + """Availability metadata for one ``display.pattern`` option.""" + + name: str + description: str + available: bool + auto_included: bool + reason: str + + +class ParameterDisplay: + """Parameter-table namespace under ``project.display``.""" + + def __init__(self, project: Project) -> None: + self._project = project + + def all(self) -> None: + """Show all structure and experiment parameters.""" + self._project.analysis.display.all_params() + + def fittable(self) -> None: + """Show all currently fittable parameters.""" + self._project.analysis.display.fittable_params() + + def free(self) -> None: + """Show all currently free parameters.""" + self._project.analysis.display.free_params() + + def access(self) -> None: + """Show Python access paths for all parameters.""" + self._project.analysis.display.how_to_access_parameters() + + def cif_uids(self) -> None: + """Show CIF unique identifiers for all parameters.""" + self._project.analysis.display.parameter_cif_uids() + + +class FitDisplay: + """Fit-report namespace under ``project.display``.""" + + def __init__(self, project: Project) -> None: + self._project = project + + def results(self) -> None: + """Show the latest fit summary and fitted parameter table.""" + self._project.analysis.display.fit_results() + + def correlations( + self, + threshold: float | None = None, + precision: int = 2, + *, + max_parameters: int = 6, + show_diagonal: bool = True, + ) -> None: + """Show parameter correlations from the latest fit.""" + self._project.rendering.plotter.plot_param_correlations( + threshold=threshold, + precision=precision, + max_parameters=max_parameters, + show_diagonal=show_diagonal, + ) + + def series( + self, + param: object, + versus: object | None = None, + ) -> None: + """Plot one fitted parameter across sequential results.""" + self._project.rendering.plotter.plot_param_series(param=param, versus=versus) + + +class PosteriorDisplay: + """Posterior-plot namespace under ``project.display``.""" + + def __init__(self, project: Project) -> None: + self._project = project + + def pairs( + self, + parameters: list[object] | None = None, + style: PosteriorPairPlotStyleEnum | str = PosteriorPairPlotStyleEnum.AUTO, + *, + threshold: float | None = None, + max_parameters: int = 6, + ) -> None: + """Plot posterior pair relationships for sampled parameters.""" + self._project.rendering.plotter.plot_posterior_pairs( + parameters=parameters, + style=style, + threshold=threshold, + max_parameters=max_parameters, + ) + + def distribution(self, param: object) -> None: + """Plot one sampled parameter's posterior distribution.""" + self._project.rendering.plotter.plot_param_distribution(param) + + def predictive( + self, + expt_name: str, + style: str = 'band', + x_min: float | None = None, + x_max: float | None = None, + *, + show_residual: bool | None = None, + x: object | None = None, + ) -> None: + """Plot posterior predictive summaries for one experiment.""" + self._project.rendering.plotter.plot_posterior_predictive( + expt_name=expt_name, + style=style, + x_min=x_min, + x_max=x_max, + show_residual=show_residual, + x=x, + ) + + +class ProjectDisplay: + """Grouped display facade exposed as ``project.display``.""" + + def __init__(self, project: Project) -> None: + self._project = project + self._parameters = ParameterDisplay(project) + self._fit = FitDisplay(project) + self._posterior = PosteriorDisplay(project) + + @property + def parameters(self) -> ParameterDisplay: + """Parameter-table namespace.""" + return self._parameters + + @property + def fit(self) -> FitDisplay: + """Fit-report namespace.""" + return self._fit + + @property + def posterior(self) -> PosteriorDisplay: + """Posterior-plot namespace.""" + return self._posterior + + def pattern( + self, + expt_name: str, + x_min: float | None = None, + x_max: float | None = None, + include: str | tuple[str, ...] = 'auto', + *, + x: object | None = None, + ) -> None: + """Show a pattern view for one experiment.""" + normalized_include = self._normalize_include(include) + statuses = self._pattern_option_statuses(expt_name) + + if normalized_include == ('auto',): + auto_include = self._auto_include(statuses) + if x is not None: + auto_include = tuple(option for option in auto_include if option != 'excluded') + if not auto_include: + msg = self._status_by_name(statuses, 'auto').reason + raise ValueError(msg) + if 'uncertainty' in auto_include: + self._project.rendering.plotter._plot_posterior_predictive_request( + expt_name=expt_name, + style='band', + plot_options=_MeasVsCalcPlotOptions( + x_min=x_min, + x_max=x_max, + show_residual=True if 'residual' in auto_include else None, + show_background='background' in auto_include, + show_bragg='bragg' in auto_include, + show_excluded='excluded' in auto_include, + x=x, + ), + ) + return + self._show_point_estimate_pattern( + expt_name=expt_name, + x_min=x_min, + x_max=x_max, + include=auto_include, + statuses=statuses, + x=x, + ) + return + + self._validate_requested_include(statuses, normalized_include) + if x is not None and 'excluded' in normalized_include: + msg = "Excluded-region overlays currently require the experiment's default x-axis." + raise ValueError(msg) + + if 'uncertainty' in normalized_include: + self._project.rendering.plotter._plot_posterior_predictive_request( + expt_name=expt_name, + style='band', + plot_options=_MeasVsCalcPlotOptions( + x_min=x_min, + x_max=x_max, + show_residual=True if 'residual' in normalized_include else None, + show_background='background' in normalized_include, + show_bragg='bragg' in normalized_include, + show_excluded='excluded' in normalized_include, + x=x, + ), + ) + return + + self._show_point_estimate_pattern( + expt_name=expt_name, + x_min=x_min, + x_max=x_max, + include=normalized_include, + statuses=statuses, + x=x, + ) + + def show_pattern_options(self, expt_name: str) -> None: + """Show available ``pattern(include=...)`` options.""" + statuses = self._pattern_option_statuses(expt_name) + render_table( + columns_headers=['Option', 'Description', 'Available', 'Auto', 'Reason'], + columns_alignment=['left', 'left', 'center', 'center', 'left'], + columns_data=[ + [ + status.name, + status.description, + 'yes' if status.available else 'no', + 'yes' if status.auto_included else 'no', + status.reason or '-', + ] + for status in statuses + ], + ) + + @staticmethod + def _normalize_include(include: str | tuple[str, ...]) -> tuple[str, ...]: + """Validate and normalize a ``pattern(include=...)`` value.""" + values = (include,) if isinstance(include, str) else include + if not values: + msg = 'include must contain at least one option.' + raise ValueError(msg) + + normalized = tuple(dict.fromkeys(values)) + unknown = [value for value in normalized if value not in _PATTERN_OPTION_DESCRIPTIONS] + if unknown: + msg = f'Unknown pattern include option(s): {unknown}.' + raise ValueError(msg) + if 'auto' in normalized and len(normalized) > 1: + msg = "include='auto' cannot be combined with other options." + raise ValueError(msg) + return normalized + + @staticmethod + def _status_by_name( + statuses: list[PatternOptionStatus], + option_name: str, + ) -> PatternOptionStatus: + """Return one pattern option status by name.""" + for status in statuses: + if status.name == option_name: + return status + msg = f'Unknown pattern option: {option_name}.' + raise ValueError(msg) + + @staticmethod + def _with_available_options( + status_by_name: dict[str, PatternOptionStatus], + required: tuple[str, ...], + optional: tuple[str, ...], + ) -> tuple[str, ...]: + """Return required options plus available optional ones.""" + include = list(required) + include.extend( + option_name for option_name in optional if status_by_name[option_name].available + ) + return tuple(include) + + @classmethod + def _auto_include( + cls, + statuses: list[PatternOptionStatus], + ) -> tuple[str, ...]: + """Return the effective include tuple for ``include='auto'``.""" + status_by_name = {status.name: status for status in statuses} + optional_point_estimate = ('background', 'residual', 'bragg', 'excluded') + + if status_by_name['uncertainty'].available: + return cls._with_available_options( + status_by_name, + ('measured', 'calculated', 'uncertainty'), + optional_point_estimate, + ) + if status_by_name['measured'].available and status_by_name['calculated'].available: + return cls._with_available_options( + status_by_name, + ('measured', 'calculated'), + optional_point_estimate, + ) + if status_by_name['measured'].available: + return cls._with_available_options( + status_by_name, + ('measured',), + ('excluded',), + ) + if status_by_name['calculated'].available: + return cls._with_available_options( + status_by_name, + ('calculated',), + ('excluded',), + ) + return () + + @classmethod + def _validate_requested_include( + cls, + statuses: list[PatternOptionStatus], + include: tuple[str, ...], + ) -> None: + """ + Raise a clear error when a requested include is unavailable. + """ + status_by_name = {status.name: status for status in statuses} + unavailable = [ + option_name + for option_name in include + if option_name != 'auto' and not status_by_name[option_name].available + ] + if unavailable: + option_name = unavailable[0] + msg = status_by_name[option_name].reason + raise ValueError(msg) + + include_set = set(include) + if 'background' in include_set and not {'measured', 'calculated'}.issubset(include_set): + msg = 'background requires both measured and calculated data in the same view.' + raise ValueError(msg) + if 'bragg' in include_set and not {'measured', 'calculated'}.issubset(include_set): + msg = 'bragg requires both measured and calculated data in the same view.' + raise ValueError(msg) + if 'residual' in include_set and not {'measured', 'calculated'}.issubset(include_set): + msg = 'residual requires both measured and calculated data in the same view.' + raise ValueError(msg) + if 'excluded' in include_set and not include_set.intersection({ + 'measured', + 'calculated', + 'uncertainty', + }): + msg = 'excluded requires measured, calculated, or uncertainty data in the same view.' + raise ValueError(msg) + + def _show_point_estimate_pattern( + self, + *, + expt_name: str, + x_min: float | None, + x_max: float | None, + include: tuple[str, ...], + statuses: list[PatternOptionStatus], + x: object | None, + ) -> None: + """ + Dispatch a point-estimate pattern view to the live plotter. + """ + self._validate_requested_include(statuses, include) + include_set = set(include) + if include_set == {'measured'}: + self._project.rendering.plotter.plot_meas( + expt_name=expt_name, + x_min=x_min, + x_max=x_max, + x=x, + show_excluded=False, + ) + return + if include_set == {'measured', 'excluded'}: + self._project.rendering.plotter.plot_meas( + expt_name=expt_name, + x_min=x_min, + x_max=x_max, + x=x, + show_excluded=True, + ) + return + if include_set == {'calculated'}: + self._project.rendering.plotter.plot_calc( + expt_name=expt_name, + x_min=x_min, + x_max=x_max, + x=x, + show_excluded=False, + ) + return + if include_set == {'calculated', 'excluded'}: + self._project.rendering.plotter.plot_calc( + expt_name=expt_name, + x_min=x_min, + x_max=x_max, + x=x, + show_excluded=True, + ) + return + if {'measured', 'calculated'}.issubset(include_set): + self._project.rendering.plotter._plot_meas_vs_calc_request( + expt_name=expt_name, + plot_options=_MeasVsCalcPlotOptions( + x_min=x_min, + x_max=x_max, + show_residual='residual' in include_set, + show_background='background' in include_set, + show_bragg='bragg' in include_set, + show_excluded='excluded' in include_set, + x=x, + ), + ) + return + + msg = ( + 'Point-estimate pattern views currently support include values ' + "'measured', 'calculated', or combinations containing both." + ) + raise ValueError(msg) + + def _pattern_option_statuses(self, expt_name: str) -> list[PatternOptionStatus]: + """Return availability details for the requested experiment.""" + self._project.rendering.plotter._update_project_categories(expt_name) + experiment = self._project.experiments[expt_name] + pattern = intensity_category_for(experiment) + sample_form = experiment.type.sample_form.value + scattering_type = experiment.type.scattering_type.value + has_linked_structure = self._has_linked_structure_for_calculation(experiment) + + measured_available = self._has_nonempty_value(getattr(pattern, 'intensity_meas', None)) + calculated_available = has_linked_structure and self._has_nonempty_value( + getattr(pattern, 'intensity_calc', None) + ) + background_available = ( + sample_form == SampleFormEnum.POWDER.value + and scattering_type == ScatteringTypeEnum.BRAGG.value + and measured_available + and calculated_available + and self._has_nonempty_value(getattr(experiment, 'background', None)) + and self._has_nonempty_value(getattr(pattern, 'intensity_bkg', None)) + ) + bragg_available = ( + measured_available + and calculated_available + and sample_form == SampleFormEnum.POWDER.value + and scattering_type == ScatteringTypeEnum.BRAGG.value + and self._has_nonempty_value(getattr(experiment, 'refln', None)) + ) + residual_available = ( + sample_form == SampleFormEnum.POWDER.value + and measured_available + and calculated_available + ) + has_excluded_regions = self._has_nonempty_value( + getattr(experiment, 'excluded_regions', None) + ) + uncertainty_available, uncertainty_reason = self._uncertainty_status( + measured_available=measured_available, + sample_form=sample_form, + scattering_type=scattering_type, + ) + + auto_include = self._auto_include([ + PatternOptionStatus( + name='measured', + description=_PATTERN_OPTION_DESCRIPTIONS['measured'], + available=measured_available, + auto_included=False, + reason='', + ), + PatternOptionStatus( + name='calculated', + description=_PATTERN_OPTION_DESCRIPTIONS['calculated'], + available=calculated_available, + auto_included=False, + reason='', + ), + PatternOptionStatus( + name='background', + description=_PATTERN_OPTION_DESCRIPTIONS['background'], + available=background_available, + auto_included=False, + reason='', + ), + PatternOptionStatus( + name='residual', + description=_PATTERN_OPTION_DESCRIPTIONS['residual'], + available=residual_available, + auto_included=False, + reason='', + ), + PatternOptionStatus( + name='bragg', + description=_PATTERN_OPTION_DESCRIPTIONS['bragg'], + available=bragg_available, + auto_included=False, + reason='', + ), + PatternOptionStatus( + name='excluded', + description=_PATTERN_OPTION_DESCRIPTIONS['excluded'], + available=has_excluded_regions, + auto_included=False, + reason='', + ), + PatternOptionStatus( + name='uncertainty', + description=_PATTERN_OPTION_DESCRIPTIONS['uncertainty'], + available=uncertainty_available, + auto_included=False, + reason='', + ), + ]) + + return [ + PatternOptionStatus( + name='auto', + description=_PATTERN_OPTION_DESCRIPTIONS['auto'], + available=bool(auto_include), + auto_included=True, + reason='' if auto_include else 'No supported pattern content is available.', + ), + PatternOptionStatus( + name='measured', + description=_PATTERN_OPTION_DESCRIPTIONS['measured'], + available=measured_available, + auto_included='measured' in auto_include, + reason='' if measured_available else 'Measured intensities are unavailable.', + ), + PatternOptionStatus( + name='calculated', + description=_PATTERN_OPTION_DESCRIPTIONS['calculated'], + available=calculated_available, + auto_included='calculated' in auto_include, + reason='' if calculated_available else 'Calculated intensities are unavailable.', + ), + PatternOptionStatus( + name='background', + description=_PATTERN_OPTION_DESCRIPTIONS['background'], + available=background_available, + auto_included='background' in auto_include, + reason='' + if background_available + else ( + 'Background display currently requires powder Bragg ' + 'measured and calculated data plus defined ' + 'background points.' + ), + ), + PatternOptionStatus( + name='residual', + description=_PATTERN_OPTION_DESCRIPTIONS['residual'], + available=residual_available, + auto_included='residual' in auto_include, + reason='' + if residual_available + else ('Residuals currently require powder measured and calculated data.'), + ), + PatternOptionStatus( + name='bragg', + description=_PATTERN_OPTION_DESCRIPTIONS['bragg'], + available=bragg_available, + auto_included='bragg' in auto_include, + reason='' + if bragg_available + else ( + 'Bragg tick marks require powder Bragg measured and ' + 'calculated data with reflection rows.' + ), + ), + PatternOptionStatus( + name='excluded', + description=_PATTERN_OPTION_DESCRIPTIONS['excluded'], + available=has_excluded_regions, + auto_included='excluded' in auto_include, + reason='' + if has_excluded_regions + else ('No excluded regions are defined for this experiment.'), + ), + PatternOptionStatus( + name='uncertainty', + description=_PATTERN_OPTION_DESCRIPTIONS['uncertainty'], + available=uncertainty_available, + auto_included='uncertainty' in auto_include, + reason=uncertainty_reason, + ), + ] + + @staticmethod + def _has_nonempty_value(value: object | None) -> bool: + """Return whether a plotting input has content.""" + if value is None: + return False + + try: + return len(value) > 0 + except TypeError: + return True + + def _has_linked_structure_for_calculation(self, experiment: object) -> bool: + """Return whether the experiment links to a known structure.""" + structure_names = set(getattr(self._project.structures, 'names', ())) + + linked_phases = getattr(experiment, 'linked_phases', None) + if self._has_nonempty_value(linked_phases): + for linked_phase in linked_phases: + identity = getattr(linked_phase, '_identity', None) + category_entry_name = getattr(identity, 'category_entry_name', None) + if category_entry_name in structure_names: + return True + + linked_crystal = getattr(experiment, 'linked_crystal', None) + linked_crystal_id = getattr(getattr(linked_crystal, 'id', None), 'value', None) + return linked_crystal_id in structure_names + + def _uncertainty_status( + self, + *, + measured_available: bool, + sample_form: str, + scattering_type: str, + ) -> tuple[bool, str]: + """ + Return whether posterior predictive uncertainty is available. + """ + if not measured_available: + return False, 'Uncertainty bands require measured data.' + + supported_sample_form = sample_form == SampleFormEnum.POWDER.value or ( + sample_form == SampleFormEnum.SINGLE_CRYSTAL.value + and scattering_type == ScatteringTypeEnum.BRAGG.value + ) + if not supported_sample_form: + return ( + False, + ('Posterior predictive pattern views are unavailable for this experiment type.'), + ) + + fit_results = getattr(self._project.analysis, 'fit_results', None) + if fit_results is None: + return False, 'No fit results are available.' + + posterior_samples = getattr(fit_results, 'posterior_samples', None) + posterior_predictive = getattr(fit_results, 'posterior_predictive', None) + if posterior_samples is None or posterior_predictive is None: + return False, 'Posterior predictive data is unavailable.' + + if self._project.rendering.chart_engine.value != PlotterEngineEnum.PLOTLY.value: + return False, 'Uncertainty bands currently require the Plotly chart engine.' + + return True, '' diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index 58d7e97fb..3f3ac4fb4 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -16,8 +16,9 @@ from easydiffraction.datablocks.structure.collection import Structures from easydiffraction.io.cif.serialize import project_config_to_cif from easydiffraction.io.cif.serialize import project_to_cif -from easydiffraction.project.categories.display import Display -from easydiffraction.project.categories.display import DisplayFactory +from easydiffraction.project.categories.rendering import Rendering +from easydiffraction.project.categories.rendering import RenderingFactory +from easydiffraction.project.display import ProjectDisplay from easydiffraction.project.project_info import ProjectInfo from easydiffraction.summary.summary import Summary from easydiffraction.utils.enums import VerbosityEnum @@ -82,8 +83,9 @@ def __init__( self._info: ProjectInfo = ProjectInfo(name, title, description) self._structures = Structures() self._experiments = Experiments() - self._display = DisplayFactory.create('default') - self._display._parent = self + self._rendering = RenderingFactory.create('default') + self._rendering._parent = self + self._display = ProjectDisplay(self) self._analysis = Analysis(self) self._summary = Summary(self) self._saved = False @@ -152,8 +154,13 @@ def experiments(self, experiments: Experiments) -> None: self._experiments = experiments @property - def display(self) -> Display: - """Display configuration and facades bound to the project.""" + def rendering(self) -> Rendering: + """Rendering configuration bound to the project.""" + return self._rendering + + @property + def display(self) -> ProjectDisplay: + """Current display entry-point bound to the project.""" return self._display @property @@ -373,7 +380,7 @@ def save(self) -> None: analysis_dir = self._info.path / 'analysis' analysis_dir.mkdir(parents=True, exist_ok=True) with (analysis_dir / 'analysis.cif').open('w') as f: - f.write(self.analysis.as_cif()) + f.write(self.analysis.as_cif) console.print('├── 📁 analysis/') console.print('│ └── 📄 analysis.cif') diff --git a/src/easydiffraction/project/project_info.py b/src/easydiffraction/project/project_info.py index 94247f331..9bdfac7b0 100644 --- a/src/easydiffraction/project/project_info.py +++ b/src/easydiffraction/project/project_info.py @@ -2,6 +2,8 @@ # SPDX-License-Identifier: BSD-3-Clause """Project metadata container used by Project.""" +from __future__ import annotations + import datetime import pathlib @@ -121,6 +123,7 @@ def parameters(self) -> None: """List parameters (not implemented).""" # TODO: Consider moving to io.cif.serialize + @property def as_cif(self) -> str: """Export project metadata to CIF.""" return project_info_to_cif(self) @@ -129,6 +132,6 @@ def as_cif(self) -> str: def show_as_cif(self) -> None: """Pretty-print CIF via shared utilities.""" paragraph_title: str = f"Project 📦 '{self.name}' info as CIF" - cif_text: str = self.as_cif() + cif_text: str = self.as_cif console.paragraph(paragraph_title) render_cif(cif_text) diff --git a/tests/integration/fitting/test_analysis_and_fit_category_support.py b/tests/integration/fitting/test_analysis_and_fit_category_support.py index 7a52c63d8..0ed65e4fc 100644 --- a/tests/integration/fitting/test_analysis_and_fit_category_support.py +++ b/tests/integration/fitting/test_analysis_and_fit_category_support.py @@ -282,6 +282,7 @@ def free_parameters(self): def test_analysis_display_as_cif_and_constraints(monkeypatch, capsys): import easydiffraction.analysis.analysis as analysis_mod + import easydiffraction.analysis.categories.constraints.default as constraints_mod from easydiffraction.analysis.analysis import Analysis analysis = Analysis(project=_make_project()) @@ -302,7 +303,7 @@ class FakeConstraint: analysis.constraints._items = [FakeConstraint()] captured: dict[str, object] = {} - monkeypatch.setattr(analysis_mod, 'render_table', lambda **kwargs: captured.update(kwargs)) + monkeypatch.setattr(constraints_mod, 'render_table', lambda **kwargs: captured.update(kwargs)) analysis.display.constraints() out = capsys.readouterr().out assert 'User defined constraints' in out diff --git a/tests/integration/fitting/test_analysis_display.py b/tests/integration/fitting/test_analysis_display.py index 2f5b0e450..bc549ee42 100644 --- a/tests/integration/fitting/test_analysis_display.py +++ b/tests/integration/fitting/test_analysis_display.py @@ -1,53 +1,53 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Integration tests for Analysis display methods and CIF serialization.""" +"""Integration tests for project display reports and analysis CIF helpers.""" def test_display_all_params(lbco_fitted_project): project = lbco_fitted_project - project.analysis.display.all_params() + project.display.parameters.all() def test_display_fittable_params(lbco_fitted_project): project = lbco_fitted_project - project.analysis.display.fittable_params() + project.display.parameters.fittable() def test_display_free_params(lbco_fitted_project): project = lbco_fitted_project - project.analysis.display.free_params() + project.display.parameters.free() def test_display_how_to_access_parameters(lbco_fitted_project): project = lbco_fitted_project - project.analysis.display.how_to_access_parameters() + project.display.parameters.access() def test_display_parameter_cif_uids(lbco_fitted_project): project = lbco_fitted_project - project.analysis.display.parameter_cif_uids() + project.display.parameters.cif_uids() def test_display_constraints_empty(lbco_fitted_project): project = lbco_fitted_project - project.analysis.display.constraints() + project.analysis.constraints.show() def test_display_fit_results(lbco_fitted_project): project = lbco_fitted_project assert project.analysis.fit_results is not None - project.analysis.display.fit_results() + project.display.fit.results() def test_display_as_cif(lbco_fitted_project): project = lbco_fitted_project - project.analysis.display.as_cif() + project.analysis.show_as_cif() def test_analysis_as_cif(lbco_fitted_project): project = lbco_fitted_project - cif_text = project.analysis.as_cif() + cif_text = project.analysis.as_cif assert isinstance(cif_text, str) assert len(cif_text) > 0 diff --git a/tests/integration/fitting/test_cli_entrypoints.py b/tests/integration/fitting/test_cli_entrypoints.py index 636c0ca4c..34cc85ad8 100644 --- a/tests/integration/fitting/test_cli_entrypoints.py +++ b/tests/integration/fitting/test_cli_entrypoints.py @@ -92,16 +92,21 @@ def fit_results() -> None: analysis = _analysis() class _display: - class _plotter: + class _fit: @staticmethod - def plot_param_correlations() -> None: - calls.append('PLOT_CORR') + def results() -> None: + calls.append('DISPLAY') @staticmethod - def plot_meas_vs_calc(expt_name: str, *, show_residual: bool = False) -> None: - calls.append(f'PLOT_{expt_name}_{show_residual}') + def correlations() -> None: + calls.append('PLOT_CORR') - plotter = _plotter() + fit = _fit() + + @staticmethod + def pattern(expt_name: str, **kwargs) -> None: + del kwargs + calls.append(f'PLOT_{expt_name}_False') display = _display() @@ -116,7 +121,7 @@ def plot_meas_vs_calc(expt_name: str, *, show_residual: bool = False) -> None: result = runner.invoke(main_mod.app, ['fit', str(project_dir)]) assert result.exit_code == 0 - assert calls == ['FIT', 'DISPLAY', 'PLOT_CORR', 'PLOT_exp1_True'] + assert calls == ['FIT', 'DISPLAY', 'PLOT_CORR', 'PLOT_exp1_False'] def test_cli_fit_dry_clears_path(monkeypatch, tmp_path): @@ -146,16 +151,20 @@ def fit_results() -> None: analysis = _analysis() class _display: - class _plotter: + class _fit: @staticmethod - def plot_param_correlations() -> None: + def results() -> None: return None @staticmethod - def plot_meas_vs_calc(expt_name: str, *, show_residual: bool = False) -> None: + def correlations() -> None: return None - plotter = _plotter() + fit = _fit() + + @staticmethod + def pattern(expt_name: str, **kwargs) -> None: + del expt_name, kwargs display = _display() diff --git a/tests/integration/fitting/test_plotting.py b/tests/integration/fitting/test_plotting.py index 1cb06c41b..393e78c3d 100644 --- a/tests/integration/fitting/test_plotting.py +++ b/tests/integration/fitting/test_plotting.py @@ -1,29 +1,29 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Integration tests for the Plotter facade on a fitted project.""" +"""Integration tests for ``project.display.pattern``.""" -def test_plot_meas(lbco_fitted_project): +def test_pattern_auto(lbco_fitted_project): project = lbco_fitted_project - project.display.plotter.plot_meas(expt_name='hrpt') + project.display.pattern(expt_name='hrpt') -def test_plot_calc(lbco_fitted_project): +def test_pattern_measured(lbco_fitted_project): project = lbco_fitted_project - project.display.plotter.plot_calc(expt_name='hrpt') + project.display.pattern(expt_name='hrpt', include='measured') -def test_plot_meas_vs_calc(lbco_fitted_project): +def test_pattern_measured_vs_calculated(lbco_fitted_project): project = lbco_fitted_project - project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') + project.display.pattern(expt_name='hrpt', include=('measured', 'calculated')) -def test_plot_meas_with_range(lbco_fitted_project): +def test_pattern_with_range(lbco_fitted_project): project = lbco_fitted_project - project.display.plotter.plot_meas(expt_name='hrpt', x_min=20, x_max=80) + project.display.pattern(expt_name='hrpt', x_min=20, x_max=80) -def test_plot_meas_vs_calc_with_range(lbco_fitted_project): +def test_show_pattern_options(lbco_fitted_project): project = lbco_fitted_project - project.display.plotter.plot_meas_vs_calc(expt_name='hrpt', x_min=20, x_max=80) + project.display.show_pattern_options(expt_name='hrpt') diff --git a/tests/unit/easydiffraction/analysis/test_analysis_coverage.py b/tests/unit/easydiffraction/analysis/test_analysis_coverage.py index 089e1f6b8..9ff2926ea 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis_coverage.py +++ b/tests/unit/easydiffraction/analysis/test_analysis_coverage.py @@ -72,7 +72,7 @@ def test_empty_constraints_warns(self, capsys): assert 'No constraints' in out def test_constraints_with_items(self, capsys, monkeypatch): - import easydiffraction.analysis.analysis as mod + import easydiffraction.analysis.categories.constraints.default as constraints_mod from easydiffraction.analysis.analysis import Analysis a = Analysis(project=_make_project()) @@ -91,7 +91,7 @@ class FakeConstraint: def fake_render_table(**kwargs): captured.update(kwargs) - monkeypatch.setattr(mod, 'render_table', fake_render_table) + monkeypatch.setattr(constraints_mod, 'render_table', fake_render_table) a.display.constraints() out = capsys.readouterr().out assert 'User defined constraints' in out diff --git a/tests/unit/easydiffraction/display/test_plotting.py b/tests/unit/easydiffraction/display/test_plotting.py index c0ef16158..08540bf07 100644 --- a/tests/unit/easydiffraction/display/test_plotting.py +++ b/tests/unit/easydiffraction/display/test_plotting.py @@ -94,19 +94,43 @@ def __init__(self): p = Plotter() # Error paths (now log errors via console; messages are printed) - p._plot_meas_data(Ptn(two_theta=None, intensity_meas=None), 'E', ExptType()) + p._plot_meas_data( + object(), + Ptn(two_theta=None, intensity_meas=None), + 'E', + ExptType(), + _MeasVsCalcPlotOptions(), + ) out = capsys.readouterr().out assert 'No two_theta data available for experiment E' in out - p._plot_meas_data(Ptn(two_theta=[1], intensity_meas=None), 'E', ExptType()) + p._plot_meas_data( + object(), + Ptn(two_theta=[1], intensity_meas=None), + 'E', + ExptType(), + _MeasVsCalcPlotOptions(), + ) out = capsys.readouterr().out assert 'No measured data available for experiment E' in out - p._plot_calc_data(Ptn(two_theta=None, intensity_calc=None), 'E', ExptType()) + p._plot_calc_data( + object(), + Ptn(two_theta=None, intensity_calc=None), + 'E', + ExptType(), + _MeasVsCalcPlotOptions(), + ) out = capsys.readouterr().out assert 'No two_theta data available for experiment E' in out - p._plot_calc_data(Ptn(two_theta=[1], intensity_calc=None), 'E', ExptType()) + p._plot_calc_data( + object(), + Ptn(two_theta=[1], intensity_calc=None), + 'E', + ExptType(), + _MeasVsCalcPlotOptions(), + ) out = capsys.readouterr().out assert 'No calculated data available for experiment E' in out @@ -154,13 +178,24 @@ def test_plotter_routes_to_ascii_plotter(monkeypatch): from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum from easydiffraction.display.plotting import Plotter + from easydiffraction.display.plotting import _MeasVsCalcPlotOptions called = {} - def fake_plot_powder(self, x, y_series, labels, axes_labels, title, height=None): + def fake_plot_powder( + self, + x, + y_series, + labels, + axes_labels, + title, + height=None, + excluded_ranges=(), + ): called['labels'] = tuple(labels) called['axes'] = tuple(axes_labels) called['title'] = title + called['excluded_ranges'] = excluded_ranges monkeypatch.setattr(ascii_mod.AsciiPlotter, 'plot_powder', fake_plot_powder) @@ -178,9 +213,16 @@ def __init__(self): p = Plotter() p.engine = 'asciichartpy' # ensure AsciiPlotter - p._plot_meas_data(Ptn(), 'E', ExptType()) + p._plot_meas_data( + object(), + Ptn(), + 'E', + ExptType(), + _MeasVsCalcPlotOptions(), + ) assert called['labels'] == ('meas',) assert 'Measured data' in called['title'] + assert called['excluded_ranges'] == () def test_extract_bragg_tick_sets_groups_and_filters(): @@ -742,7 +784,15 @@ class Experiment: plotter._plot_posterior_predictive_data( experiment=Experiment(), expt_name='hrpt', - plot_options=SimpleNamespace(x_min=None, x_max=None, show_residual=None, x=None), + plot_options=SimpleNamespace( + x_min=None, + x_max=None, + show_residual=None, + show_background=None, + show_bragg=None, + show_excluded=False, + x=None, + ), x_axis=XAxisType.TWO_THETA, style='band', ) @@ -752,6 +802,73 @@ class Experiment: assert plot_spec.y_calc_line_dash == 'dot' +def test_plot_meas_vs_calc_request_respects_background_and_bragg_flags(): + from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum + from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum + from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum + from easydiffraction.display.plotting import Plotter + from easydiffraction.display.plotting import _MeasVsCalcPlotOptions + + captured: dict[str, object] = {} + + class FakeBackend: + def plot_powder_meas_vs_calc(self, *, plot_spec): + captured['plot_spec'] = plot_spec + + class Pattern: + two_theta = np.array([1.0, 2.0, 3.0]) + intensity_meas = np.array([10.0, 12.0, 11.0]) + intensity_calc = np.array([9.0, 11.0, 10.5]) + intensity_bkg = np.array([1.0, 1.0, 1.0]) + + class Refln: + phase_id = np.array(['phase-a']) + two_theta = np.array([2.0]) + index_h = np.array([1]) + index_k = np.array([0]) + index_l = np.array([1]) + f_squared_calc = np.array([50.0]) + f_calc = np.array([7.0]) + + class ExptType: + sample_form = type('SF', (), {'value': SampleFormEnum.POWDER})() + scattering_type = type('S', (), {'value': ScatteringTypeEnum.BRAGG})() + beam_mode = type('B', (), {'value': BeamModeEnum.CONSTANT_WAVELENGTH})() + + class Experiment: + data = Pattern() + type = ExptType() + refln = Refln() + + plotter = Plotter() + plotter._backend = FakeBackend() + plotter._plot_meas_vs_calc_data( + experiment=Experiment(), + expt_name='E1', + plot_options=_MeasVsCalcPlotOptions( + show_background=False, + show_bragg=False, + ), + ) + + plot_spec = captured['plot_spec'] + assert plot_spec.y_bkg is None + assert plot_spec.bragg_tick_sets == () + + plotter._plot_meas_vs_calc_data( + experiment=Experiment(), + expt_name='E1', + plot_options=_MeasVsCalcPlotOptions( + show_background=True, + show_bragg=True, + ), + ) + + plot_spec = captured['plot_spec'] + assert np.allclose(plot_spec.y_bkg, np.array([1.0, 1.0, 1.0])) + assert len(plot_spec.bragg_tick_sets) == 1 + + def test_build_param_distribution_plot_accepts_unique_name_string(): plotter, fit_results, posterior_samples = _make_bayesian_plotter_fixture() unique_name = 'phase.cell.length_a' @@ -1105,6 +1222,7 @@ def fake_plot_summary( axes_labels, show_band, show_draws, + excluded_ranges, ): captured['expt_name'] = expt_name captured['summary'] = summary @@ -1112,6 +1230,7 @@ def fake_plot_summary( captured['axes_labels'] = axes_labels captured['show_band'] = show_band captured['show_draws'] = show_draws + captured['excluded_ranges'] = excluded_ranges monkeypatch.setattr(Plotter, '_plot_posterior_predictive_summary', fake_plot_summary) @@ -1125,6 +1244,7 @@ def fake_plot_summary( np.testing.assert_allclose(captured['y_meas'], np.array([20.0])) assert captured['show_band'] is True assert captured['show_draws'] is False + assert captured['excluded_ranges'] == () assert any('ignoring show_residual=True' in warning for warning in warnings) diff --git a/tests/unit/easydiffraction/project/categories/display/test_default.py b/tests/unit/easydiffraction/project/categories/display/test_default.py deleted file mode 100644 index c43171e31..000000000 --- a/tests/unit/easydiffraction/project/categories/display/test_default.py +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - -import gemmi - - -def test_display_defaults(): - from easydiffraction.project.categories.display.default import Display - - display = Display() - - assert display.type_info.tag == 'default' - assert display._identity.category_code == 'display' - assert display.plotter_type.value == display.plotter.engine - assert display.tabler_type.value == display.tabler.engine - - -def test_display_plotter_binds_parent(): - from easydiffraction.project.categories.display.default import Display - - display = Display() - parent = object() - display._parent = parent - - plotter = display.plotter - - assert plotter._project is parent - - -def test_display_setters_update_engines(): - from easydiffraction.project.categories.display.default import Display - - display = Display() - - display.plotter_type = 'plotly' - display.tabler_type = 'rich' - - assert display.plotter_type.value == 'plotly' - assert display.plotter.engine == 'plotly' - assert display.tabler_type.value == 'rich' - assert display.tabler.engine == 'rich' - - -def test_display_from_cif_restores_types(): - from easydiffraction.project.categories.display.default import Display - - display = Display() - block = gemmi.cif.read_string( - 'data_test\n_display.plotter_type plotly\n_display.tabler_type rich\n', - ).sole_block() - - display.from_cif(block) - - assert display.plotter_type.value == 'plotly' - assert display.tabler_type.value == 'rich' diff --git a/tests/unit/easydiffraction/project/categories/display/test_factory.py b/tests/unit/easydiffraction/project/categories/display/test_factory.py deleted file mode 100644 index b335a0530..000000000 --- a/tests/unit/easydiffraction/project/categories/display/test_factory.py +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - -import pytest - - -def test_display_factory_default_and_create(): - from easydiffraction.project.categories.display.default import Display - from easydiffraction.project.categories.display.factory import DisplayFactory - - assert DisplayFactory.default_tag() == 'default' - assert 'default' in DisplayFactory.supported_tags() - - display = DisplayFactory.create('default') - - assert isinstance(display, Display) - - -def test_display_factory_rejects_unknown_tag(): - from easydiffraction.project.categories.display.factory import DisplayFactory - - with pytest.raises(ValueError, match=r"Unsupported type: 'missing'"): - DisplayFactory.create('missing') diff --git a/tests/unit/easydiffraction/project/categories/rendering/test_default.py b/tests/unit/easydiffraction/project/categories/rendering/test_default.py new file mode 100644 index 000000000..edb884272 --- /dev/null +++ b/tests/unit/easydiffraction/project/categories/rendering/test_default.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +import gemmi + + +def test_rendering_defaults(): + from easydiffraction.project.categories.rendering.default import Rendering + + rendering = Rendering() + + assert rendering.type_info.tag == 'default' + assert rendering._identity.category_code == 'rendering' + assert rendering.chart_engine.value == rendering.plotter.engine + assert rendering.table_engine.value == rendering.tabler.engine + + +def test_rendering_plotter_binds_parent(): + from easydiffraction.project.categories.rendering.default import Rendering + + rendering = Rendering() + parent = object() + rendering._parent = parent + + plotter = rendering.plotter + + assert plotter._project is parent + + +def test_rendering_setters_update_engines(): + from easydiffraction.project.categories.rendering.default import Rendering + + rendering = Rendering() + + rendering.chart_engine = 'plotly' + rendering.table_engine = 'rich' + + assert rendering.chart_engine.value == 'plotly' + assert rendering.plotter.engine == 'plotly' + assert rendering.table_engine.value == 'rich' + assert rendering.tabler.engine == 'rich' + + +def test_rendering_from_cif_restores_types(): + from easydiffraction.project.categories.rendering.default import Rendering + + rendering = Rendering() + block = gemmi.cif.read_string( + 'data_test\n_rendering.chart_engine plotly\n_rendering.table_engine rich\n', + ).sole_block() + + rendering.from_cif(block) + + assert rendering.chart_engine.value == 'plotly' + assert rendering.table_engine.value == 'rich' diff --git a/tests/unit/easydiffraction/project/categories/rendering/test_factory.py b/tests/unit/easydiffraction/project/categories/rendering/test_factory.py new file mode 100644 index 000000000..0f2812e5c --- /dev/null +++ b/tests/unit/easydiffraction/project/categories/rendering/test_factory.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_rendering_factory_default_and_create(): + from easydiffraction.project.categories.rendering.default import Rendering + from easydiffraction.project.categories.rendering.factory import RenderingFactory + + assert RenderingFactory.default_tag() == 'default' + assert 'default' in RenderingFactory.supported_tags() + + rendering = RenderingFactory.create('default') + + assert isinstance(rendering, Rendering) + + +def test_rendering_factory_rejects_unknown_tag(): + from easydiffraction.project.categories.rendering.factory import RenderingFactory + + with pytest.raises(ValueError, match=r"Unsupported type: 'missing'"): + RenderingFactory.create('missing') diff --git a/tests/unit/easydiffraction/project/test_display.py b/tests/unit/easydiffraction/project/test_display.py new file mode 100644 index 000000000..46f22296a --- /dev/null +++ b/tests/unit/easydiffraction/project/test_display.py @@ -0,0 +1,479 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Unit tests for project/display.py.""" + +from __future__ import annotations + +from types import SimpleNamespace + +import pytest + +from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum +from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum +from easydiffraction.display.plotting import _MeasVsCalcPlotOptions +from easydiffraction.project.display import PatternOptionStatus +from easydiffraction.project.display import ProjectDisplay + + +def _make_project_stub() -> tuple[SimpleNamespace, list[tuple[str, tuple, dict]]]: + calls: list[tuple[str, tuple, dict]] = [] + + def record(name: str): + def _recorder(*args, **kwargs): + calls.append((name, args, kwargs)) + + return _recorder + + analysis_display = SimpleNamespace( + all_params=record('all_params'), + fittable_params=record('fittable_params'), + free_params=record('free_params'), + how_to_access_parameters=record('how_to_access_parameters'), + parameter_cif_uids=record('parameter_cif_uids'), + fit_results=record('fit_results'), + ) + plotter = SimpleNamespace( + plot_param_correlations=record('plot_param_correlations'), + plot_param_series=record('plot_param_series'), + plot_posterior_pairs=record('plot_posterior_pairs'), + plot_param_distribution=record('plot_param_distribution'), + plot_posterior_predictive=record('plot_posterior_predictive'), + _plot_posterior_predictive_request=record('_plot_posterior_predictive_request'), + plot_meas=record('plot_meas'), + plot_calc=record('plot_calc'), + plot_meas_vs_calc=record('plot_meas_vs_calc'), + _plot_meas_vs_calc_request=record('_plot_meas_vs_calc_request'), + ) + project = SimpleNamespace( + analysis=SimpleNamespace(display=analysis_display), + rendering=SimpleNamespace(plotter=plotter), + ) + return project, calls + + +def _make_statuses( + *, + measured: bool = False, + calculated: bool = False, + background: bool = False, + residual: bool = False, + bragg: bool = False, + excluded: bool = False, + uncertainty: bool = False, + uncertainty_reason: str = 'Posterior predictive data is unavailable.', +) -> list[PatternOptionStatus]: + return [ + PatternOptionStatus( + name='auto', + description='auto', + available=True, + auto_included=True, + reason='', + ), + PatternOptionStatus( + name='measured', + description='measured', + available=measured, + auto_included=False, + reason='' if measured else 'Measured unavailable', + ), + PatternOptionStatus( + name='calculated', + description='calculated', + available=calculated, + auto_included=False, + reason='' if calculated else 'Calculated unavailable', + ), + PatternOptionStatus( + name='background', + description='background', + available=background, + auto_included=False, + reason='' if background else 'Background unavailable', + ), + PatternOptionStatus( + name='residual', + description='residual', + available=residual, + auto_included=False, + reason='' if residual else 'Residual unavailable', + ), + PatternOptionStatus( + name='bragg', + description='bragg', + available=bragg, + auto_included=False, + reason='' if bragg else 'Bragg unavailable', + ), + PatternOptionStatus( + name='excluded', + description='excluded', + available=excluded, + auto_included=False, + reason='' if excluded else 'Excluded unavailable', + ), + PatternOptionStatus( + name='uncertainty', + description='uncertainty', + available=uncertainty, + auto_included=False, + reason='' if uncertainty else uncertainty_reason, + ), + ] + + +def test_parameter_display_delegates_to_analysis_display(): + project, calls = _make_project_stub() + display = ProjectDisplay(project) + + display.parameters.all() + display.parameters.fittable() + display.parameters.free() + display.parameters.access() + display.parameters.cif_uids() + + assert [name for name, _args, _kwargs in calls] == [ + 'all_params', + 'fittable_params', + 'free_params', + 'how_to_access_parameters', + 'parameter_cif_uids', + ] + + +def test_fit_display_delegates_to_analysis_and_rendering(): + project, calls = _make_project_stub() + display = ProjectDisplay(project) + + display.fit.results() + display.fit.correlations( + threshold=0.75, + precision=3, + max_parameters=4, + show_diagonal=False, + ) + display.fit.series(param='scale', versus='temperature') + + assert calls[0] == ('fit_results', (), {}) + assert calls[1] == ( + 'plot_param_correlations', + (), + { + 'threshold': 0.75, + 'precision': 3, + 'max_parameters': 4, + 'show_diagonal': False, + }, + ) + assert calls[2] == ( + 'plot_param_series', + (), + {'param': 'scale', 'versus': 'temperature'}, + ) + + +def test_posterior_display_delegates_to_rendering_plotter(): + project, calls = _make_project_stub() + display = ProjectDisplay(project) + + display.posterior.pairs(parameters=['a'], threshold=0.5, max_parameters=3) + display.posterior.distribution('a') + display.posterior.predictive( + 'hrpt', + style='draws', + x_min=1.0, + x_max=2.0, + show_residual=True, + x='d_spacing', + ) + + assert calls[0][0] == 'plot_posterior_pairs' + assert calls[0][2]['parameters'] == ['a'] + assert calls[0][2]['threshold'] == 0.5 + assert calls[0][2]['max_parameters'] == 3 + assert calls[1] == ('plot_param_distribution', ('a',), {}) + assert calls[2] == ( + 'plot_posterior_predictive', + (), + { + 'expt_name': 'hrpt', + 'style': 'draws', + 'x_min': 1.0, + 'x_max': 2.0, + 'show_residual': True, + 'x': 'd_spacing', + }, + ) + + +def test_pattern_auto_routes_measured_and_excluded_to_plot_meas(): + project, calls = _make_project_stub() + display = ProjectDisplay(project) + display._pattern_option_statuses = lambda expt_name: _make_statuses( + measured=True, + excluded=True, + ) + + display.pattern('hrpt') + + assert calls == [ + ( + 'plot_meas', + (), + { + 'expt_name': 'hrpt', + 'x_min': None, + 'x_max': None, + 'x': None, + 'show_excluded': True, + }, + ) + ] + + +def test_pattern_uncertainty_routes_to_posterior_predictive(): + project, calls = _make_project_stub() + display = ProjectDisplay(project) + display._pattern_option_statuses = lambda expt_name: _make_statuses( + measured=True, + calculated=True, + residual=True, + excluded=True, + uncertainty=True, + ) + + display.pattern( + 'hrpt', + x_min=1.0, + x_max=2.0, + include=('measured', 'calculated', 'uncertainty', 'residual', 'excluded'), + ) + + assert calls == [ + ( + '_plot_posterior_predictive_request', + (), + { + 'expt_name': 'hrpt', + 'style': 'band', + 'plot_options': _MeasVsCalcPlotOptions( + x_min=1.0, + x_max=2.0, + show_residual=True, + show_background=False, + show_bragg=False, + show_excluded=True, + x=None, + ), + }, + ) + ] + + +def test_pattern_measured_and_calculated_suppresses_background_and_bragg(): + project, calls = _make_project_stub() + display = ProjectDisplay(project) + display._pattern_option_statuses = lambda expt_name: _make_statuses( + measured=True, + calculated=True, + background=True, + bragg=True, + ) + + display.pattern('hrpt', include=('measured', 'calculated')) + + assert calls == [ + ( + '_plot_meas_vs_calc_request', + (), + { + 'expt_name': 'hrpt', + 'plot_options': _MeasVsCalcPlotOptions( + x_min=None, + x_max=None, + show_residual=False, + show_background=False, + show_bragg=False, + show_excluded=False, + x=None, + ), + }, + ) + ] + + +def test_pattern_measured_and_calculated_can_enable_background_and_bragg(): + project, calls = _make_project_stub() + display = ProjectDisplay(project) + display._pattern_option_statuses = lambda expt_name: _make_statuses( + measured=True, + calculated=True, + background=True, + residual=True, + bragg=True, + excluded=True, + ) + + display.pattern( + 'hrpt', + include=('measured', 'calculated', 'background', 'residual', 'bragg', 'excluded'), + ) + + assert calls == [ + ( + '_plot_meas_vs_calc_request', + (), + { + 'expt_name': 'hrpt', + 'plot_options': _MeasVsCalcPlotOptions( + x_min=None, + x_max=None, + show_residual=True, + show_background=True, + show_bragg=True, + show_excluded=True, + x=None, + ), + }, + ) + ] + + +def test_pattern_option_statuses_ignore_placeholder_arrays_without_usable_state(monkeypatch): + pattern = SimpleNamespace( + intensity_meas=[1.0, 2.0], + intensity_calc=[0.0, 0.0], + intensity_bkg=[0.0, 0.0], + ) + + experiment = SimpleNamespace( + type=SimpleNamespace( + sample_form=SimpleNamespace(value=SampleFormEnum.POWDER.value), + scattering_type=SimpleNamespace(value=ScatteringTypeEnum.BRAGG.value), + ), + linked_phases=[], + background=[], + refln=[], + excluded_regions=[], + ) + project = SimpleNamespace( + experiments={'hrpt': experiment}, + structures=SimpleNamespace(names=['phase-a']), + analysis=SimpleNamespace(fit_results=None), + rendering=SimpleNamespace( + plotter=SimpleNamespace(_update_project_categories=lambda expt_name: None), + chart_engine=SimpleNamespace(value='plotly'), + ), + ) + display = ProjectDisplay(project) + + monkeypatch.setattr( + 'easydiffraction.project.display.intensity_category_for', lambda expt: pattern + ) + + statuses = {status.name: status for status in display._pattern_option_statuses('hrpt')} + + assert statuses['measured'].available is True + assert statuses['calculated'].available is False + assert statuses['background'].available is False + assert statuses['bragg'].available is False + assert statuses['measured'].auto_included is True + assert statuses['calculated'].auto_included is False + + +def test_pattern_auto_routes_single_crystal_with_calculated_data(monkeypatch): + calls: list[tuple[str, tuple, dict]] = [] + + def record(name: str): + def _recorder(*args, **kwargs): + calls.append((name, args, kwargs)) + + return _recorder + + pattern = SimpleNamespace( + intensity_meas=[10.0, 12.0], + intensity_calc=[9.5, 11.5], + ) + experiment = SimpleNamespace( + type=SimpleNamespace( + sample_form=SimpleNamespace(value=SampleFormEnum.SINGLE_CRYSTAL.value), + scattering_type=SimpleNamespace(value=ScatteringTypeEnum.BRAGG.value), + ), + linked_crystal=SimpleNamespace(id=SimpleNamespace(value='si')), + excluded_regions=[], + ) + project = SimpleNamespace( + experiments={'heidi': experiment}, + structures=SimpleNamespace(names=['si']), + analysis=SimpleNamespace(fit_results=None), + rendering=SimpleNamespace( + plotter=SimpleNamespace( + _update_project_categories=lambda expt_name: None, + _plot_meas_vs_calc_request=record('_plot_meas_vs_calc_request'), + ), + chart_engine=SimpleNamespace(value='plotly'), + ), + ) + display = ProjectDisplay(project) + + monkeypatch.setattr( + 'easydiffraction.project.display.intensity_category_for', + lambda expt: pattern, + ) + + display.pattern('heidi') + + assert calls == [ + ( + '_plot_meas_vs_calc_request', + (), + { + 'expt_name': 'heidi', + 'plot_options': _MeasVsCalcPlotOptions( + x_min=None, + x_max=None, + show_residual=False, + show_background=False, + show_bragg=False, + show_excluded=False, + x=None, + ), + }, + ) + ] + + +def test_pattern_rejects_excluded_with_custom_x(): + project, _calls = _make_project_stub() + display = ProjectDisplay(project) + display._pattern_option_statuses = lambda expt_name: _make_statuses( + measured=True, + excluded=True, + ) + + with pytest.raises(ValueError, match='default x-axis'): + display.pattern('hrpt', include=('measured', 'excluded'), x='d_spacing') + + +def test_show_pattern_options_renders_table(monkeypatch): + project, _calls = _make_project_stub() + display = ProjectDisplay(project) + display._pattern_option_statuses = lambda expt_name: _make_statuses( + measured=True, + calculated=True, + ) + captured: dict[str, object] = {} + + def fake_render_table(*, columns_headers, columns_alignment, columns_data): + captured['columns_headers'] = columns_headers + captured['columns_alignment'] = columns_alignment + captured['columns_data'] = columns_data + + monkeypatch.setattr('easydiffraction.project.display.render_table', fake_render_table) + + display.show_pattern_options('hrpt') + + assert captured['columns_headers'] == ['Option', 'Description', 'Available', 'Auto', 'Reason'] + assert captured['columns_alignment'] == ['left', 'left', 'center', 'center', 'left'] + assert captured['columns_data'][0][0] == 'auto' + assert captured['columns_data'][1][0] == 'measured' diff --git a/tests/unit/easydiffraction/project/test_project.py b/tests/unit/easydiffraction/project/test_project.py index 090ce5404..e0ff677dd 100644 --- a/tests/unit/easydiffraction/project/test_project.py +++ b/tests/unit/easydiffraction/project/test_project.py @@ -63,3 +63,14 @@ def test_project_free_params_aggregate_structures_and_experiments(): project._experiments = SimpleNamespace(free_parameters=[experiment_param]) assert project.free_parameters == [structure_param, experiment_param] + + +def test_project_exposes_rendering_and_display_facades(): + from easydiffraction.project.categories.rendering import Rendering + from easydiffraction.project.display import ProjectDisplay + from easydiffraction.project.project import Project + + project = Project() + + assert isinstance(project.rendering, Rendering) + assert isinstance(project.display, ProjectDisplay) diff --git a/tests/unit/easydiffraction/project/test_project_load.py b/tests/unit/easydiffraction/project/test_project_load.py index c788f0a74..448345777 100644 --- a/tests/unit/easydiffraction/project/test_project_load.py +++ b/tests/unit/easydiffraction/project/test_project_load.py @@ -81,16 +81,16 @@ def test_round_trips_fit_mode(self, tmp_path): assert loaded.analysis.fit.mode.value == 'joint' - def test_round_trips_display_configuration(self, tmp_path): + def test_round_trips_rendering_configuration(self, tmp_path): original = Project(name='d1') - original.display.plotter_type = 'asciichartpy' - original.display.tabler_type = 'rich' + original.rendering.chart_engine = 'asciichartpy' + original.rendering.table_engine = 'rich' original.save_as(str(tmp_path / 'proj')) loaded = Project.load(str(tmp_path / 'proj')) - assert loaded.display.plotter_type.value == 'asciichartpy' - assert loaded.display.tabler_type.value == 'rich' + assert loaded.rendering.chart_engine.value == 'asciichartpy' + assert loaded.rendering.table_engine.value == 'rich' def test_round_trips_constraints(self, tmp_path): original = Project(name='c1') diff --git a/tests/unit/easydiffraction/project/test_project_save.py b/tests/unit/easydiffraction/project/test_project_save.py index bf632e11c..08292525d 100644 --- a/tests/unit/easydiffraction/project/test_project_save.py +++ b/tests/unit/easydiffraction/project/test_project_save.py @@ -24,8 +24,8 @@ def test_project_save_as_writes_core_files(tmp_path, monkeypatch): from easydiffraction.summary.summary import Summary # Monkeypatch as_cif producers to avoid heavy internals - monkeypatch.setattr(ProjectInfo, 'as_cif', lambda self: 'info') - monkeypatch.setattr(Analysis, 'as_cif', lambda self: 'analysis') + monkeypatch.setattr(ProjectInfo, 'as_cif', property(lambda self: 'info')) + monkeypatch.setattr(Analysis, 'as_cif', property(lambda self: 'analysis')) monkeypatch.setattr(Summary, 'as_cif', lambda self: 'summary') p = Project(name='p1') diff --git a/tests/unit/easydiffraction/test___main__.py b/tests/unit/easydiffraction/test___main__.py index 76d150c6d..9edd2bc27 100644 --- a/tests/unit/easydiffraction/test___main__.py +++ b/tests/unit/easydiffraction/test___main__.py @@ -95,16 +95,21 @@ def fit_results(): analysis = _analysis() class _display: - class _plotter: + class _fit: @staticmethod - def plot_param_correlations(): - calls.append('PLOT_CORR') + def results(): + calls.append('DISPLAY') @staticmethod - def plot_meas_vs_calc(expt_name, *, show_residual=False): - calls.append(f'PLOT_{expt_name}_{show_residual}') + def correlations(): + calls.append('PLOT_CORR') - plotter = _plotter() + fit = _fit() + + @staticmethod + def pattern(expt_name, **kwargs): + del kwargs + calls.append(f'PLOT_{expt_name}_False') display = _display() @@ -119,7 +124,7 @@ def plot_meas_vs_calc(expt_name, *, show_residual=False): result = runner.invoke(main_mod.app, ['fit', str(proj_dir)]) assert result.exit_code == 0 - assert calls == ['FIT', 'DISPLAY', 'PLOT_CORR', 'PLOT_exp1_True'] + assert calls == ['FIT', 'DISPLAY', 'PLOT_CORR', 'PLOT_exp1_False'] def test_cli_fit_dry_clears_path(monkeypatch, tmp_path): @@ -149,16 +154,20 @@ def fit_results(): analysis = _analysis() class _display: - class _plotter: + class _fit: @staticmethod - def plot_param_correlations(): + def results(): pass @staticmethod - def plot_meas_vs_calc(expt_name, *, show_residual=False): + def correlations(): pass - plotter = _plotter() + fit = _fit() + + @staticmethod + def pattern(expt_name, **kwargs): + del expt_name, kwargs display = _display()