Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
if: contains(github.ref, 'rc') == false
uses: everlytic/branch-merge@1.1.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.GH_PAT }}
source_ref: ${{ github.ref }}
target_branch: "latest"
commit_message_template: ':tada: RELEASE: Merged {source_ref} into target {target_branch}'
Expand All @@ -90,7 +90,7 @@ jobs:
with:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
pypi-release:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -119,7 +119,7 @@ jobs:
if: contains(github.ref, 'rc') == false
uses: everlytic/branch-merge@1.1.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.GH_PAT }}
source_ref: "latest"
target_branch: "develop"
commit_message_template: ':tada: RELEASE: Synced latest into develop'
4 changes: 2 additions & 2 deletions .github/workflows/sync-to-readthedocs-repo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
git remote add mirror https://github.com/flexcompute-readthedocs/tidy3d-docs.git
git push mirror ${{ needs.extract_branch_or_tag.outputs.ref_name }} --force # overwrites always
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_PAT }}

# Conditional Checkout for Tag
- name: Checkout Tag if tag-triggered-sync
Expand All @@ -69,4 +69,4 @@ jobs:
git remote add mirror https://github.com/flexcompute-readthedocs/tidy3d-docs.git
git push mirror ${{ needs.extract_branch_or_tag.outputs.ref_name }} --force # overwrites always
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- `TopologyDesignRegion` is now invariant in `z` by default and supports assigning dimensions along which a design should be uniform via `TopologyDesignRegion(uniform=(bool, bool, bool))`.

### Fixed
- Some validation fixes for design region.
- Bug in adjoint source creation that included empty sources for extraneous `FieldMonitor` objects, triggering unnecessary errors.

## [2.7.4] - 2024-09-25

### Added
Expand Down
2 changes: 1 addition & 1 deletion docs/notebooks
1,259 changes: 664 additions & 595 deletions poetry.lock

Large diffs are not rendered by default.

29 changes: 25 additions & 4 deletions tests/test_components/test_autograd.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import tidy3d.web as web
import xarray as xr
from tidy3d.components.autograd.derivative_utils import DerivativeInfo
from tidy3d.components.data.sim_data import AdjointSourceInfo
from tidy3d.web import run, run_async
from tidy3d.web.api.autograd.utils import FieldMap

Expand Down Expand Up @@ -197,15 +196,12 @@ def emulated_run_bwd(simulation, task_name, **run_kwargs) -> td.SimulationData:
# get the original traced fields
sim_fields_keys = cache[task_id_fwd][AUX_KEY_SIM_FIELDS_KEYS]

adjoint_source_info = AdjointSourceInfo(sources=[], post_norm=1.0, normalize_sim=True)

# postprocess (compute adjoint gradients)
traced_fields_vjp = postprocess_adj(
sim_data_adj=sim_data_adj,
sim_data_orig=sim_data_orig,
sim_data_fwd=sim_data_fwd,
sim_fields_keys=sim_fields_keys,
adjoint_source_info=adjoint_source_info,
)

return traced_fields_vjp
Expand Down Expand Up @@ -1475,3 +1471,28 @@ def objective(params):
NotImplementedError, match="Could not formulate adjoint source for 'FluxMonitor' output"
):
g = ag.grad(objective)(params0)


def test_extraneous_field(use_emulated_run, log_capture):
"""Make sure this doesnt fail."""

def objective(params):
structure_traced = make_structures(params)["medium"]
sim = SIM_BASE.updated_copy(
structures=[structure_traced],
monitors=[
SIM_BASE.monitors[0],
td.ModeMonitor(
size=(1, 1, 0),
center=(0, 0, 0),
mode_spec=td.ModeSpec(),
freqs=[FREQ0 * 0.9, FREQ0 * 1.1],
name="mode",
),
],
)
data = run(sim, task_name="extra_field")
amp = data["mode"].amps.sel(direction="+", f=FREQ0 * 0.9, mode_index=0).values
return abs(anp.squeeze(amp.tolist())) ** 2

g = ag.grad(objective)(params0)
12 changes: 12 additions & 0 deletions tests/test_plugins/test_invdes.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,18 @@ def test_region_params():
_ = design_region.params_zeros


def test_region_uniform():
"""Test parameter shape for uniform dimensions"""
region = make_design_region()
shape = region.params_shape

test_region = region.updated_copy(uniform=(1, 1, 1))
assert test_region.params_shape == (1, 1, 1)

test_region = region.updated_copy(uniform=(1, 0, 1))
assert test_region.params_shape == (1, *shape[1:])


def test_region_penalties():
"""Test evaluation of penalties of a ``TopologyDesignRegion``."""

Expand Down
14 changes: 10 additions & 4 deletions tidy3d/components/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,28 @@ def _get_valid_extension(fname: str) -> str:
)


def skip_if_fields_missing(fields: List[str]):
def skip_if_fields_missing(fields: List[str], root=False):
"""Decorate ``validator`` to check that other fields have passed validation."""

def actual_decorator(validator):
@wraps(validator)
def _validator(cls, val, values):
def _validator(cls, *args, **kwargs):
"""New validator function."""
values = kwargs.get("values")
if values is None:
values = args[0] if root else args[1]
for field in fields:
if field not in values:
log.warning(
f"Could not execute validator '{validator.__name__}' because field "
f"'{field}' failed validation."
)
return val
if root:
return values
else:
return kwargs.get("val") if "val" in kwargs.keys() else args[0]

return validator(cls, val, values)
return validator(cls, *args, **kwargs)

return _validator

Expand Down
4 changes: 4 additions & 0 deletions tidy3d/components/data/monitor_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,10 @@ def to_adjoint_field_sources(self, fwidth: float) -> List[CustomCurrentSource]:
if not np.all(values == 0):
src_field_components[name] = ScalarFieldDataArray(values, coords=coords)

# dont include this source if no data
if all(fld_cmp is None for fld_cmp in src_field_components.values()):
continue

# construct custom Current source
dataset = FieldDataset(**src_field_components)
custom_source = CustomCurrentSource(
Expand Down
5 changes: 3 additions & 2 deletions tidy3d/components/data/sim_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,7 @@ def split_original_fwd(self, num_mnts_original: int) -> Tuple[SimulationData, Si

def make_adjoint_sim(
self, data_vjp_paths: set[tuple], adjoint_monitors: list[Monitor]
) -> tuple[Simulation, AdjointSourceInfo]:
) -> Simulation:
"""Make the adjoint simulation from the original simulation and the VJP-containing data."""

sim_original = self.simulation
Expand All @@ -1043,6 +1043,7 @@ def make_adjoint_sim(
sources=adjoint_source_info.sources,
boundary_spec=bc_adj,
monitors=adjoint_monitors,
post_norm=adjoint_source_info.post_norm,
)

if not adjoint_source_info.normalize_sim:
Expand All @@ -1055,7 +1056,7 @@ def make_adjoint_sim(
grid_spec_adj = grid_spec_original.updated_copy(wavelength=wavelength_original)
sim_adj_update_dict["grid_spec"] = grid_spec_adj

return sim_original.updated_copy(**sim_adj_update_dict), adjoint_source_info
return sim_original.updated_copy(**sim_adj_update_dict)

def make_adjoint_sources(self, data_vjp_paths: set[tuple]) -> dict[str, SourceType]:
"""Generate all of the non-zero sources for the adjoint simulation given the VJP data."""
Expand Down
8 changes: 8 additions & 0 deletions tidy3d/components/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
PMCBoundary,
StablePML,
)
from .data.data_array import FreqDataArray
from .data.dataset import CustomSpatialDataType, Dataset
from .geometry.base import Box, Geometry
from .geometry.mesh import TriangleMesh
Expand Down Expand Up @@ -210,6 +211,13 @@ class AbstractYeeGridSimulation(AbstractSimulation, ABC):
"``autograd`` gradient processing.",
)

post_norm: Union[float, FreqDataArray] = pydantic.Field(
1.0,
title="Post Normalization Values",
description="Factor to multiply the fields by after running, "
"given the adjoint source pipeline used. Note: this is used internally only.",
)

"""
Supply :class:`SubpixelSpec` to select subpixel averaging methods separately for dielectric, metal, and
PEC material interfaces. Alternatively, supply ``True`` to use default subpixel averaging methods,
Expand Down
3 changes: 1 addition & 2 deletions tidy3d/components/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ def check_symmetry(cls, val, values):
and geometric_object.center[dim] != sim_center[dim]
):
raise SetupError(
f"Mode object '{geometric_object}' "
f"(at 'simulation.{field_name}[{position_index}]') "
f"{obj_type} at 'simulation.{field_name}[{position_index}]' "
"in presence of symmetries must be in the main quadrant, "
"or centered on the symmetry axis."
)
Expand Down
6 changes: 6 additions & 0 deletions tidy3d/plugins/invdes/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import pydantic.v1 as pd

import tidy3d as td
from tidy3d.components.types import TYPE_TAG_STR

from .base import InvdesBaseModel
from .design import InverseDesignType
Expand All @@ -23,6 +24,7 @@ class AbstractOptimizer(InvdesBaseModel, abc.ABC):
...,
title="Inverse Design Specification",
description="Specification describing the inverse design problem we wish to optimize.",
discriminator=TYPE_TAG_STR,
)

learning_rate: pd.NonNegativeFloat = pd.Field(
Expand Down Expand Up @@ -69,6 +71,10 @@ class AbstractOptimizer(InvdesBaseModel, abc.ABC):
def initial_state(self, parameters: np.ndarray) -> dict:
"""The initial state of the optimizer."""

def validate_pre_upload(self) -> None:
"""Validate the fully initialized optimizer is ok for upload to our servers."""
pass

def display_fn(self, result: InverseDesignResult, step_index: int) -> None:
"""Default display function while optimizing."""
print(f"step ({step_index + 1}/{self.num_steps})")
Expand Down
11 changes: 8 additions & 3 deletions tidy3d/plugins/invdes/region.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ class TopologyDesignRegion(DesignRegion):
"a value on the same order as the grid size.",
)

uniform: tuple[bool, bool, bool] = pd.Field(
(False, False, True),
title="Uniform",
description="Axes along which the design should be uniform. By default, the structure "
"is assumed to be uniform, i.e. invariant, in the z direction.",
)

transformations: typing.Tuple[TransformationType, ...] = pd.Field(
(),
title="Transformations",
Expand Down Expand Up @@ -166,12 +173,10 @@ def _check_params(params: anp.ndarray = None):
@property
def params_shape(self) -> typing.Tuple[int, int, int]:
"""Shape of the parameters array in (x, y, z), given the ``pixel_size`` and bounds."""
# rmin, rmax = np.array(self.geometry.bounds)
# lengths = rmax - rmin
side_lengths = np.array(self.size)
num_pixels = np.ceil(side_lengths / self.pixel_size)
# TODO: if the structure is infinite but the simulation is finite, need reduced bounds
num_pixels[np.isinf(num_pixels)] = 1
num_pixels[np.logical_or(np.isinf(num_pixels), self.uniform)] = 1
return tuple(int(n) for n in num_pixels)

def _warn_deprecate_params(self):
Expand Down
3 changes: 2 additions & 1 deletion tidy3d/plugins/invdes/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pydantic.v1 as pd

import tidy3d as td
from tidy3d.components.base import skip_if_fields_missing

# warn if pixel size is > PIXEL_SIZE_WARNING_THRESHOLD * (minimum wavelength in material)
PIXEL_SIZE_WARNING_THRESHOLD = 0.1
Expand Down Expand Up @@ -45,9 +46,9 @@ def check_pixel_size_sim(sim: td.Simulation, pixel_size: float, index: int = Non
)

@pd.root_validator(allow_reuse=True)
@skip_if_fields_missing(["design_region"], root=True)
def _check_pixel_size(cls, values):
"""Make sure region pixel_size isn't too large compared to sim's wavelength in material."""

sim = values.get(sim_field_name)
region = values.get("design_region")
pixel_size = region.pixel_size
Expand Down
Loading
Loading