PyMieSimX 0.1.0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/.github/workflows/quality.yml +1 -1
- pymiesimx-0.1.1/PKG-INFO +133 -0
- pymiesimx-0.1.1/PyMieSimX/__init__.py +58 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/__main__.py +17 -6
- pymiesimx-0.1.1/PyMieSimX/api.py +39 -0
- pymiesimx-0.1.1/PyMieSimX/application/__init__.py +1 -0
- pymiesimx-0.1.1/PyMieSimX/application/wsgi.py +9 -0
- pymiesimx-0.1.1/PyMieSimX/gui/__init__.py +12 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/assets/dashboard.css +112 -1
- pymiesimx-0.1.1/PyMieSimX/gui/assets/materials-stock.json +142 -0
- pymiesimx-0.1.1/PyMieSimX/gui/callback_helpers.py +112 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/interface.py +212 -68
- pymiesimx-0.1.1/PyMieSimX/gui/jobs.py +118 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/layout.py +72 -3
- pymiesimx-0.1.1/PyMieSimX/gui/material_catalog.py +90 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/__init__.py +3 -1
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/documentation.py +3 -60
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/experiment/page.py +2 -2
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/experiment/sections/configuration.py +3 -3
- pymiesimx-0.1.1/PyMieSimX/gui/pages/field_syntax.py +77 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/home.py +3 -3
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/install_local.py +7 -4
- pymiesimx-0.1.1/PyMieSimX/gui/pages/sellmeier.py +169 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/single/page.py +1 -1
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/single/sections/representations.py +1 -1
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/single/sections/setup.py +2 -2
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/parsing.py +41 -6
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/schemas.py +7 -7
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/services.py +255 -9
- pymiesimx-0.1.1/PyMieSimX/gui/usage_metrics.py +225 -0
- pymiesimx-0.1.1/PyMieSimX.egg-info/PKG-INFO +133 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX.egg-info/SOURCES.txt +15 -1
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX.egg-info/requires.txt +6 -5
- pymiesimx-0.1.1/PyMieSimX.egg-info/scm_file_list.json +67 -0
- pymiesimx-0.1.1/PyMieSimX.egg-info/scm_version.json +8 -0
- pymiesimx-0.1.1/README.rst +106 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/meta.yaml +6 -5
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/pyproject.toml +12 -7
- pymiesimx-0.1.1/tests/gui/test_api.py +59 -0
- pymiesimx-0.1.1/tests/gui/test_services.py +221 -0
- pymiesimx-0.1.1/tests/gui/test_usage_metrics.py +30 -0
- pymiesimx-0.1.0/PKG-INFO +0 -90
- pymiesimx-0.1.0/PyMieSimX/__init__.py +0 -5
- pymiesimx-0.1.0/PyMieSimX/gui/__init__.py +0 -5
- pymiesimx-0.1.0/PyMieSimX.egg-info/PKG-INFO +0 -90
- pymiesimx-0.1.0/README.rst +0 -43
- pymiesimx-0.1.0/tests/gui/test_services.py +0 -87
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/.coveragerc +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/.flake8 +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/.github/dependabot.yml +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/.github/workflows/deploy_PyPi.yml +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/.github/workflows/deploy_anaconda.yml +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/.github/workflows/deploy_coverage.yml +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/.gitignore +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/.pre-commit-config.yaml +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/Dockerfile +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/LICENSE +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/assets/card-state.js +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/assets/default-settings.json +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/assets/logo.png +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/assets/pymiesim-favicon.svg +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/assets/pymiesim-logo-dark.svg +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/assets/pymiesim-logo.svg +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/components/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/components/cards.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/defaults.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/citation.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/experiment/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/experiment/sections/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/settings.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/single/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX/gui/pages/single/sections/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX.egg-info/dependency_links.txt +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX.egg-info/entry_points.txt +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/PyMieSimX.egg-info/top_level.txt +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/makefile +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/pytest.ini +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/setup.cfg +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.1}/tests/gui/cli.py +0 -0
pymiesimx-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyMieSimX
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Graphical Dash interface for PyMieSim.
|
|
5
|
+
Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/MartinPdeS/PyMieSimX
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/x-rst
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: PyMieSim<6,>=5.1.2
|
|
12
|
+
Requires-Dist: dash<5,>=4.1.0
|
|
13
|
+
Requires-Dist: gunicorn<24,>=23.0.0
|
|
14
|
+
Requires-Dist: numpy<3,>=2.0
|
|
15
|
+
Requires-Dist: pandas<3,>=2.2
|
|
16
|
+
Requires-Dist: plotly<7,>=5.24
|
|
17
|
+
Requires-Dist: psycopg[binary]<4,>=3.2.9
|
|
18
|
+
Provides-Extra: testing
|
|
19
|
+
Requires-Dist: pytest>=8.0; extra == "testing"
|
|
20
|
+
Requires-Dist: pytest-cov>=5.0; extra == "testing"
|
|
21
|
+
Requires-Dist: coverage>=7.0; extra == "testing"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
24
|
+
Requires-Dist: mypy==1.15.0; extra == "dev"
|
|
25
|
+
Requires-Dist: ruff==0.11.2; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
PyMieSimX
|
|
29
|
+
=========
|
|
30
|
+
|
|
31
|
+
PyMieSimX is the standalone graphical interface for `PyMieSim
|
|
32
|
+
<https://github.com/MartinPdeS/PyMieSim>`_. It provides a Dash application for
|
|
33
|
+
configuring optical setups, running parameter sweeps, exploring individual
|
|
34
|
+
particles, and exporting results.
|
|
35
|
+
|
|
36
|
+
Source-model note
|
|
37
|
+
-----------------
|
|
38
|
+
|
|
39
|
+
The Gaussian source exposed by PyMieSimX is not a generalized Lorenz--Mie
|
|
40
|
+
theory (GLMT) implementation. It is a convenience object for defining a
|
|
41
|
+
Gaussian illumination through its numerical aperture and optical power in
|
|
42
|
+
watts. The ``Gaussian`` and ``GaussianSet`` source options should therefore be
|
|
43
|
+
interpreted as a practical source parameterization, not as a separate GLMT
|
|
44
|
+
solver.
|
|
45
|
+
|
|
46
|
+
Installation
|
|
47
|
+
------------
|
|
48
|
+
|
|
49
|
+
Install the GUI and its PyMieSim dependency with::
|
|
50
|
+
|
|
51
|
+
pip install PyMieSimX
|
|
52
|
+
|
|
53
|
+
Launch the dashboard with::
|
|
54
|
+
|
|
55
|
+
pymiesimx
|
|
56
|
+
|
|
57
|
+
Use ``pymiesimx --help`` to see the available host, port, browser, and debug
|
|
58
|
+
options.
|
|
59
|
+
|
|
60
|
+
Development
|
|
61
|
+
-----------
|
|
62
|
+
|
|
63
|
+
Install an editable checkout with::
|
|
64
|
+
|
|
65
|
+
pip install -e .
|
|
66
|
+
|
|
67
|
+
The GUI source lives in ``PyMieSimX/gui``. Scientific calculations are provided
|
|
68
|
+
by the installed ``PyMieSim`` package rather than duplicated here.
|
|
69
|
+
|
|
70
|
+
Testing and automation
|
|
71
|
+
----------------------
|
|
72
|
+
|
|
73
|
+
Run the GUI test suite with::
|
|
74
|
+
|
|
75
|
+
pip install -e ".[testing]"
|
|
76
|
+
python -m pytest
|
|
77
|
+
|
|
78
|
+
GitHub Actions includes quality checks, GUI tests, PyPI publishing, Conda
|
|
79
|
+
recipe publishing, and coverage deployment. The Conda recipe is in
|
|
80
|
+
``meta.yaml`` and the container entry point is defined in ``Dockerfile``.
|
|
81
|
+
|
|
82
|
+
Python API
|
|
83
|
+
----------
|
|
84
|
+
|
|
85
|
+
The computational API can be used without constructing the Dash application::
|
|
86
|
+
|
|
87
|
+
from PyMieSimX import run_experiment
|
|
88
|
+
|
|
89
|
+
result = run_experiment(
|
|
90
|
+
source_type="GaussianSet",
|
|
91
|
+
source_values={
|
|
92
|
+
"wavelength": "650",
|
|
93
|
+
"polarization": "0",
|
|
94
|
+
"optical_power": "1e-3",
|
|
95
|
+
"numerical_aperture": "0.2",
|
|
96
|
+
},
|
|
97
|
+
scatterer_type="SphereSet",
|
|
98
|
+
scatterer_values={"diameter": "500", "material": "1.4", "medium": "1.0"},
|
|
99
|
+
detector_type="None",
|
|
100
|
+
detector_values={},
|
|
101
|
+
measure="Qsca",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
``PyMieSimX.create_dash_app`` and ``PyMieSimX.OpticalSetupGUI`` remain
|
|
105
|
+
available for applications that need the graphical interface.
|
|
106
|
+
|
|
107
|
+
Background calculations and limits
|
|
108
|
+
-----------------------------------
|
|
109
|
+
|
|
110
|
+
Parameter sweeps are submitted to a bounded background worker and reported to
|
|
111
|
+
the dashboard through a polling status indicator. Results are guarded by both
|
|
112
|
+
an in-memory dataframe limit and a serialized Dash-payload limit. Large-result
|
|
113
|
+
warnings are logged before execution; oversized results are rejected with an
|
|
114
|
+
actionable message asking you to reduce the sweep.
|
|
115
|
+
|
|
116
|
+
Run the command-line launcher with ``--debug`` to enable detailed logs. Log
|
|
117
|
+
messages use the format ``timestamp | level | logger | message`` and include
|
|
118
|
+
experiment job identifiers, input configuration, queue transitions, dataframe
|
|
119
|
+
memory usage, and serialized result sizes.
|
|
120
|
+
|
|
121
|
+
Server-side usage metrics
|
|
122
|
+
-------------------------
|
|
123
|
+
|
|
124
|
+
The dashboard can share a PostgreSQL metrics database with RosettaX. Configure
|
|
125
|
+
the Render service with::
|
|
126
|
+
|
|
127
|
+
PYMIESIMX_USAGE_METRICS_BACKEND=postgres
|
|
128
|
+
DATABASE_URL=<the shared PostgreSQL URL>
|
|
129
|
+
|
|
130
|
+
PyMieSimX writes namespaced counters to the shared ``metrics_counters`` table:
|
|
131
|
+
``pymiesimx_home_page_visit_count``, ``pymiesimx_experiment_run_count``, and
|
|
132
|
+
``pymiesimx_single_run_count``. If PostgreSQL is unavailable, local development
|
|
133
|
+
falls back to a JSON file under the platform's application-data directory.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Python API and optional Dash graphical interface for PyMieSim."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
from PyMieSimX.api import (
|
|
6
|
+
MAX_SWEEP_COMBINATIONS,
|
|
7
|
+
MAX_RESULT_FRAME_BYTES,
|
|
8
|
+
MAX_RESULT_PAYLOAD_BYTES,
|
|
9
|
+
ExperimentValidationError,
|
|
10
|
+
ResultSizeEstimate,
|
|
11
|
+
ResultSizeLimitError,
|
|
12
|
+
ValidationIssue,
|
|
13
|
+
available_measures,
|
|
14
|
+
build_figure,
|
|
15
|
+
build_single_figure,
|
|
16
|
+
estimate_sweep_size,
|
|
17
|
+
export_result_to_csv,
|
|
18
|
+
export_single_result_to_csv,
|
|
19
|
+
estimate_result_size,
|
|
20
|
+
run_experiment,
|
|
21
|
+
validate_experiment_inputs,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
__version__ = version("PyMieSimX")
|
|
26
|
+
except PackageNotFoundError:
|
|
27
|
+
__version__ = "0+unknown"
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"MAX_SWEEP_COMBINATIONS",
|
|
31
|
+
"MAX_RESULT_FRAME_BYTES",
|
|
32
|
+
"MAX_RESULT_PAYLOAD_BYTES",
|
|
33
|
+
"ExperimentValidationError",
|
|
34
|
+
"ResultSizeEstimate",
|
|
35
|
+
"ResultSizeLimitError",
|
|
36
|
+
"ValidationIssue",
|
|
37
|
+
"available_measures",
|
|
38
|
+
"build_figure",
|
|
39
|
+
"build_single_figure",
|
|
40
|
+
"estimate_sweep_size",
|
|
41
|
+
"export_result_to_csv",
|
|
42
|
+
"export_single_result_to_csv",
|
|
43
|
+
"estimate_result_size",
|
|
44
|
+
"run_experiment",
|
|
45
|
+
"validate_experiment_inputs",
|
|
46
|
+
"__version__",
|
|
47
|
+
"OpticalSetupGUI",
|
|
48
|
+
"create_dash_app",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def __getattr__(name: str):
|
|
53
|
+
"""Load the optional Dash interface only when it is explicitly requested."""
|
|
54
|
+
if name in {"OpticalSetupGUI", "create_dash_app"}:
|
|
55
|
+
from PyMieSimX.gui.interface import OpticalSetupGUI, create_dash_app
|
|
56
|
+
|
|
57
|
+
return {"OpticalSetupGUI": OpticalSetupGUI, "create_dash_app": create_dash_app}[name]
|
|
58
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import logging
|
|
5
|
-
|
|
6
|
-
from PyMieSimX.gui.interface import OpticalSetupGUI
|
|
5
|
+
import sys
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
def _build_argument_parser() -> argparse.ArgumentParser:
|
|
@@ -16,13 +15,25 @@ def _build_argument_parser() -> argparse.ArgumentParser:
|
|
|
16
15
|
return parser
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
def
|
|
20
|
-
"""
|
|
21
|
-
|
|
18
|
+
def configure_logging(*, debug: bool, log_level: str = "INFO") -> int:
|
|
19
|
+
"""Configure application logging using RosettaX's console format."""
|
|
20
|
+
resolved_level = logging.DEBUG if debug else getattr(logging, str(log_level).upper(), logging.INFO)
|
|
22
21
|
logging.basicConfig(
|
|
23
|
-
level=
|
|
22
|
+
level=resolved_level,
|
|
24
23
|
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
|
24
|
+
stream=sys.stdout,
|
|
25
|
+
force=True,
|
|
25
26
|
)
|
|
27
|
+
logging.getLogger("PyMieSimX").setLevel(resolved_level)
|
|
28
|
+
return resolved_level
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main(argv: list[str] | None = None) -> None:
|
|
32
|
+
"""Launch the PyMieSimX dashboard from a console entry point."""
|
|
33
|
+
args = _build_argument_parser().parse_args(argv)
|
|
34
|
+
configure_logging(debug=args.debug)
|
|
35
|
+
from PyMieSimX.gui.interface import OpticalSetupGUI
|
|
36
|
+
|
|
26
37
|
OpticalSetupGUI().run(
|
|
27
38
|
host=args.host,
|
|
28
39
|
port=args.port,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Stable, GUI-independent Python API for PyMieSimX computations."""
|
|
2
|
+
|
|
3
|
+
from PyMieSimX.gui.services import (
|
|
4
|
+
MAX_SWEEP_COMBINATIONS,
|
|
5
|
+
MAX_RESULT_FRAME_BYTES,
|
|
6
|
+
MAX_RESULT_PAYLOAD_BYTES,
|
|
7
|
+
ExperimentValidationError,
|
|
8
|
+
ResultSizeEstimate,
|
|
9
|
+
ResultSizeLimitError,
|
|
10
|
+
ValidationIssue,
|
|
11
|
+
available_measures,
|
|
12
|
+
build_figure,
|
|
13
|
+
build_single_figure,
|
|
14
|
+
estimate_sweep_size,
|
|
15
|
+
export_result_to_csv,
|
|
16
|
+
export_single_result_to_csv,
|
|
17
|
+
estimate_result_size,
|
|
18
|
+
run_experiment,
|
|
19
|
+
validate_experiment_inputs,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"MAX_SWEEP_COMBINATIONS",
|
|
24
|
+
"MAX_RESULT_FRAME_BYTES",
|
|
25
|
+
"MAX_RESULT_PAYLOAD_BYTES",
|
|
26
|
+
"ExperimentValidationError",
|
|
27
|
+
"ResultSizeEstimate",
|
|
28
|
+
"ResultSizeLimitError",
|
|
29
|
+
"ValidationIssue",
|
|
30
|
+
"available_measures",
|
|
31
|
+
"build_figure",
|
|
32
|
+
"build_single_figure",
|
|
33
|
+
"estimate_sweep_size",
|
|
34
|
+
"export_result_to_csv",
|
|
35
|
+
"export_single_result_to_csv",
|
|
36
|
+
"estimate_result_size",
|
|
37
|
+
"run_experiment",
|
|
38
|
+
"validate_experiment_inputs",
|
|
39
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""WSGI compatibility entry points for the PyMieSimX dashboard."""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Dash dashboard components for PyMieSimX."""
|
|
2
|
+
|
|
3
|
+
__all__ = ["OpticalSetupGUI", "create_dash_app"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def __getattr__(name: str):
|
|
7
|
+
"""Load the Dash application lazily for computational-only imports."""
|
|
8
|
+
if name in __all__:
|
|
9
|
+
from PyMieSimX.gui.interface import OpticalSetupGUI, create_dash_app
|
|
10
|
+
|
|
11
|
+
return {"OpticalSetupGUI": OpticalSetupGUI, "create_dash_app": create_dash_app}[name]
|
|
12
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -208,7 +208,7 @@ body {
|
|
|
208
208
|
.plot-options-grid {
|
|
209
209
|
display: grid;
|
|
210
210
|
grid-template-columns: repeat(8, minmax(104px, 1fr));
|
|
211
|
-
gap:
|
|
211
|
+
gap: 6px 8px;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
.plot-option-field {
|
|
@@ -806,6 +806,43 @@ html .rosettax-workflow-step-card--purple { border-left: 4px solid #8d6bd8 !impo
|
|
|
806
806
|
line-height: 1.5;
|
|
807
807
|
}
|
|
808
808
|
|
|
809
|
+
.documentation-equation {
|
|
810
|
+
margin: 10px 0;
|
|
811
|
+
padding: 9px 12px;
|
|
812
|
+
border: 1px solid rgba(13, 110, 253, .16);
|
|
813
|
+
border-radius: 8px;
|
|
814
|
+
background: rgba(13, 110, 253, .05);
|
|
815
|
+
color: #204a74;
|
|
816
|
+
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
817
|
+
font-size: .84rem;
|
|
818
|
+
line-height: 1.45;
|
|
819
|
+
overflow-x: auto;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.documentation-table {
|
|
823
|
+
width: 100%;
|
|
824
|
+
border-collapse: collapse;
|
|
825
|
+
margin: 8px 0 4px;
|
|
826
|
+
font-size: .82rem;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.documentation-table th,
|
|
830
|
+
.documentation-table td {
|
|
831
|
+
border: 1px solid rgba(13, 110, 253, .16);
|
|
832
|
+
padding: 7px 8px;
|
|
833
|
+
text-align: left;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.documentation-table th {
|
|
837
|
+
background: rgba(13, 110, 253, .08);
|
|
838
|
+
color: #204a74;
|
|
839
|
+
font-weight: 700;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.documentation-table td {
|
|
843
|
+
color: #52697a;
|
|
844
|
+
}
|
|
845
|
+
|
|
809
846
|
@media (max-width: 1000px) {
|
|
810
847
|
.documentation-quickstart-body,
|
|
811
848
|
.documentation-model-grid {
|
|
@@ -2429,6 +2466,80 @@ html .field-input {
|
|
|
2429
2466
|
overflow-wrap: anywhere;
|
|
2430
2467
|
}
|
|
2431
2468
|
|
|
2469
|
+
.material-field-controls {
|
|
2470
|
+
display: grid;
|
|
2471
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2472
|
+
gap: 8px;
|
|
2473
|
+
align-items: stretch;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
.material-mode-toggle {
|
|
2477
|
+
border: 1px solid var(--line);
|
|
2478
|
+
background: var(--panel-strong);
|
|
2479
|
+
border-radius: 999px;
|
|
2480
|
+
padding: 4px 6px;
|
|
2481
|
+
display: inline-flex;
|
|
2482
|
+
align-items: center;
|
|
2483
|
+
gap: 8px;
|
|
2484
|
+
width: fit-content;
|
|
2485
|
+
cursor: pointer;
|
|
2486
|
+
margin-bottom: 8px;
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
.material-mode-label {
|
|
2490
|
+
font-size: .84rem;
|
|
2491
|
+
font-weight: 700;
|
|
2492
|
+
line-height: 1;
|
|
2493
|
+
color: var(--muted);
|
|
2494
|
+
min-width: 56px;
|
|
2495
|
+
text-align: center;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
.material-mode-knob {
|
|
2499
|
+
width: 26px;
|
|
2500
|
+
height: 26px;
|
|
2501
|
+
border-radius: 999px;
|
|
2502
|
+
background: #d8e1e8;
|
|
2503
|
+
transform: translateX(-5px);
|
|
2504
|
+
transition: background .16s ease, transform .16s ease;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
.material-mode-toggle.is-index .material-mode-label-ri {
|
|
2508
|
+
color: #0d6efd;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
.material-mode-toggle.is-material .material-mode-label-material {
|
|
2512
|
+
color: #0d6efd;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
.material-mode-toggle.is-material .material-mode-knob {
|
|
2516
|
+
transform: translateX(5px);
|
|
2517
|
+
background: #0d6efd;
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
.material-mode-toggle:focus-visible {
|
|
2521
|
+
outline: 0;
|
|
2522
|
+
border-color: rgba(44, 120, 180, 0.65);
|
|
2523
|
+
box-shadow: 0 0 0 2px rgba(44, 120, 180, 0.18);
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
.material-name-select .Select-control {
|
|
2527
|
+
min-height: 48px;
|
|
2528
|
+
border-radius: 12px;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
.material-name-select .Select-placeholder,
|
|
2532
|
+
.material-name-select .Select-value-label,
|
|
2533
|
+
.material-name-select .Select-input > input {
|
|
2534
|
+
line-height: 46px;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
@media (max-width: 760px) {
|
|
2538
|
+
.material-mode-label {
|
|
2539
|
+
min-width: 48px;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2432
2543
|
.dashboard-dropdown .Select-control,
|
|
2433
2544
|
.dashboard-dropdown .Select-menu-outer,
|
|
2434
2545
|
.dashboard-dropdown .Select-menu,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
{
|
|
2
|
+
"materials": [
|
|
3
|
+
"fused_silica",
|
|
4
|
+
"BK7",
|
|
5
|
+
"water",
|
|
6
|
+
"air",
|
|
7
|
+
"polystyren",
|
|
8
|
+
"soda_lime_glass",
|
|
9
|
+
"crown",
|
|
10
|
+
"flint",
|
|
11
|
+
"BAK1",
|
|
12
|
+
"BAF10",
|
|
13
|
+
"gold",
|
|
14
|
+
"silver"
|
|
15
|
+
],
|
|
16
|
+
"sellmeier_reference": [
|
|
17
|
+
{
|
|
18
|
+
"material": "fused_silica",
|
|
19
|
+
"formula": "Formula 1",
|
|
20
|
+
"B1": "0.6961663",
|
|
21
|
+
"B2": "0.4079426",
|
|
22
|
+
"B3": "0.8974794",
|
|
23
|
+
"C1_um2": "0.004679148",
|
|
24
|
+
"C2_um2": "0.013512063",
|
|
25
|
+
"C3_um2": "97.9340025"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"material": "BK7",
|
|
29
|
+
"formula": "Formula 2",
|
|
30
|
+
"B1": "1.18318503",
|
|
31
|
+
"B2": "0.0871756426",
|
|
32
|
+
"B3": "1.03133701",
|
|
33
|
+
"C1_um2": "0.00722141956",
|
|
34
|
+
"C2_um2": "0.0268216805",
|
|
35
|
+
"C3_um2": "101.702362"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"material": "water",
|
|
39
|
+
"formula": "Formula 2",
|
|
40
|
+
"B1": "0.5672526103",
|
|
41
|
+
"B2": "0.1736581125",
|
|
42
|
+
"B3": "0.02121531502",
|
|
43
|
+
"C1_um2": "0.005085550461",
|
|
44
|
+
"C2_um2": "0.01814938654",
|
|
45
|
+
"C3_um2": "0.02617260739"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"material": "air",
|
|
49
|
+
"formula": "Formula 6",
|
|
50
|
+
"B1": "0.05792105",
|
|
51
|
+
"B2": "0.00167917",
|
|
52
|
+
"B3": "—",
|
|
53
|
+
"C1_um2": "238.0185",
|
|
54
|
+
"C2_um2": "57.362",
|
|
55
|
+
"C3_um2": "—",
|
|
56
|
+
"parameters": "B1=0.05792105, C1=238.0185; B2=0.00167917, C2=57.362"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"material": "polystyren",
|
|
60
|
+
"formula": "Formula 2",
|
|
61
|
+
"B1": "1.4435",
|
|
62
|
+
"B2": "—",
|
|
63
|
+
"B3": "—",
|
|
64
|
+
"C1_um2": "0.020216",
|
|
65
|
+
"C2_um2": "—",
|
|
66
|
+
"C3_um2": "—"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"material": "soda_lime_glass",
|
|
70
|
+
"formula": "Formula 5",
|
|
71
|
+
"B1": "—",
|
|
72
|
+
"B2": "—",
|
|
73
|
+
"B3": "—",
|
|
74
|
+
"C1_um2": "—",
|
|
75
|
+
"C2_um2": "—",
|
|
76
|
+
"C3_um2": "—",
|
|
77
|
+
"parameters": "A=1.5130; B1=-0.003169, C1=2; B2=0.003962, C2=-2"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"material": "crown",
|
|
81
|
+
"formula": "Formula 2",
|
|
82
|
+
"B1": "1.1273555",
|
|
83
|
+
"B2": "0.124412303",
|
|
84
|
+
"B3": "0.827100531",
|
|
85
|
+
"C1_um2": "0.00720341707",
|
|
86
|
+
"C2_um2": "0.0269835916",
|
|
87
|
+
"C3_um2": "100.384588"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"material": "flint",
|
|
91
|
+
"formula": "Formula 2",
|
|
92
|
+
"B1": "1.34533359",
|
|
93
|
+
"B2": "0.209073176",
|
|
94
|
+
"B3": "0.937357162",
|
|
95
|
+
"C1_um2": "0.00997743871",
|
|
96
|
+
"C2_um2": "0.0470450767",
|
|
97
|
+
"C3_um2": "111.886764"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"material": "BAK1",
|
|
101
|
+
"formula": "Formula 2",
|
|
102
|
+
"B1": "1.12365662",
|
|
103
|
+
"B2": "0.309276848",
|
|
104
|
+
"B3": "0.881511957",
|
|
105
|
+
"C1_um2": "0.00644742752",
|
|
106
|
+
"C2_um2": "0.0222284402",
|
|
107
|
+
"C3_um2": "107.297751"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"material": "BAF10",
|
|
111
|
+
"formula": "Formula 2",
|
|
112
|
+
"B1": "1.5851495",
|
|
113
|
+
"B2": "0.143559385",
|
|
114
|
+
"B3": "1.08521269",
|
|
115
|
+
"C1_um2": "0.00926681282",
|
|
116
|
+
"C2_um2": "0.0424489805",
|
|
117
|
+
"C3_um2": "105.613573"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"material": "gold",
|
|
121
|
+
"formula": "Tabulated",
|
|
122
|
+
"B1": "—",
|
|
123
|
+
"B2": "—",
|
|
124
|
+
"B3": "—",
|
|
125
|
+
"C1_um2": "—",
|
|
126
|
+
"C2_um2": "—",
|
|
127
|
+
"C3_um2": "—",
|
|
128
|
+
"parameters": "Tabulated n(lambda) and k(lambda) data"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"material": "silver",
|
|
132
|
+
"formula": "Tabulated",
|
|
133
|
+
"B1": "—",
|
|
134
|
+
"B2": "—",
|
|
135
|
+
"B3": "—",
|
|
136
|
+
"C1_um2": "—",
|
|
137
|
+
"C2_um2": "—",
|
|
138
|
+
"C3_um2": "—",
|
|
139
|
+
"parameters": "Tabulated n(lambda) and k(lambda) data"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Pure orchestration helpers used by the Dash callbacks.
|
|
2
|
+
|
|
3
|
+
Keeping state conversion and callback error handling here makes the expensive
|
|
4
|
+
parts of the dashboard testable without invoking Dash's callback machinery.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from PyMieSimX.gui.services import ExperimentValidationError, build_single_figure, run_experiment
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class CallbackExecution:
|
|
17
|
+
"""Serializable outcome of a computation requested by a Dash callback."""
|
|
18
|
+
|
|
19
|
+
result: dict[str, Any] | None
|
|
20
|
+
run_count: int
|
|
21
|
+
message: str
|
|
22
|
+
level: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def pair_ids_with_values(ids: list[dict[str, str]], values: list[str]) -> dict[str, str]:
|
|
26
|
+
"""Convert dynamic Dash field IDs and values into a flat mapping."""
|
|
27
|
+
return {field_id["name"]: value for field_id, value in zip(ids, values)}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def merge_local_plot_values(settings: dict | None, values: tuple[Any, ...]) -> dict:
|
|
31
|
+
"""Overlay values from a plot-local options card onto stored preferences."""
|
|
32
|
+
merged = dict(settings or {})
|
|
33
|
+
names = ("x_scale", "log_y", "font_size", "line_width", "show_legend", "show_grid")
|
|
34
|
+
for name, value in zip(names, values):
|
|
35
|
+
if value is not None:
|
|
36
|
+
merged[name] = value
|
|
37
|
+
return merged
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def execute_experiment_callback(
|
|
41
|
+
*,
|
|
42
|
+
source_type: str,
|
|
43
|
+
source_values: list[str],
|
|
44
|
+
source_ids: list[dict[str, str]],
|
|
45
|
+
scatterer_type: str,
|
|
46
|
+
scatterer_values: list[str],
|
|
47
|
+
scatterer_ids: list[dict[str, str]],
|
|
48
|
+
detector_type: str,
|
|
49
|
+
detector_values: list[str],
|
|
50
|
+
detector_ids: list[dict[str, str]],
|
|
51
|
+
measure: str,
|
|
52
|
+
run_count: int | None,
|
|
53
|
+
) -> CallbackExecution:
|
|
54
|
+
"""Execute a parameter sweep and convert errors into UI-ready state."""
|
|
55
|
+
next_count = int(run_count or 0)
|
|
56
|
+
try:
|
|
57
|
+
result = run_experiment(
|
|
58
|
+
source_type=source_type,
|
|
59
|
+
source_values=pair_ids_with_values(source_ids, source_values),
|
|
60
|
+
scatterer_type=scatterer_type,
|
|
61
|
+
scatterer_values=pair_ids_with_values(scatterer_ids, scatterer_values),
|
|
62
|
+
detector_type=detector_type,
|
|
63
|
+
detector_values=pair_ids_with_values(detector_ids, detector_values),
|
|
64
|
+
measure=measure,
|
|
65
|
+
)
|
|
66
|
+
except ExperimentValidationError as error:
|
|
67
|
+
return CallbackExecution(None, next_count, " ".join(issue.message for issue in error.issues), "error")
|
|
68
|
+
except (TypeError, ValueError, KeyError) as error:
|
|
69
|
+
return CallbackExecution(None, next_count, str(error), "error")
|
|
70
|
+
|
|
71
|
+
return CallbackExecution(result, next_count + 1, f"Completed {result['row_count']:,} result rows.", "success")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def execute_single_callback(
|
|
75
|
+
*,
|
|
76
|
+
source_type: str,
|
|
77
|
+
source_values: list[str],
|
|
78
|
+
source_ids: list[dict[str, str]],
|
|
79
|
+
scatterer_type: str,
|
|
80
|
+
scatterer_values: list[str],
|
|
81
|
+
scatterer_ids: list[dict[str, str]],
|
|
82
|
+
representation: str,
|
|
83
|
+
projection: str,
|
|
84
|
+
sampling: int,
|
|
85
|
+
nearfield_mode: list[str] | str | None,
|
|
86
|
+
include_incident_field: list[str] | None,
|
|
87
|
+
run_count: int | None,
|
|
88
|
+
) -> CallbackExecution:
|
|
89
|
+
"""Execute a particle representation and convert errors into UI state."""
|
|
90
|
+
next_count = int(run_count or 0)
|
|
91
|
+
nearfield_values = nearfield_mode if isinstance(nearfield_mode, list) else ([nearfield_mode] if nearfield_mode else [])
|
|
92
|
+
try:
|
|
93
|
+
figure, summary = build_single_figure(
|
|
94
|
+
source_type=source_type,
|
|
95
|
+
source_values=pair_ids_with_values(source_ids, source_values),
|
|
96
|
+
scatterer_type=scatterer_type,
|
|
97
|
+
scatterer_values=pair_ids_with_values(scatterer_ids, scatterer_values),
|
|
98
|
+
representation=representation,
|
|
99
|
+
projection=projection,
|
|
100
|
+
sampling=sampling or 120,
|
|
101
|
+
nearfield_mode="absolute" if "absolute" in nearfield_values else "real",
|
|
102
|
+
include_incident_field="include" in (include_incident_field or []),
|
|
103
|
+
)
|
|
104
|
+
except (TypeError, ValueError, KeyError) as error:
|
|
105
|
+
return CallbackExecution(None, next_count, str(error), "error")
|
|
106
|
+
|
|
107
|
+
return CallbackExecution(
|
|
108
|
+
{"figure": figure.to_plotly_json(), "summary": summary},
|
|
109
|
+
next_count + 1,
|
|
110
|
+
"Representation calculated.",
|
|
111
|
+
"success",
|
|
112
|
+
)
|