diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 6154f60399..46013e7346 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -79,7 +79,7 @@ If those overrides are omitted, deployment targets are inferred from `release_ty ### `tidy3d-python-client-tests.yml` Primary CI workflow; it runs on PRs (`latest`, `develop`, `pre/*`), merge queue (`merge_group`), manual dispatch, and `workflow_call`. Highlights: -- **Code quality**: `ruff format`, `ruff check`, `mypy`, `zizmor`, schema regeneration, commit/branch linting. +- **Code quality**: `ruff format`, `ruff check`, `mypy`, `zizmor`, schema regeneration, commit/branch linting, and changelog policy enforcement (no direct `CHANGELOG.md` edits on regular PR branches). - **Local tests**: Self-hosted Slurm runners on Python 3.10 and 3.13 (coverage enforced, diff-coverage comments for 3.13). - **Remote tests**: GitHub-hosted matrix across Windows, Linux, and macOS for Python 3.10–3.13. - **Optional suites**: CLI tests, version consistency checks, submodule validation (non-RC release tags only), and `tidy3d-extras` integration tests can be toggled via inputs. @@ -150,6 +150,26 @@ Manual or called workflow that updates `poetry.lock`, authenticates against AWS The workflow creates a PR with branch name `chore/update-poetry-lock-{source_branch}` targeting the specified source branch. +### `tidy3d-python-client-build-changelog-pr.yml` + +Manual workflow that builds `CHANGELOG.md` from Towncrier fragments and opens a PR. + +**Key inputs:** +- `source_branch` – branch to checkout and build changelog from (defaults to `develop`). +- `target_branch` – branch to open the PR against (defaults to `develop`). +- `release_version` – optional override for the release version. If omitted, it is derived from `pyproject.toml` by stripping `.devN`. +- `release_date` – optional override in `YYYY-MM-DD`. If omitted, UTC `today` is used. +- `previous_version` – optional override for the compare-link previous version. If omitted, the workflow uses the latest reachable stable `vX.Y.Z` tag, and falls back to the latest stable heading in `CHANGELOG.md` when no tag is available. +- `run_workflow` – boolean guard to enable/disable execution. + +The workflow: +1. Installs Poetry dependencies (`--extras dev`). +2. Runs `towncrier build --yes`. +3. Runs `scripts/changelog_refs.py` to update compare reference links. +4. Opens a PR with the generated changelog updates. + +If no fragments are present in `changelog.d/`, the workflow exits without opening a PR. + ## Documentation Workflows ### `tidy3d-docs-sync-readthedocs-repo.yml` diff --git a/.github/workflows/tidy3d-python-client-build-changelog-pr.yml b/.github/workflows/tidy3d-python-client-build-changelog-pr.yml new file mode 100644 index 0000000000..7256d14e5f --- /dev/null +++ b/.github/workflows/tidy3d-python-client-build-changelog-pr.yml @@ -0,0 +1,164 @@ +name: public/tidy3d/python-client-build-changelog-pr + +on: + workflow_dispatch: + inputs: + run_workflow: + description: 'Set to true to build changelog and create a PR' + required: true + type: boolean + default: true + source_branch: + description: 'Source branch to checkout and build changelog from' + required: false + type: string + default: 'develop' + target_branch: + description: 'Target branch for the generated changelog PR' + required: false + type: string + default: 'develop' + release_version: + description: 'Optional release version override (for example: 2.11.0)' + required: false + type: string + default: '' + release_date: + description: 'Optional release date override in YYYY-MM-DD (defaults to UTC today)' + required: false + type: string + default: '' + previous_version: + description: 'Optional previous release version override (for example: 2.10.2)' + required: false + type: string + default: '' + +permissions: + contents: read + +jobs: + build-changelog-pr: + if: github.event.inputs.run_workflow == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.inputs.source_branch || 'develop' }} + fetch-depth: 0 + submodules: false + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.10' + + - name: Install Poetry + uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 + with: + version: 2.1.1 + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Install dependencies + run: | + set -e + poetry install --extras dev --no-interaction + + - name: Build changelog + id: build-changelog + env: + INPUT_RELEASE_VERSION: ${{ github.event.inputs.release_version }} + INPUT_RELEASE_DATE: ${{ github.event.inputs.release_date }} + INPUT_PREVIOUS_VERSION: ${{ github.event.inputs.previous_version }} + SOURCE_BRANCH: ${{ github.event.inputs.source_branch || 'develop' }} + run: | + set -euo pipefail + + if [[ ! -d changelog.d ]]; then + echo "changelog.d/ directory not found; skipping changelog build." + echo "changes_detected=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + fragment_count=$(find changelog.d -maxdepth 1 -type f -name '*.md' ! -name 'README.md' ! -name 'template.md' | wc -l) + if [[ "$fragment_count" -eq 0 ]]; then + echo "No changelog fragments found in changelog.d/." + echo "changes_detected=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + relver="${INPUT_RELEASE_VERSION}" + if [[ -z "$relver" ]]; then + relver=$(poetry version -s | sed -E 's/\.dev[0-9]+$//') + fi + relver=$(echo "$relver" | sed -E 's/^v//') + if [[ -z "$relver" ]]; then + echo "::error::Release version is empty after normalization." + exit 1 + fi + + reldate="${INPUT_RELEASE_DATE}" + if [[ -z "$reldate" ]]; then + reldate=$(date -u +%F) + fi + + prevver="${INPUT_PREVIOUS_VERSION}" + prevver=$(echo "$prevver" | sed -E 's/^v//') + if [[ -z "$prevver" ]]; then + prevver=$(git tag --merged HEAD --list 'v*' --sort=-v:refname | sed 's/^v//' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | grep -v "^${relver}$" | head -n1 || true) + fi + if [[ -z "$prevver" ]]; then + prevver=$(grep -E '^## \[[0-9]+\.[0-9]+\.[0-9]+\] - ' CHANGELOG.md | sed -E 's/^## \[([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/' | grep -v "^${relver}$" | head -n1 || true) + fi + + safe_source_branch=$(echo "$SOURCE_BRANCH" | tr '/ ' '--') + + echo "Building changelog for version ${relver} on ${reldate}" + poetry run towncrier build --yes --version "${relver}" --date "${reldate}" + + if [[ -n "$prevver" ]]; then + poetry run python scripts/changelog_refs.py --version "${relver}" --previous-version "${prevver}" + else + poetry run python scripts/changelog_refs.py --version "${relver}" + fi + + if git diff --quiet -- CHANGELOG.md changelog.d; then + echo "changes_detected=false" >> "$GITHUB_OUTPUT" + else + echo "changes_detected=true" >> "$GITHUB_OUTPUT" + fi + previous_version_output="${prevver}" + if [[ -z "$previous_version_output" ]]; then + previous_version_output="auto-detected" + fi + echo "release_version=${relver}" >> "$GITHUB_OUTPUT" + echo "release_date=${reldate}" >> "$GITHUB_OUTPUT" + echo "previous_version=${previous_version_output}" >> "$GITHUB_OUTPUT" + echo "safe_source_branch=${safe_source_branch}" >> "$GITHUB_OUTPUT" + + - name: Create Pull Request + if: steps.build-changelog.outputs.changes_detected == 'true' + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore(changelog): :robot: build release notes for ${{ steps.build-changelog.outputs.release_version }}" + title: "chore(changelog): :robot: build release notes for ${{ steps.build-changelog.outputs.release_version }}" + body: | + This pull request was automatically generated by a GitHub Action. + + It builds `CHANGELOG.md` from Towncrier fragments and updates compare reference links. + + Source branch: `${{ github.event.inputs.source_branch || 'develop' }}` + Target branch: `${{ github.event.inputs.target_branch || 'develop' }}` + Version: `${{ steps.build-changelog.outputs.release_version }}` + Previous version: `${{ steps.build-changelog.outputs.previous_version }}` + Date: `${{ steps.build-changelog.outputs.release_date }}` + branch: "chore/build-changelog-${{ steps.build-changelog.outputs.safe_source_branch }}-${{ github.run_id }}" + base: "${{ github.event.inputs.target_branch || 'develop' }}" + delete-branch: true diff --git a/.github/workflows/tidy3d-python-client-tests.yml b/.github/workflows/tidy3d-python-client-tests.yml index ef2c7ad16e..76619f5d8a 100644 --- a/.github/workflows/tidy3d-python-client-tests.yml +++ b/.github/workflows/tidy3d-python-client-tests.yml @@ -387,7 +387,10 @@ jobs: - name: enforce-jira-key id: enforce-jira-key - if: github.event_name == 'pull_request' && github.event.pull_request.user.login != 'dependabot[bot]' + if: > + github.event_name == 'pull_request' && + github.event.pull_request.user.login != 'dependabot[bot]' && + !startsWith(github.event.pull_request.head.ref, 'chore/build-changelog-') env: STEPS_EXTRACT_BRANCH_NAME_OUTPUTS_BRANCH_NAME: ${{ steps.extract-branch-name.outputs.branch_name }} run: | @@ -425,6 +428,44 @@ jobs: fi fi + enforce-changelog-policy: + needs: determine-test-scope + runs-on: ubuntu-latest + if: needs.determine-test-scope.outputs.pr_review_tests == 'true' + name: enforce-changelog-policy + steps: + - name: Check out source code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: disallow-manual-changelog-edits + env: + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PR_BRANCH: ${{ github.event.pull_request.head.ref }} + run: | + set -euo pipefail + + if ! git cat-file -e "${PR_BASE_SHA}:changelog.d/README.md" 2>/dev/null; then + echo "Towncrier changelog workflow not detected in base commit; skipping check." + exit 0 + fi + + if [[ "$PR_BRANCH" == chore/build-changelog-* ]]; then + echo "Auto-generated changelog branch detected; allowing CHANGELOG.md edits." + exit 0 + fi + + if git diff --name-only "$PR_BASE_SHA" "$PR_HEAD_SHA" | grep -Fxq "CHANGELOG.md"; then + echo "❌ Manual edits to CHANGELOG.md are not allowed." + echo "Add changelog fragments under changelog.d/ instead." + exit 1 + fi + + echo "✅ No direct CHANGELOG.md edits detected." + lint-commit-messages: needs: determine-test-scope runs-on: ubuntu-latest @@ -1365,6 +1406,7 @@ jobs: - verify-schema-change - lint-commit-messages - lint-branch-name + - enforce-changelog-policy - zizmor - develop-cli-tests - verify-version-consistency @@ -1420,6 +1462,12 @@ jobs: echo "❌ Branch name linting failed." exit 1 + - name: check-changelog-policy + if: ${{ needs.determine-test-scope.outputs.pr_review_tests == 'true' && needs.enforce-changelog-policy.result != 'success' && needs.enforce-changelog-policy.result != 'skipped' }} + run: | + echo "❌ Changelog policy check failed." + exit 1 + - name: check-zizmor-static-analysis if: ${{ needs.determine-test-scope.outputs.code_quality_tests == 'true' && needs.zizmor.result != 'success' && needs.zizmor.result != 'skipped' }} run: | diff --git a/AGENTS.md b/AGENTS.md index 232c920a37..5a39282c3d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,6 +48,7 @@ - Follow Conventional Commits per `.commitlintrc.json`. - Branch names must use an allowed prefix (`chore`, `hotfix`, `daily-chore`) or include a Jira key to satisfy CI. - PRs should link issues, summarize behavior changes, list the `poetry run …` checks you executed, and call out docs/schema updates. -- Add a changelog entry under `## [Unreleased]` in `CHANGELOG.md` for user-facing changes (new features, bug fixes, breaking changes). +- For user-facing changes (new features, bug fixes, breaking changes), add a changelog fragment under `changelog.d/` using the pattern `..md` (for example `1234.added.md`) instead of editing `CHANGELOG.md` directly; CI rejects direct `CHANGELOG.md` edits on regular PR branches. +- Release managers can use the GitHub Actions workflow `public/tidy3d/python-client-build-changelog-pr` to generate `CHANGELOG.md` from fragments and open a PR (defaults source/target to `develop`). _Reminder: update this AGENTS.md whenever workflow, tooling, or review expectations change so agents stay in sync with the repo._ diff --git a/CHANGELOG.md b/CHANGELOG.md index c2cb6cf7fd..fae209ac84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,67 +5,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - -### Added -- Added `GeometryArray` class for efficiently representing multiple copies of a base geometry at specified offsets with transformation matrices. Includes a convenience method `geometry.array(offsets=..., transforms=...)` on all geometry objects. -- Added `ModeSortSpec.keep_modes` which can be set to `"all"` to keep all modes in the mode solver (the default), `"filtered"` to keep only modes passing the filter defined by the `ModeSortSpec`, or an integer `N` to keep only the top `N` modes after filtering and sorting. -- Added `fill_fraction_box` as a new filtering and sorting key which computes the field-energy fill fraction within a specified bounding box (`ModeSortSpec.bounding_box`). -- Added `Grid.fine_mesh_info` property to identify and report locations where grid cell sizes are fine for understanding meshing hotspots. -- Added visualization of finest grid regions in `Simulation.plot_grid()` with shaded regions highlighting areas of fine meshing. -- Added autograd support for `Sphere`. -- Added validation warning in `HeatChargeSimulation` for very small `Cylinder` radii to help users avoid meshing and numerical issues. -- Added `GaussianOverlapMonitor` and `AstigmaticGaussianOverlapMonitor` for decomposing electromagnetic fields onto Gaussian beam profiles. -- Added `GaussianPort` and `AstigmaticGaussianPort` for S-matrix calculations using Gaussian beam sources and overlap monitors. -- Added `symmetric_pseudo` option for `s_param_def` in `TerminalComponentModeler` which applies a scaling factor that ensures the S-matrix is symmetric in reciprocal systems. -- Added deprecation warning for ``TemperatureMonitor`` and ``SteadyPotentialMonitor`` when ``unstructured`` parameter is not explicitly set. The default value of ``unstructured`` will change from ``False`` to ``True`` after the 2.11 release. -- Added flag `remove_fragments` to the base `UnstructuredGrid` to remove fragments in unstructured grids. This can ease meshing by eliminating internal boundaries in overlapping structures. -- Added deprecation warning for `conformal` in TCAD heat/charge monitors when explicitly set; this option is ignored (treated as `False`) when meshing with `remove_fragments=True`. -- Added in-memory caching for downloaded batch results, configurable via ``config.batch_data_cache``. -- Added `DesignSpace` support for sweeping `WorkflowType` objects, including mode/EME simulations and component modelers. -- Added `current_amplitude_definition` parameter to `UniformCurrentSource` for size-independent total current injection. Set to `"total"` to interpret the source amplitude as total current rather than current density. -- Added baseband source time classes (`BasebandStep`, `BasebandGaussianPulse`, `BasebandRectangularPulse`, `BasebandCustomSourceTime`) for transient RF simulations with real-valued time signals. -- Added config versioning with automatic backward migrations by default; forward‑compat is best‑effort unless strict mode is enabled. - -### Breaking Changes -- `web.Batch(simulations=...)` now requires string task names when simulations are passed as a dictionary. Numeric keys (for example `0`, `1`) are no longer converted automatically; convert them to strings first (for example `"0"`, `"1"`). -- Added optional automatic extrusion of structures at the simulation boundaries into/through PML/Absorber layers via `extrude_structures` field in class `AbsorberSpec`. -- Added `structure_priority_mode` for `TerminalComponentModeler` and default to `"conductor"` to ensure metal structures override dielectrics regardless of structure order, preventing order-dependent results in RF simulations. -- 1D lumped elements (with zero lateral extent) are no longer allowed. Use a small finite lateral extent (e.g., `1e-6`) instead. -- `ModeSortSpec.sort_key` is now required with a default of `"n_eff"` (previously optional with `None` default). `ModeSortSpec.sort_order` is now optional with a default of `None`, which automatically selects the natural order based on `sort_key` and `sort_reference`: ascending when a reference is provided (closest first), otherwise descending for `n_eff` and polarization fractions (higher values first), ascending for `k_eff` and `mode_area` (lower values first). -- Changed the interpretation of `waist_distance` and `waist_distances` for backward-propagating Gaussian beams (`GaussianBeam`, `AstigmaticGaussianBeam`, `GaussianBeamProfile`, `AstigmaticGaussianBeamProfile`). Previously, the waist position was interpreted relative to the directed propagation axis, meaning switching `direction` from `+` to `-` would also flip the waist position in the global reference frame. Now, the waist position is defined consistently for both directions: a positive `waist_distance` always places the beam waist behind the source/monitor plane (toward the negative normal axis), regardless of propagation direction. This ensures reciprocity between Gaussian sources and overlap monitors used in port-based S-matrix calculations. Users with existing simulations using backward-propagating Gaussian beams with non-zero waist distances may need to adjust their values. - -### Changed -- Changed the default value of `fill_value` in `UnstructuredGridDataset.interp()` from `0` to `"extrapolate"`. This means points outside the mesh will now use nearest-neighbor extrapolation instead of being filled with zeros. -- `ModeSortSpec.sort_key` is now required with a default of `"n_eff"` (previously optional with `None` default). `ModeSortSpec.sort_order` is now optional with a default of `None`, which automatically selects the natural order based on `sort_key` and `sort_reference`: ascending when a reference is provided (closest first), otherwise descending for `n_eff` and polarization fractions (higher values first), ascending for `k_eff` and `mode_area` (lower values first). -- Added `symmetric_pseudo` option for `s_param_def` in `TerminalComponentModeler` which applies a scaling factor that ensures the S-matrix is symmetric in reciprocal systems. -- Added deprecation warning for `TemperatureMonitor` and `SteadyPotentialMonitor` when `unstructured` parameter is not explicitly set. The default value of `unstructured` will change from `False` to `True` in the next release. -- Added deprecation warning for ``TemperatureMonitor`` and ``SteadyPotentialMonitor`` when ``unstructured`` parameter is not explicitly set. The default value of ``unstructured`` will change from ``False`` to ``True`` after the 2.11 release. -- Added validation to `GaussianDoping` to ensure `ref_con < concentration`, validate `source` face identifier, and warn the user when the box size is not sufficient for the specified transition width. -- Local caching is now enabled by default (set `td.config.local_cache.enabled=False` to opt out). -- Reduced computation time of `adaptive_vjp_spacing` for `GeometryGroup` by allowing permittivity based spacing value to be cached. -- Added warning in `LayerRefinementSpec` when `dl_min_from_gaps` (derived from automatic gap refinement) is very small relative to the lateral grid size for identifying cases where excessive grid refinement may occur due to very small detected gaps. -- Added `custom_vjp` and new custom run functions that provide hooks into adjoint for custom gradient calculations. -- Changed default `num_points` in `EMEModeSpec.interp_spec` from 3 to 5 for improved accuracy of frequency interpolation. -- Unstructured data plots are now "crinkled", showing the full mesh elements that cover given monitor boundaries. Previously, the mesh elements were "clipped" to the monitor boundaries. - -### Fixed -- Fixed intermittent "API key not found" errors in parallel job launches by making configuration directory detection race-safe. -- Fixed frequency accumulation of gradients for custom dispersive media. -- Fixed `snap_box_to_grid` producing zero-size boxes when using `Expand` behavior with very small intervals centered on a grid point. -- Fixed sliver polygon artifacts in 2D material subdivision by filtering polygons based on grid cell size, preventing numerical issues with large-coordinate geometries. -- Fixed `CustomMedium` gradient calculation when field coordinates exactly align with boundaries. -- Fixed adjoint simulation `grid_spec` to align exactly with forward simulation for correct `FieldData` adjoint source power. -- Fixed `TerminalComponentModeler` serialization failure with `CustomGridBoundaries` by storing JSON-serializable grid metadata in `GridSpec.attrs`. -- Fixed redundant logging when `Batch.download()` skips existing files, and added `replace_existing` to `Batch.run()` so overwrite behavior can be controlled directly. -- Fixed redundant server lookups when loading simulation results. -- Updated docstrings for `DerivativeInfo` to more accurately reflect dataclass fields. -- Fixed local cache race conditions causing `FileNotFoundError`. -- Improved `ModeSolver.solve()` to suppress the accuracy warning when local subpixel averaging is enabled via `tidy3d-extras`, and expanded docstrings for `ModeSolver.solve()`, `ModeSimulation.run_local()`, `Simulation.epsilon()`, and `Simulation.epsilon_on_grid()` to document the `config.simulation.use_local_subpixel` option. -- Fixed `ModeSolver.validate_pre_upload` not validating `MicrowaveModeSpec` with `AutoImpedanceSpec`, causing cryptic server-side errors for invalid conductor geometries. -- Fixed local cache not storing results for `ModalComponentModeler` (and `TerminalComponentModeler`) runs, causing cache misses on repeated `web.run()` calls. -- Fixed spurious `ModeSimulation` error logs during deserialization of `SimulationDataMap` by adding discriminators to `SimulationType` and `SimulationDataType` unions. -- Fixed unintended model output in Jupyter notebooks during `import tidy3d` by preventing `Tidy3dBaseModel.__str__` from triggering notebook display side effects. + + ## [2.10.2] - 2026-01-21 @@ -2011,7 +1952,6 @@ which fields are to be projected is now determined automatically based on the me - Job and Batch classes for better simulation handling (eventually to fully replace webapi functions). - A large number of small improvements and bug fixes. -[Unreleased]: https://github.com/flexcompute/tidy3d/compare/v2.10.2...develop [2.10.2]: https://github.com/flexcompute/tidy3d/compare/v2.10.1...v2.10.2 [2.10.1]: https://github.com/flexcompute/tidy3d/compare/v2.10.0...v2.10.1 [2.10.0]: https://github.com/flexcompute/tidy3d/compare/v2.9.3...v2.10.0 diff --git a/changelog.d/3015.changed.md b/changelog.d/3015.changed.md new file mode 100644 index 0000000000..6cc805b208 --- /dev/null +++ b/changelog.d/3015.changed.md @@ -0,0 +1 @@ +Added `custom_vjp` and new custom run functions that provide hooks into adjoint for custom gradient calculations. diff --git a/changelog.d/3026.breaking.md b/changelog.d/3026.breaking.md new file mode 100644 index 0000000000..76db3c6202 --- /dev/null +++ b/changelog.d/3026.breaking.md @@ -0,0 +1 @@ +Added optional automatic extrusion of structures at the simulation boundaries into/through PML/Absorber layers via `extrude_structures` field in class `AbsorberSpec`. diff --git a/changelog.d/3055.1.added.md b/changelog.d/3055.1.added.md new file mode 100644 index 0000000000..890fc467da --- /dev/null +++ b/changelog.d/3055.1.added.md @@ -0,0 +1 @@ +Added `ModeSortSpec.keep_modes` which can be set to `"all"` to keep all modes in the mode solver (the default), `"filtered"` to keep only modes passing the filter defined by the `ModeSortSpec`, or an integer `N` to keep only the top `N` modes after filtering and sorting. diff --git a/changelog.d/3055.2.added.md b/changelog.d/3055.2.added.md new file mode 100644 index 0000000000..74a753f17e --- /dev/null +++ b/changelog.d/3055.2.added.md @@ -0,0 +1 @@ +Added `fill_fraction_box` as a new filtering and sorting key which computes the field-energy fill fraction within a specified bounding box (`ModeSortSpec.bounding_box`). diff --git a/changelog.d/3082.added.md b/changelog.d/3082.added.md new file mode 100644 index 0000000000..f82a1bf9e3 --- /dev/null +++ b/changelog.d/3082.added.md @@ -0,0 +1 @@ +Added autograd support for `Sphere`. diff --git a/changelog.d/3168.changed.md b/changelog.d/3168.changed.md new file mode 100644 index 0000000000..965802d05c --- /dev/null +++ b/changelog.d/3168.changed.md @@ -0,0 +1 @@ +Added validation to `GaussianDoping` to ensure `ref_con < concentration`, validate `source` face identifier, and warn the user when the box size is not sufficient for the specified transition width. diff --git a/changelog.d/3179.fixed.md b/changelog.d/3179.fixed.md new file mode 100644 index 0000000000..f8b435ea2e --- /dev/null +++ b/changelog.d/3179.fixed.md @@ -0,0 +1 @@ +Fixed frequency accumulation of gradients for custom dispersive media. diff --git a/changelog.d/3185.breaking.md b/changelog.d/3185.breaking.md new file mode 100644 index 0000000000..7f2c5286e5 --- /dev/null +++ b/changelog.d/3185.breaking.md @@ -0,0 +1 @@ +1D lumped elements (with zero lateral extent) are no longer allowed. Use a small finite lateral extent (e.g., `1e-6`) instead. diff --git a/changelog.d/3185.fixed.md b/changelog.d/3185.fixed.md new file mode 100644 index 0000000000..8d8a901182 --- /dev/null +++ b/changelog.d/3185.fixed.md @@ -0,0 +1 @@ +Fixed `snap_box_to_grid` producing zero-size boxes when using `Expand` behavior with very small intervals centered on a grid point. diff --git a/changelog.d/3190.fixed.md b/changelog.d/3190.fixed.md new file mode 100644 index 0000000000..3c641e4b19 --- /dev/null +++ b/changelog.d/3190.fixed.md @@ -0,0 +1 @@ +Fixed sliver polygon artifacts in 2D material subdivision by filtering polygons based on grid cell size, preventing numerical issues with large-coordinate geometries. diff --git a/changelog.d/3192.1.added.md b/changelog.d/3192.1.added.md new file mode 100644 index 0000000000..7c83bab81f --- /dev/null +++ b/changelog.d/3192.1.added.md @@ -0,0 +1 @@ +Added `Grid.fine_mesh_info` property to identify and report locations where grid cell sizes are fine for understanding meshing hotspots. diff --git a/changelog.d/3192.2.added.md b/changelog.d/3192.2.added.md new file mode 100644 index 0000000000..3040474576 --- /dev/null +++ b/changelog.d/3192.2.added.md @@ -0,0 +1 @@ +Added visualization of finest grid regions in `Simulation.plot_grid()` with shaded regions highlighting areas of fine meshing. diff --git a/changelog.d/3200.breaking.md b/changelog.d/3200.breaking.md new file mode 100644 index 0000000000..412f47c012 --- /dev/null +++ b/changelog.d/3200.breaking.md @@ -0,0 +1 @@ +Added `structure_priority_mode` for `TerminalComponentModeler` and default to `"conductor"` to ensure metal structures override dielectrics regardless of structure order, preventing order-dependent results in RF simulations. diff --git a/changelog.d/3203.fixed.md b/changelog.d/3203.fixed.md new file mode 100644 index 0000000000..07f2dfc0c9 --- /dev/null +++ b/changelog.d/3203.fixed.md @@ -0,0 +1 @@ +Fixed intermittent "API key not found" errors in parallel job launches by making configuration directory detection race-safe. diff --git a/changelog.d/3206.changed.md b/changelog.d/3206.changed.md new file mode 100644 index 0000000000..82b7c9d949 --- /dev/null +++ b/changelog.d/3206.changed.md @@ -0,0 +1 @@ +Added warning in `LayerRefinementSpec` when `dl_min_from_gaps` (derived from automatic gap refinement) is very small relative to the lateral grid size for identifying cases where excessive grid refinement may occur due to very small detected gaps. diff --git a/changelog.d/3211.2.changed.md b/changelog.d/3211.2.changed.md new file mode 100644 index 0000000000..8abedd6feb --- /dev/null +++ b/changelog.d/3211.2.changed.md @@ -0,0 +1 @@ +Added deprecation warning for `TemperatureMonitor` and `SteadyPotentialMonitor` when `unstructured` parameter is not explicitly set. The default value of `unstructured` will change from `False` to `True` after the 2.11 release. diff --git a/changelog.d/3215.added.md b/changelog.d/3215.added.md new file mode 100644 index 0000000000..b0fe5dc254 --- /dev/null +++ b/changelog.d/3215.added.md @@ -0,0 +1 @@ +Added `GeometryArray` class for efficiently representing multiple copies of a base geometry at specified offsets with transformation matrices. Includes a convenience method `geometry.array(offsets=..., transforms=...)` on all geometry objects. diff --git a/changelog.d/3216.changed.md b/changelog.d/3216.changed.md new file mode 100644 index 0000000000..4603ff1c77 --- /dev/null +++ b/changelog.d/3216.changed.md @@ -0,0 +1 @@ +Reduced computation time of `adaptive_vjp_spacing` for `GeometryGroup` by allowing permittivity based spacing value to be cached. diff --git a/changelog.d/3217.added.md b/changelog.d/3217.added.md new file mode 100644 index 0000000000..f2d36c6c59 --- /dev/null +++ b/changelog.d/3217.added.md @@ -0,0 +1 @@ +Added validation warning in `HeatChargeSimulation` for very small `Cylinder` radii to help users avoid meshing and numerical issues. diff --git a/changelog.d/3219.1.added.md b/changelog.d/3219.1.added.md new file mode 100644 index 0000000000..c59091ee02 --- /dev/null +++ b/changelog.d/3219.1.added.md @@ -0,0 +1 @@ +Added `GaussianOverlapMonitor` and `AstigmaticGaussianOverlapMonitor` for decomposing electromagnetic fields onto Gaussian beam profiles. diff --git a/changelog.d/3219.1.breaking.md b/changelog.d/3219.1.breaking.md new file mode 100644 index 0000000000..04f48cca80 --- /dev/null +++ b/changelog.d/3219.1.breaking.md @@ -0,0 +1 @@ +`ModeSortSpec.sort_key` is now required with a default of `"n_eff"` (previously optional with `None` default). `ModeSortSpec.sort_order` is now optional with a default of `None`, which automatically selects the natural order based on `sort_key` and `sort_reference`: ascending when a reference is provided (closest first), otherwise descending for `n_eff` and polarization fractions (higher values first), ascending for `k_eff` and `mode_area` (lower values first). diff --git a/changelog.d/3219.2.added.md b/changelog.d/3219.2.added.md new file mode 100644 index 0000000000..07f04df648 --- /dev/null +++ b/changelog.d/3219.2.added.md @@ -0,0 +1 @@ +Added `GaussianPort` and `AstigmaticGaussianPort` for S-matrix calculations using Gaussian beam sources and overlap monitors. diff --git a/changelog.d/3219.2.breaking.md b/changelog.d/3219.2.breaking.md new file mode 100644 index 0000000000..99172120e8 --- /dev/null +++ b/changelog.d/3219.2.breaking.md @@ -0,0 +1 @@ +Changed the interpretation of `waist_distance` and `waist_distances` for backward-propagating Gaussian beams (`GaussianBeam`, `AstigmaticGaussianBeam`, `GaussianBeamProfile`, `AstigmaticGaussianBeamProfile`). Previously, the waist position was interpreted relative to the directed propagation axis, meaning switching `direction` from `+` to `-` would also flip the waist position in the global reference frame. Now, the waist position is defined consistently for both directions: a positive `waist_distance` always places the beam waist behind the source/monitor plane (toward the negative normal axis), regardless of propagation direction. This ensures reciprocity between Gaussian sources and overlap monitors used in port-based S-matrix calculations. Users with existing simulations using backward-propagating Gaussian beams with non-zero waist distances may need to adjust their values. diff --git a/changelog.d/3219.3.added.md b/changelog.d/3219.3.added.md new file mode 100644 index 0000000000..6e37f358c4 --- /dev/null +++ b/changelog.d/3219.3.added.md @@ -0,0 +1 @@ +Added `symmetric_pseudo` option for `s_param_def` in `TerminalComponentModeler` which applies a scaling factor that ensures the S-matrix is symmetric in reciprocal systems. diff --git a/changelog.d/3224.added.md b/changelog.d/3224.added.md new file mode 100644 index 0000000000..5827645c5a --- /dev/null +++ b/changelog.d/3224.added.md @@ -0,0 +1 @@ +Added `DesignSpace` support for sweeping `WorkflowType` objects, including mode/EME simulations and component modelers. diff --git a/changelog.d/3225.added.md b/changelog.d/3225.added.md new file mode 100644 index 0000000000..188d31850c --- /dev/null +++ b/changelog.d/3225.added.md @@ -0,0 +1 @@ +Added config versioning with automatic backward migrations by default; forward-compat is best-effort unless strict mode is enabled. diff --git a/changelog.d/3227.fixed.md b/changelog.d/3227.fixed.md new file mode 100644 index 0000000000..3a1c6b61a2 --- /dev/null +++ b/changelog.d/3227.fixed.md @@ -0,0 +1 @@ +Fixed local cache race conditions causing `FileNotFoundError`. diff --git a/changelog.d/3228.fixed.md b/changelog.d/3228.fixed.md new file mode 100644 index 0000000000..9c8a598973 --- /dev/null +++ b/changelog.d/3228.fixed.md @@ -0,0 +1 @@ +Fixed redundant logging when `Batch.download()` skips existing files, and added `replace_existing` to `Batch.run()` so overwrite behavior can be controlled directly. diff --git a/changelog.d/3229.fixed.md b/changelog.d/3229.fixed.md new file mode 100644 index 0000000000..ac2bc38da6 --- /dev/null +++ b/changelog.d/3229.fixed.md @@ -0,0 +1 @@ +Fixed redundant server lookups when loading simulation results. diff --git a/changelog.d/3233.1.added.md b/changelog.d/3233.1.added.md new file mode 100644 index 0000000000..5ec83b1af5 --- /dev/null +++ b/changelog.d/3233.1.added.md @@ -0,0 +1 @@ +Added flag `remove_fragments` to the base `UnstructuredGrid` to remove fragments in unstructured grids. This can ease meshing by eliminating internal boundaries in overlapping structures. diff --git a/changelog.d/3233.2.added.md b/changelog.d/3233.2.added.md new file mode 100644 index 0000000000..4b34016794 --- /dev/null +++ b/changelog.d/3233.2.added.md @@ -0,0 +1 @@ +Added deprecation warning for `conformal` in TCAD heat/charge monitors when explicitly set; this option is ignored (treated as `False`) when meshing with `remove_fragments=True`. diff --git a/changelog.d/3234.added.md b/changelog.d/3234.added.md new file mode 100644 index 0000000000..77f30d26f2 --- /dev/null +++ b/changelog.d/3234.added.md @@ -0,0 +1 @@ +Added in-memory caching for downloaded batch results, configurable via ``config.batch_data_cache``. diff --git a/changelog.d/3236.added.md b/changelog.d/3236.added.md new file mode 100644 index 0000000000..20c350cfaa --- /dev/null +++ b/changelog.d/3236.added.md @@ -0,0 +1 @@ +Added `current_amplitude_definition` parameter to `UniformCurrentSource` for size-independent total current injection. Set to `"total"` to interpret the source amplitude as total current rather than current density. diff --git a/changelog.d/3237.1.fixed.md b/changelog.d/3237.1.fixed.md new file mode 100644 index 0000000000..5a700a62c8 --- /dev/null +++ b/changelog.d/3237.1.fixed.md @@ -0,0 +1 @@ +Fixed `CustomMedium` gradient calculation when field coordinates exactly align with boundaries. diff --git a/changelog.d/3237.2.fixed.md b/changelog.d/3237.2.fixed.md new file mode 100644 index 0000000000..2b1dd30f1b --- /dev/null +++ b/changelog.d/3237.2.fixed.md @@ -0,0 +1 @@ +Fixed adjoint simulation `grid_spec` to align exactly with forward simulation for correct `FieldData` adjoint source power. diff --git a/changelog.d/3239.changed.md b/changelog.d/3239.changed.md new file mode 100644 index 0000000000..612fa7dc42 --- /dev/null +++ b/changelog.d/3239.changed.md @@ -0,0 +1 @@ +Local caching is now enabled by default (set `td.config.local_cache.enabled=False` to opt out). diff --git a/changelog.d/3241.fixed.md b/changelog.d/3241.fixed.md new file mode 100644 index 0000000000..86f8e7ceeb --- /dev/null +++ b/changelog.d/3241.fixed.md @@ -0,0 +1 @@ +Updated docstrings for `DerivativeInfo` to more accurately reflect dataclass fields. diff --git a/changelog.d/3244.fixed.md b/changelog.d/3244.fixed.md new file mode 100644 index 0000000000..d32b5211da --- /dev/null +++ b/changelog.d/3244.fixed.md @@ -0,0 +1 @@ +Fixed `TerminalComponentModeler` serialization failure with `CustomGridBoundaries` by storing JSON-serializable grid metadata in `GridSpec.attrs`. diff --git a/changelog.d/3250.added.md b/changelog.d/3250.added.md new file mode 100644 index 0000000000..5ba16c90a6 --- /dev/null +++ b/changelog.d/3250.added.md @@ -0,0 +1 @@ +Added baseband source time classes (`BasebandStep`, `BasebandGaussianPulse`, `BasebandRectangularPulse`, `BasebandCustomSourceTime`) for transient RF simulations with real-valued time signals. diff --git a/changelog.d/3252.fixed.md b/changelog.d/3252.fixed.md new file mode 100644 index 0000000000..6db5bd13f5 --- /dev/null +++ b/changelog.d/3252.fixed.md @@ -0,0 +1 @@ +Improved `ModeSolver.solve()` to suppress the accuracy warning when local subpixel averaging is enabled via `tidy3d-extras`, and expanded docstrings for `ModeSolver.solve()`, `ModeSimulation.run_local()`, `Simulation.epsilon()`, and `Simulation.epsilon_on_grid()` to document the `config.simulation.use_local_subpixel` option. diff --git a/changelog.d/3253.changed.md b/changelog.d/3253.changed.md new file mode 100644 index 0000000000..620eeb8e6e --- /dev/null +++ b/changelog.d/3253.changed.md @@ -0,0 +1 @@ +Changed default `num_points` in `EMEModeSpec.interp_spec` from 3 to 5 for improved accuracy of frequency interpolation. diff --git a/changelog.d/3259.changed.md b/changelog.d/3259.changed.md new file mode 100644 index 0000000000..b429ef7e94 --- /dev/null +++ b/changelog.d/3259.changed.md @@ -0,0 +1 @@ +Unstructured data plots are now "crinkled", showing the full mesh elements that cover given monitor boundaries. Previously, the mesh elements were "clipped" to the monitor boundaries. diff --git a/changelog.d/3260.fixed.md b/changelog.d/3260.fixed.md new file mode 100644 index 0000000000..9ff9f7b7b9 --- /dev/null +++ b/changelog.d/3260.fixed.md @@ -0,0 +1 @@ +Fixed `ModeSolver.validate_pre_upload` not validating `MicrowaveModeSpec` with `AutoImpedanceSpec`, causing cryptic server-side errors for invalid conductor geometries. diff --git a/changelog.d/3268.fixed.md b/changelog.d/3268.fixed.md new file mode 100644 index 0000000000..6dfecc3cf7 --- /dev/null +++ b/changelog.d/3268.fixed.md @@ -0,0 +1 @@ +Fixed local cache not storing results for `ModalComponentModeler` (and `TerminalComponentModeler`) runs, causing cache misses on repeated `web.run()` calls. diff --git a/changelog.d/3275.fixed.md b/changelog.d/3275.fixed.md new file mode 100644 index 0000000000..76702d55e2 --- /dev/null +++ b/changelog.d/3275.fixed.md @@ -0,0 +1 @@ +Fixed spurious `ModeSimulation` error logs during deserialization of `SimulationDataMap` by adding discriminators to `SimulationType` and `SimulationDataType` unions. diff --git a/changelog.d/3294.fixed.md b/changelog.d/3294.fixed.md new file mode 100644 index 0000000000..f48ba9decf --- /dev/null +++ b/changelog.d/3294.fixed.md @@ -0,0 +1 @@ +Fixed unintended model output in Jupyter notebooks during `import tidy3d` by preventing `Tidy3dBaseModel.__str__` from triggering notebook display side effects. diff --git a/changelog.d/3296.breaking.md b/changelog.d/3296.breaking.md new file mode 100644 index 0000000000..42557fc6f7 --- /dev/null +++ b/changelog.d/3296.breaking.md @@ -0,0 +1 @@ +`web.Batch(simulations=...)` now requires string task names when simulations are passed as a dictionary. Numeric keys (for example `0`, `1`) are no longer converted automatically; convert them to strings first (for example `"0"`, `"1"`). diff --git a/changelog.d/3301.changed.md b/changelog.d/3301.changed.md new file mode 100644 index 0000000000..278c53f3c1 --- /dev/null +++ b/changelog.d/3301.changed.md @@ -0,0 +1 @@ +Changed the default value of `fill_value` in `UnstructuredGridDataset.interp()` from `0` to `"extrapolate"`. This means points outside the mesh will now use nearest-neighbor extrapolation instead of being filled with zeros. diff --git a/changelog.d/README.md b/changelog.d/README.md new file mode 100644 index 0000000000..360fa2fbdd --- /dev/null +++ b/changelog.d/README.md @@ -0,0 +1,47 @@ +# Changelog Fragments + +Use Towncrier fragments for user-visible changes instead of editing `CHANGELOG.md` directly. +`CHANGELOG.md` is release-generated; regular PRs should add fragment files in this directory. +CI rejects direct `CHANGELOG.md` edits in regular PRs (auto-generated `chore/build-changelog-*` branches are exempt). + +## File naming + +Create one file per change using: + +- Single entry for a PR/type: `..md` +- Multiple entries for the same PR/type: `...md` where `N` starts at `1` + +Examples: + +- `1234.added.md` +- `1235.1.added.md` +- `1235.2.added.md` +- `1236.changed.md` +- `1237.fixed.md` +- `1238.removed.md` +- `1239.breaking.md` +- `1240.planned_deprecation.md` + +## Allowed fragment types + +- `added` +- `changed` +- `fixed` +- `removed` +- `breaking` +- `planned_deprecation` + +## Content format + +- Write plain text only (no leading `-` bullet). +- Keep it to one short sentence per fragment file. +- Focus on the user-visible impact. + +Examples: + +- `added`: `Added GeometryArray for efficiently representing repeated geometry instances.` +- `changed`: `Improved local cache performance for repeated result loads.` +- `fixed`: `Fixed race conditions when reading the local configuration directory in parallel jobs.` +- `removed`: `Removed the deprecated legacy material alias from the public API.` +- `breaking`: `ModeSortSpec.sort_key is now required; update any code relying on None defaults.` +- `planned_deprecation`: `CurrentIntegralAxisAligned is deprecated and will be removed in a future release; use AxisAlignedCurrentIntegral.` diff --git a/changelog.d/template.md b/changelog.d/template.md new file mode 100644 index 0000000000..34c3c38418 --- /dev/null +++ b/changelog.d/template.md @@ -0,0 +1,16 @@ +{% for section, _ in sections.items() %} +{% set section_name = definitions[section]["name"] if section else "" %} +{% if section_name %} +### {{ section_name }} + +{% endif %} +{% for category, val in definitions.items() if category in sections[section] %} + +### {{ definitions[category]["name"] }} + +{% for text, values in sections[section][category].items() %} +- {{ text }} +{% endfor %} +{% endfor %} +{% endfor %} +{{ "\n" -}} diff --git a/docs/development/release/flow.rst b/docs/development/release/flow.rst index 55df801e72..cc8815a466 100644 --- a/docs/development/release/flow.rst +++ b/docs/development/release/flow.rst @@ -60,7 +60,7 @@ a clean branch on your machine. Develop your code in this new branch, committing your changes when it seems like a natural time to “save your progress”. -If you are working on a new feature, make sure you add a line in the `CHANGELOG.md `_ file (if it exists in that repository) to summarize your changes. +If your PR has a user-visible change, add a changelog fragment in ``changelog.d/`` instead of editing ``CHANGELOG.md`` directly. Use ``..md`` for a single entry, or ``...md`` when a PR has multiple entries of the same type (with ``N`` starting at ``1``). ``type`` is one of ``added``, ``changed``, ``fixed``, ``removed``, ``breaking``, or ``planned_deprecation``. Write one short plain sentence without a leading bullet (Towncrier adds bullets during release builds). 3. Create a pull request on GitHub @@ -118,7 +118,7 @@ rebasing has changed its history. Every PR must have the following before it can be merged: - At least one review. -- A description in the CHANGELOG of what has been done. +- For user-visible changes, at least one changelog fragment in ``changelog.d/``. Every new major feature must also pass all of the following before it can be merged: diff --git a/docs/development/release/version.rst b/docs/development/release/version.rst index ae01557d85..cf0074f840 100644 --- a/docs/development/release/version.rst +++ b/docs/development/release/version.rst @@ -143,6 +143,37 @@ Examples workflow_control: start-deploy # Skips tag creation and tests +Release Changelog Build +^^^^^^^^^^^^^^^^^^^^^^^ + +Before running the release workflow, generate release notes from ``changelog.d`` fragments: + +.. code-block:: bash + + RELVER=$(poetry version -s | sed -E 's/\.dev[0-9]+$//') + RELDATE=$(date -u +%F) + poetry run towncrier build --yes --version "${RELVER}" --date "${RELDATE}" + poetry run python scripts/changelog_refs.py --version "${RELVER}" + +This sequence: + +- Derives the release version from ``pyproject.toml`` (for example ``2.11.0.dev0`` -> ``2.11.0``). +- Builds ``CHANGELOG.md`` and consumes fragment files. +- Adds or updates the reference-style compare link for ``[RELVER]`` using the latest reachable stable ``vX.Y.Z`` tag as the previous version. + +Commit the updated ``CHANGELOG.md`` and removed fragment files in the same release commit. + +Automated Changelog PR Workflow +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can automate changelog generation and PR creation with GitHub Actions workflow ``public/tidy3d/python-client-build-changelog-pr``. + +- Default source branch: ``develop`` +- Default target branch: ``develop`` +- Optional overrides: ``source_branch``, ``target_branch``, ``release_version``, ``release_date``, ``previous_version`` + +This workflow runs Towncrier, updates compare reference links, and opens a PR with the resulting changes. + Best Practices ^^^^^^^^^^^^^^ diff --git a/poetry.lock b/poetry.lock index ae4830e7b8..97be8451cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -7643,6 +7643,27 @@ files = [ {file = "tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7"}, ] +[[package]] +name = "towncrier" +version = "25.8.0" +description = "Building newsfiles for your project." +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "towncrier-25.8.0-py3-none-any.whl", hash = "sha256:b953d133d98f9aeae9084b56a3563fd2519dfc6ec33f61c9cd2c61ff243fb513"}, + {file = "towncrier-25.8.0.tar.gz", hash = "sha256:eef16d29f831ad57abb3ae32a0565739866219f1ebfbdd297d32894eb9940eb1"}, +] + +[package.dependencies] +click = "*" +jinja2 = "*" +tomli = {version = "*", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["furo (>=2024.05.06)", "nox", "packaging", "sphinx (>=5)", "twisted"] + [[package]] name = "tox" version = "4.34.1" @@ -8129,7 +8150,7 @@ files = [ [extras] design = ["bayesian-optimization", "pygad", "pyswarms"] -dev = ["bayesian-optimization", "cma", "coverage", "diff-cover", "dill", "gdstk", "grcwa", "ipython", "ipython", "jinja2", "jupyter", "libcst", "memory_profiler", "mypy", "myst-parser", "nbconvert", "nbdime", "nbsphinx", "networkx", "openpyxl", "optax", "pre-commit", "psutil", "pydata-sphinx-theme", "pygad", "pylint", "pyswarms", "pytest", "pytest-cov", "pytest-env", "pytest-testmon", "pytest-timeout", "pytest-xdist", "rtree", "ruff", "sax", "scikit-rf", "signac", "sphinx", "sphinx-book-theme", "sphinx-copybutton", "sphinx-design", "sphinx-favicon", "sphinx-notfound-page", "sphinx-sitemap", "sphinx-tabs", "sphinxemoji", "tmm", "torch", "torch", "tox", "trimesh", "vtk", "zizmor"] +dev = ["bayesian-optimization", "cma", "coverage", "diff-cover", "dill", "gdstk", "grcwa", "ipython", "ipython", "jinja2", "jupyter", "libcst", "memory_profiler", "mypy", "myst-parser", "nbconvert", "nbdime", "nbsphinx", "networkx", "openpyxl", "optax", "pre-commit", "psutil", "pydata-sphinx-theme", "pygad", "pylint", "pyswarms", "pytest", "pytest-cov", "pytest-env", "pytest-testmon", "pytest-timeout", "pytest-xdist", "rtree", "ruff", "sax", "scikit-rf", "signac", "sphinx", "sphinx-book-theme", "sphinx-copybutton", "sphinx-design", "sphinx-favicon", "sphinx-notfound-page", "sphinx-sitemap", "sphinx-tabs", "sphinxemoji", "tmm", "torch", "torch", "towncrier", "tox", "trimesh", "vtk", "zizmor"] docs = ["cma", "gdstk", "grcwa", "ipython", "jinja2", "jupyter", "myst-parser", "nbconvert", "nbdime", "nbsphinx", "openpyxl", "optax", "pydata-sphinx-theme", "pylint", "sax", "signac", "sphinx", "sphinx-book-theme", "sphinx-copybutton", "sphinx-design", "sphinx-favicon", "sphinx-notfound-page", "sphinx-sitemap", "sphinx-tabs", "sphinxemoji", "tmm"] extras = ["tidy3d-extras"] gdstk = ["gdstk"] @@ -8144,4 +8165,4 @@ vtk = ["vtk"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.14" -content-hash = "a09616de367b2f46ad52109dc990ad23613c4cb0008bc1ed510e372594d8733a" +content-hash = "b373985f28814d5d05ddab1800864563dbc586b85b8b7d8b98292573b64d1cee" diff --git a/pyproject.toml b/pyproject.toml index f8b1fd312b..d2ed1f00b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,7 @@ pytest-cov = "^6.0.0" pytest-env = "^1.1.5" pytest-testmon = { version = "^2.1.3", optional = true } tox = { version = "^4.33.0", optional = true } +towncrier = { version = "^25.8.0", optional = true } diff-cover = { version = "^10.1.0", optional = true } zizmor = { version = "^1.20.0", optional = true } mypy = { version = "1.13.0", optional = true } @@ -173,6 +174,7 @@ dev = [ 'sphinxemoji', 'tmm', 'tox', + 'towncrier', 'trimesh', 'scikit-rf', 'vtk', @@ -253,6 +255,43 @@ name = "codeartifact" url = "https://flexcompute-625554095313.d.codeartifact.us-east-1.amazonaws.com/pypi/pypi-releases/simple/" priority = "supplemental" +[tool.towncrier] +directory = "changelog.d" +filename = "CHANGELOG.md" +start_string = "\n" +template = "changelog.d/template.md" +title_format = "## [{version}] - {project_date}" + +[[tool.towncrier.type]] +directory = "added" +name = "Added" +showcontent = true + +[[tool.towncrier.type]] +directory = "changed" +name = "Changed" +showcontent = true + +[[tool.towncrier.type]] +directory = "fixed" +name = "Fixed" +showcontent = true + +[[tool.towncrier.type]] +directory = "removed" +name = "Removed" +showcontent = true + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking Changes" +showcontent = true + +[[tool.towncrier.type]] +directory = "planned_deprecation" +name = "Planned Deprecation" +showcontent = true + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/scripts/changelog_refs.py b/scripts/changelog_refs.py new file mode 100644 index 0000000000..a80e37b726 --- /dev/null +++ b/scripts/changelog_refs.py @@ -0,0 +1,199 @@ +from __future__ import annotations + +import argparse +import re +import subprocess +import sys +from collections import OrderedDict +from pathlib import Path + +try: + import tomllib +except ModuleNotFoundError: # pragma: no cover - Python < 3.11 fallback + tomllib = None # type: ignore[assignment] + +import toml + +REFERENCE_RE = re.compile(r"^\[([^\]]+)\]:\s+(\S+)\s*$") +RELEASE_HEADING_RE = re.compile(r"^##\s+\[([^\]]+)\]") +DEV_SUFFIX_RE = re.compile(r"\.dev\d+$") +RELEASE_VERSION_RE = re.compile(r"^\d+\.\d+\.\d+$") + + +def _run_git_command(*args: str) -> str: + """Run a git command and return stdout.""" + completed = subprocess.run( + ["git", *args], + check=True, + capture_output=True, + text=True, + ) + return completed.stdout.strip() + + +def _read_pyproject_version(pyproject_path: Path) -> str: + """Read the Poetry version from pyproject.toml.""" + if tomllib is not None: + data = tomllib.loads(pyproject_path.read_text(encoding="utf-8")) + else: + data = toml.loads(pyproject_path.read_text(encoding="utf-8")) + return data["tool"]["poetry"]["version"] + + +def _derive_release_version(pyproject_path: Path) -> str: + """Derive a release version by stripping any .devN suffix.""" + version = _read_pyproject_version(pyproject_path) + return DEV_SUFFIX_RE.sub("", version) + + +def _find_previous_version(new_version: str) -> str: + """Find the latest reachable stable vX.Y.Z tag excluding the new version.""" + tags = _run_git_command( + "tag", "--merged", "HEAD", "--list", "v*", "--sort=-v:refname" + ).splitlines() + for tag in tags: + tag = tag.strip() + if not tag: + continue + candidate_version = tag.removeprefix("v") + if not RELEASE_VERSION_RE.fullmatch(candidate_version): + continue + if candidate_version == new_version: + continue + return candidate_version + raise RuntimeError("Could not determine previous stable vX.Y.Z tag reachable from HEAD.") + + +def _find_previous_version_from_changelog(changelog_path: Path, new_version: str) -> str: + """Find the latest stable release heading in CHANGELOG.md excluding the new version.""" + for line in changelog_path.read_text(encoding="utf-8").splitlines(): + match = RELEASE_HEADING_RE.match(line) + if not match: + continue + candidate_version = match.group(1).strip() + if not RELEASE_VERSION_RE.fullmatch(candidate_version): + continue + if candidate_version == new_version: + continue + return candidate_version + raise RuntimeError("Could not determine previous stable release from CHANGELOG.md headings.") + + +def _update_reference_links( + changelog_path: Path, + new_version: str, + previous_version: str, + repo_url: str, +) -> None: + """Add or update the release compare URL reference at the bottom of CHANGELOG.md.""" + lines = changelog_path.read_text(encoding="utf-8").splitlines() + first_ref_index = next( + (index for index, line in enumerate(lines) if REFERENCE_RE.match(line)), None + ) + + if first_ref_index is None: + body_lines = lines + existing_refs: list[str] = [] + else: + body_lines = lines[:first_ref_index] + existing_refs = lines[first_ref_index:] + + ordered_refs: OrderedDict[str, str] = OrderedDict() + for ref_line in existing_refs: + match = REFERENCE_RE.match(ref_line) + if not match: + continue + reference_name, url = match.groups() + if reference_name in {"Unreleased", new_version}: + continue + ordered_refs[reference_name] = url + + compare_url = f"{repo_url.rstrip('/')}/compare/v{previous_version}...v{new_version}" + ordered_refs = OrderedDict([(new_version, compare_url), *ordered_refs.items()]) + + rendered_refs = [f"[{name}]: {url}" for name, url in ordered_refs.items()] + body = "\n".join(body_lines).rstrip("\n") + output = f"{body}\n\n" + "\n".join(rendered_refs) + "\n" + changelog_path.write_text(output, encoding="utf-8") + + +def main() -> int: + """CLI entry point.""" + parser = argparse.ArgumentParser( + description="Update CHANGELOG.md reference-style release compare links.", + ) + parser.add_argument( + "--version", + help="New release version (for example: 2.11.0). Defaults to pyproject version without .devN.", + ) + parser.add_argument( + "--previous-version", + help=( + "Previous release version (with or without leading v). " + "Defaults to latest reachable stable vX.Y.Z tag; if unavailable, " + "falls back to latest stable release heading in CHANGELOG.md." + ), + ) + parser.add_argument( + "--changelog", + default="CHANGELOG.md", + help="Path to CHANGELOG.md.", + ) + parser.add_argument( + "--pyproject", + default="pyproject.toml", + help="Path to pyproject.toml.", + ) + parser.add_argument( + "--repo-url", + default="https://github.com/flexcompute/tidy3d", + help="Repository URL used to build compare links.", + ) + args = parser.parse_args() + + changelog_path = Path(args.changelog) + pyproject_path = Path(args.pyproject) + + if not changelog_path.exists(): + raise FileNotFoundError(f"Changelog path does not exist: {changelog_path}") + if not pyproject_path.exists(): + raise FileNotFoundError(f"pyproject path does not exist: {pyproject_path}") + + new_version = args.version or _derive_release_version(pyproject_path) + new_version = DEV_SUFFIX_RE.sub("", new_version).strip().removeprefix("v") + if not new_version: + raise ValueError("New release version is empty.") + + previous_version = None + if args.previous_version: + previous_version = args.previous_version.strip().removeprefix("v") + if not previous_version: + raise ValueError("Previous release version is empty after normalization.") + if previous_version is None: + try: + previous_version = _find_previous_version(new_version) + except (RuntimeError, subprocess.CalledProcessError): + previous_version = _find_previous_version_from_changelog(changelog_path, new_version) + print( + ( + "No stable git tag found; " + f"falling back to latest stable release in {changelog_path}: " + f"{previous_version}" + ), + file=sys.stderr, + ) + if previous_version == new_version: + raise ValueError("Previous version must differ from new version.") + + _update_reference_links( + changelog_path=changelog_path, + new_version=new_version, + previous_version=previous_version, + repo_url=args.repo_url, + ) + print(f"Updated changelog references for {new_version} (previous {previous_version}).") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_package/test_changelog_refs.py b/tests/test_package/test_changelog_refs.py new file mode 100644 index 0000000000..b874fd2ecb --- /dev/null +++ b/tests/test_package/test_changelog_refs.py @@ -0,0 +1,96 @@ +"""Regression tests for the changelog reference updater script.""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + + +def test_changelog_refs_normalizes_leading_v_in_version(tmp_path: Path) -> None: + """Ensure --version values like v2.10.2 do not generate vv compare links.""" + + repo_root = Path(__file__).resolve().parents[2] + script_path = repo_root / "scripts" / "changelog_refs.py" + pyproject_path = repo_root / "pyproject.toml" + + changelog_path = tmp_path / "CHANGELOG.md" + changelog_path.write_text( + ( + "# Changelog\n\n" + "\n\n" + "## [2.10.2] - 2026-01-21\n\n" + "### Added\n" + "- Existing entry.\n\n" + "[2.10.2]: " + "https://github.com/flexcompute/tidy3d/compare/v2.10.1...v2.10.2\n" + ), + encoding="utf-8", + ) + + result = subprocess.run( + [ + sys.executable, + str(script_path), + "--changelog", + str(changelog_path), + "--pyproject", + str(pyproject_path), + "--version", + "v2.10.2", + "--previous-version", + "2.10.1", + ], + capture_output=True, + text=True, + check=True, + ) + + content = changelog_path.read_text(encoding="utf-8") + assert "[2.10.2]:" in content + assert "[v2.10.2]:" not in content + assert "...vv2.10.2" not in content + assert "compare/v2.10.1...v2.10.2" in content + assert "Updated changelog references for 2.10.2" in result.stdout + + +def test_changelog_refs_rejects_empty_previous_version_after_normalization(tmp_path: Path) -> None: + """Reject previous-version values that normalize to an empty string.""" + + repo_root = Path(__file__).resolve().parents[2] + script_path = repo_root / "scripts" / "changelog_refs.py" + pyproject_path = repo_root / "pyproject.toml" + + changelog_path = tmp_path / "CHANGELOG.md" + changelog_path.write_text( + ( + "# Changelog\n\n" + "\n\n" + "## [2.10.2] - 2026-01-21\n\n" + "### Added\n" + "- Existing entry.\n\n" + "[2.10.2]: " + "https://github.com/flexcompute/tidy3d/compare/v2.10.1...v2.10.2\n" + ), + encoding="utf-8", + ) + + result = subprocess.run( + [ + sys.executable, + str(script_path), + "--changelog", + str(changelog_path), + "--pyproject", + str(pyproject_path), + "--version", + "2.10.2", + "--previous-version", + " v ", + ], + capture_output=True, + text=True, + ) + + assert result.returncode != 0 + assert "Previous release version is empty after normalization." in result.stderr