Skip to content

Commit f36ebcd

Browse files
committed
config argument docs and developer readme
1 parent a69cbb1 commit f36ebcd

File tree

3 files changed

+370
-1
lines changed

3 files changed

+370
-1
lines changed

docs/configuration/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ your API key, the active environment, and any local tweaks you make while
88
experimenting. The ``tidy3d.config`` module keeps all of this in one place
99
through a single object called ``config``. This page explains how it behaves,
1010
where values are stored, and how to keep your changes consistent across
11-
sessions.
11+
sessions. For a catalog of every option, see :doc:`reference`.
1212

1313
Getting Started
1414
---------------
@@ -174,5 +174,6 @@ updating scripts that depend on these names.
174174
Next Steps
175175
----------
176176

177+
- :doc:`reference`
177178
- :doc:`migration`
178179
- :doc:`../api/configuration`

docs/configuration/reference.rst

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
Configuration Reference |:clipboard:|
2+
====================================
3+
4+
All configuration for ``tidy3d`` is organized into named sections reachable
5+
through ``from tidy3d.config import config``. This page lists every built-in
6+
section and the options they expose so you can see everything that is
7+
configurable in one place.
8+
9+
How to read this page
10+
---------------------
11+
12+
- Environment variable overrides always win. Use the pattern
13+
``TIDY3D_<SECTION>__<FIELD>`` (additional ``__`` segments follow the same
14+
nesting, for example ``TIDY3D_PLUGINS__SAMPLE__ENABLED``).
15+
- Fields tagged with **[persisted]** are written to the active ``config.toml``
16+
(base or profile) when you call ``config.save()``. Fields without the tag are
17+
runtime-only and revert to their defaults on the next import unless you use
18+
``config.save(include_defaults=True)`` or set them via environment variables
19+
or profiles.
20+
- Types reference Python objects unless noted otherwise. Literal values are
21+
quoted and tuples use the ``(x, y, z)`` notation seen in the API.
22+
23+
24+
Logging (``config.logging``)
25+
----------------------------
26+
27+
Controls the verbosity and suppression behaviour of the global logger.
28+
29+
``level`` — ``LogLevel``, default ``"WARNING"`` **[persisted]**
30+
Lowest logging level that will be emitted. Accepts ``"DEBUG"``, ``"SUPPORT"``,
31+
``"USER"``, ``"INFO"``, ``"WARNING"``, ``"ERROR"``, and ``"CRITICAL"``.
32+
33+
``suppression`` — ``bool``, default ``True``
34+
Suppresses repeated log messages when ``True`` so only the first occurrence
35+
of identical messages is shown.
36+
37+
38+
Simulation (``config.simulation``)
39+
----------------------------------
40+
41+
Optional overrides that tweak solver behaviour at runtime.
42+
43+
``use_local_subpixel`` — ``bool | None``, default ``None``
44+
Forces or disables the local subpixel averaging routine. ``True`` enables
45+
the algorithm, ``False`` disables it, and ``None`` keeps the solver default.
46+
47+
48+
Microwave (``config.microwave``)
49+
--------------------------------
50+
51+
Applies to the microwave solver add-on.
52+
53+
``suppress_rf_license_warning`` — ``bool``, default ``False``
54+
When ``True`` the microwave solver skips the warning about RF license
55+
availability.
56+
57+
58+
Adjoint (``config.adjoint``)
59+
----------------------------
60+
61+
Parameters that shape autograd (adjoint) behaviour, including local execution
62+
settings and numerical tolerances.
63+
64+
``min_wvl_fraction`` — ``float``, default ``0.05``
65+
Minimum fraction of the smallest wavelength used when discretizing cylinders
66+
for autograd derivatives. Must be ``>= 0``.
67+
68+
``points_per_wavelength`` — ``int``, default ``10``
69+
Default number of material sample points per wavelength for cylinder
70+
discretization. Must be positive.
71+
72+
``default_wavelength_fraction`` — ``float``, default ``0.1``
73+
Fallback fraction of the minimum wavelength when adaptive spacing is needed.
74+
Must be ``>= 0``.
75+
76+
``minimum_spacing_fraction`` — ``float``, default ``0.01``
77+
Smallest normalized spacing allowed when constructing adaptive finite
78+
difference stencils. Must be ``>= 0``.
79+
80+
``local_gradient`` — ``bool``, default ``False`` **[persisted]**
81+
Enables local gradient evaluation. Remote gradients ignore the other adjoint
82+
overrides unless this is ``True``.
83+
84+
``local_adjoint_dir`` — ``pathlib.Path``, default ``"adjoint_data"`` **[persisted]**
85+
Directory (relative to the working directory) where intermediate gradient
86+
artifacts are stored when ``local_gradient`` is enabled.
87+
88+
``gradient_precision`` — ``Literal["single", "double"]``, default ``"single"``
89+
Floating-point precision used for gradient calculations.
90+
91+
``monitor_interval_poly`` — ``tuple[int, int, int]``, default ``(1, 1, 1)``
92+
Cell spacing between samples for polynomial autograd monitors.
93+
94+
``monitor_interval_custom`` — ``tuple[int, int, int]``, default ``(1, 1, 1)``
95+
Cell spacing between samples for custom autograd monitors.
96+
97+
``quadrature_sample_fraction`` — ``float``, default ``0.4``
98+
Fraction of uniform samples reused when building Gauss quadrature nodes.
99+
Must be between ``0`` and ``1``.
100+
101+
``gauss_quadrature_order`` — ``int``, default ``7``
102+
Maximum Gauss–Legendre order used in composite quadrature rules. Must be
103+
positive.
104+
105+
``edge_clip_tolerance`` — ``float``, default ``1e-9``
106+
Padding tolerance used when clipping polygon edges during surface
107+
integrations. Must be ``>= 0``.
108+
109+
``solver_freq_chunk_size`` — ``int | None``, default ``None``
110+
Upper bound on the number of frequencies processed per chunk during gradient
111+
evaluation. ``None`` disables chunking.
112+
113+
``max_traced_structures`` — ``int``, default ``500``
114+
Maximum number of structures whose fields may be traced in an adjoint run.
115+
116+
``max_adjoint_per_fwd`` — ``int``, default ``10``
117+
Maximum number of adjoint simulations dispatched per forward solve.
118+
119+
120+
Web (``config.web``)
121+
--------------------
122+
123+
Settings for the cloud API client and related environment overrides.
124+
125+
``apikey`` — ``SecretStr | None``, default ``None`` **[persisted]**
126+
API key used for authentication. The value is masked when serialized. Also
127+
accepts ``SIMCLOUD_APIKEY`` as a shortcut environment variable.
128+
129+
``ssl_verify`` — ``bool``, default ``True``
130+
Verifies SSL certificates for API requests.
131+
132+
``enable_caching`` — ``bool``, default ``True`` **[persisted]**
133+
Allows the web service to return cached simulation results when available.
134+
135+
``api_endpoint`` — ``str``, default ``"https://tidy3d-api.simulation.cloud"`` **[persisted]**
136+
Base URL for API calls. Must be an HTTP or HTTPS URL.
137+
138+
``website_endpoint`` — ``str``, default ``"https://tidy3d.simulation.cloud"`` **[persisted]**
139+
Base URL for the Tidy3D website. Must be an HTTP or HTTPS URL.
140+
141+
``s3_region`` — ``str``, default ``"us-gov-west-1"``
142+
AWS region used by the platform’s S3 storage.
143+
144+
``timeout`` — ``int``, default ``120``
145+
HTTP request timeout in seconds. Must be between ``0`` and ``300``.
146+
147+
``ssl_version`` — ``ssl.TLSVersion | None``, default ``None``
148+
Explicit TLS version to enforce. Accepts values such as
149+
``ssl.TLSVersion.TLSv1_2``. ``None`` lets ``requests`` negotiate the version.
150+
151+
``env_vars`` — ``dict[str, str]``, default ``{}``
152+
Additional environment variables exported before API calls. Handy for proxy
153+
or credential helpers.
154+
155+
156+
Local Cache (``config.local_cache``)
157+
------------------------------------
158+
159+
Controls the optional on-disk cache for simulation artifacts.
160+
161+
``enabled`` — ``bool``, default ``False`` **[persisted]**
162+
Turns the local cache on or off. When enabled, results are reused if the
163+
inputs match.
164+
165+
``directory`` — ``pathlib.Path``, default ``<base>/cache/simulations`` **[persisted]**
166+
Directory where cached artifacts are stored. The path is expanded, resolved,
167+
and created if missing. ``<base>`` comes from ``TIDY3D_BASE_DIR`` when set,
168+
otherwise ``XDG_CACHE_HOME`` on Unix or ``~/.cache`` on other systems, so the
169+
default resolves to ``~/.cache/tidy3d/simulations``.
170+
171+
``max_size_gb`` — ``float``, default ``10.0`` **[persisted]**
172+
Maximum cache size in gigabytes. ``0`` disables the size limit.
173+
174+
``max_entries`` — ``int``, default ``0`` **[persisted]**
175+
Maximum number of cached simulations retained. ``0`` means no limit and the
176+
cache falls back to size-based eviction.
177+
178+
179+
Plugins (``config.plugins``)
180+
----------------------------
181+
182+
Acts as a container for plugin-defined sections. After a plugin calls
183+
``@register_plugin("name")``, its configuration becomes available under
184+
``config.plugins.<name>`` and follows the same persistence and environment
185+
variable rules described above (for example ``TIDY3D_PLUGINS__NAME__FIELD``).

tidy3d/config/README.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# `tidy3d.config` Architecture
2+
3+
The configuration subsystem wires together runtime defaults, environment
4+
overrides, profile files, and optional plugin hooks so that `from tidy3d.config
5+
import config` always exposes an up-to-date view of the active settings. This
6+
document focuses on the developer-facing design so you can confidently extend
7+
or debug the module.
8+
9+
## Big Picture
10+
11+
- **Pydantic schemas define sections.** Every built-in section lives in
12+
`sections.py` and is registered via the `@register_section` decorator.
13+
- **A central manager composes state.** `ConfigManager` merges defaults,
14+
environment variables, builtin/user profiles, and runtime overrides. It also
15+
applies side-effect handlers whenever values change.
16+
- **Persistence is layered.** `ConfigLoader` handles filesystem reads/writes;
17+
`serializer.py` keeps TOML files annotated with descriptions and stable key
18+
ordering.
19+
- **Registries are dynamic.** `registry.py` tracks section schemas and handler
20+
callables and notifies the manager whenever new items arrive (including from
21+
plugins).
22+
- **Legacy wrappers exist for compatibility.** `legacy.py` exposes the previous
23+
API surface while delegating to the modern manager.
24+
25+
## Lifecycle Overview
26+
27+
1. Importing `tidy3d.config` loads `sections.py`, which registers every built-in
28+
section and handler.
29+
2. `ConfigManager` instantiates, attaches itself to the registry, and loads the
30+
effective tree by merging builtin profiles, `config.toml`, profile overrides,
31+
environment variables, and any runtime overrides.
32+
3. Handlers declared with `@register_handler` run to push settings into global
33+
side effects (log level, environment variables, cache directories, etc.).
34+
4. The public `config` object is a `LegacyConfigWrapper` that proxies to the
35+
active manager but still honours legacy attributes.
36+
5. Runtime mutations go through `ConfigManager.update_section`, which triggers a
37+
reload+reapply cycle so memoized models always represent the latest state.
38+
39+
## Component Map
40+
41+
```mermaid
42+
flowchart LR
43+
subgraph ImportTime["Import Time"]
44+
sections_py["sections.py<br/>@register_section"] --> registry_py
45+
sections_handlers["sections.py<br/>@register_handler"] --> registry_py
46+
end
47+
48+
subgraph Registry
49+
registry_py["registry.py<br/>section & handler registries"]
50+
end
51+
52+
subgraph Manager
53+
manager_py["manager.ConfigManager"] --> loader_py
54+
manager_py --> handlers["Registered handlers"]
55+
manager_py --> legacy_wrapper["legacy.LegacyConfigWrapper"]
56+
manager_py --> plugins_accessor["plugins accessor"]
57+
end
58+
59+
subgraph Persistence
60+
loader_py["loader.ConfigLoader"] --> serializer_py
61+
serializer_py["serializer.py<br/>annotated TOML builder"] --> filesystem["config.toml<br/>profiles/<name>.toml"]
62+
end
63+
64+
env_vars["Environment variables"] --> loader_py
65+
builtin_profiles["profiles.py<br/>BUILTIN_PROFILES"] --> manager_py
66+
runtime_overrides["Runtime overrides"] --> manager_py
67+
plugins["register_plugin(... )<br/>(plugin imports)"] --> registry_py
68+
registry_py --> manager_py
69+
```
70+
71+
## Module Reference
72+
73+
### `sections.py`
74+
75+
- Defines concrete `ConfigSection` subclasses (Pydantic models) for built-in
76+
sections (`logging`, `simulation`, `microwave`, `adjoint`, `web`,
77+
`local_cache`, `plugins` container).
78+
- Decorated with `@register_section("name")` so the schema is discoverable at
79+
import time. For plugins, use `@register_plugin("plugin_name")`.
80+
- Optional `@register_handler("name")` functions apply side effects after the
81+
manager reloads (e.g., update logger globals, export environment variables).
82+
- Fields can include `json_schema_extra={"persist": True}` to mark values that
83+
should be written to disk by default.
84+
85+
### `registry.py`
86+
87+
- Holds the global section and handler dictionaries.
88+
- Exposes decorators (`register_section`, `register_plugin`, `register_handler`)
89+
and accessors (`get_sections`, `get_handlers`).
90+
- Provides `attach_manager()` so the active `ConfigManager` is notified whenever
91+
new sections or handlers register. This allows late imports (plugins, tests)
92+
to automatically appear without manual refresh.
93+
94+
### `manager.py`
95+
96+
- `ConfigManager` orchestrates the entire system:
97+
- Attaches to the registry and caches per-section Pydantic model instances.
98+
- Loads data through `ConfigLoader` and merges layers using `deep_merge`.
99+
- Tracks runtime overrides in memory per profile.
100+
- Filters persisted values (`_filter_persisted`) so `config.save()` writes only
101+
annotated fields unless `include_defaults=True`.
102+
- Invokes handlers after every reload or targeted update.
103+
- Exposes helper accessors (`plugins`, `profiles`, `get_section`, `format`,
104+
etc.).
105+
- `SectionAccessor` proxies dot-attribute access back into `update_section`.
106+
- Normalizes profile names so builtin aliases like `"prod"` resolve
107+
consistently.
108+
109+
### `loader.py`
110+
111+
- Resolves the configuration directory (`resolve_config_directory`) based on
112+
platform, `$TIDY3D_BASE_DIR`, and legacy fallbacks.
113+
- Reads/writes `config.toml` and profile overrides atomically, including
114+
temporary file + backup behaviour.
115+
- Parses environment variables into nested dictionaries (`load_environment_overrides`).
116+
- Supplies dictionary utilities (`deep_merge`, `deep_diff`) used throughout
117+
the manager.
118+
- Coordinates with `serializer.build_document` to preserve inline comments and
119+
descriptions when writing TOML.
120+
121+
### `serializer.py`
122+
123+
- Looks up field descriptions from registered Pydantic models so TOML files
124+
stay annotated.
125+
- Converts nested dictionaries into `tomlkit` documents with stable formatting
126+
and comment preservation—critical for user-friendly diffs.
127+
128+
### `profiles.py`
129+
130+
- Maintains the shipped profile presets (`default`, `prod`, `dev`, `uat`,
131+
`pre`, `nexus`). The manager merges these into the baseline before applying
132+
user overrides.
133+
134+
### `legacy.py`
135+
136+
- Provides backwards-compatible attribute access (`LegacyConfigWrapper`,
137+
`LegacyEnvironment`, etc.) that forward to the modern manager.
138+
- Emits `DeprecationWarning`s to encourage migrations while keeping older code
139+
functional.
140+
141+
## Adding a New Section
142+
143+
1. Define a Pydantic model in `sections.py` (or your plugin) that inherits from
144+
`ConfigSection` and decorate it with `@register_section("name")`.
145+
2. Optionally decorate a function with `@register_handler("name")` to apply
146+
side effects whenever the section changes.
147+
3. Add field descriptions and `json_schema_extra={"persist": True}` where
148+
appropriate so they show up in the generated docs and persisted config.
149+
4. Import the module somewhere reachable so registration happens as part of
150+
normal startup (built-ins live in `sections.py`; plugins should register at
151+
import time).
152+
153+
`ConfigManager` will automatically detect the new section, expose it under
154+
`config.<name>`, and include it in persistence and documentation without extra
155+
wiring.
156+
157+
## Handler Side Effects
158+
159+
- Handlers receive the fully validated Pydantic model for the section.
160+
- Only the sections explicitly updated trigger their handler; call
161+
`config.reload_config()` or `ConfigManager._apply_handlers()` manually if you
162+
need to reapply everything (usually done during initialization).
163+
- Handlers must be robust to repeated calls because they run on every reload.
164+
165+
## Persistence and Serialization Notes
166+
167+
- Only fields marked with `json_schema_extra={"persist": True}` are written by
168+
default. Call `config.save(include_defaults=True)` to flush the entire model
169+
tree to disk (useful for debugging).
170+
- TOML output preserves descriptions derived from field docstrings, so keep the
171+
docstrings concise and informative.
172+
- The loader writes files atomically and stores a `.bak` during the swap to
173+
prevent data loss. Beware of direct edits to `_loader._docs`; tests can use
174+
`ConfigLoader` to manipulate the cached documents safely.
175+
176+
## Debugging Tips
177+
178+
- `config.format()` (or `print(config)`) renders a Rich tree of the current
179+
effective configuration—useful when verifying merges.
180+
- To inspect persisted values without env overrides, call
181+
`ConfigManager._compose_without_env()` in a debugger.
182+
- The registry exposes `get_sections()` / `get_handlers()` for quick sanity
183+
checks that your plugin registered correctly.

0 commit comments

Comments
 (0)