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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Added support for `tidy3d-extras`, an optional plugin that enables more accurate local mode solving via subpixel averaging.
- Added support for `symlog` and `log` scale plotting in `Scene.plot_eps()` and `Scene.plot_structures_property()` methods. The `symlog` scale provides linear behavior near zero and logarithmic behavior elsewhere, while 'log' is a base 10 logarithmic scale.

### Changed
- Improved performance of antenna metrics calculation by utilizing cached wave amplitude calculations instead of recomputing wave amplitudes for each port excitation in the `TerminalComponentModelerData`.
Expand Down
76 changes: 71 additions & 5 deletions tests/test_components/test_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import tidy3d as td
from tidy3d.components.scene import MAX_GEOMETRY_COUNT, MAX_NUM_MEDIUMS
from tidy3d.exceptions import SetupError

from ..utils import SIM_FULL, cartesian_to_unstructured

Expand Down Expand Up @@ -370,7 +371,7 @@ def test_plot_property():
name="Si_MultiPhysics",
)

def try_plotting(mpm, display=False):
def try_plotting(mpm, display=False, scale=None):
# Structure
struct = td.Structure(
geometry=td.Box(size=(2, 2, 2), center=(0, 0, 0)),
Expand All @@ -384,24 +385,32 @@ def try_plotting(mpm, display=False):
)

_, ax = plt.subplots(1, 4, figsize=(20, 4))
scene.plot_structures_property(z=0, property="N_a", ax=ax[0])
scene.plot_structures_property(z=0, property="N_d", ax=ax[1])
scene.plot_structures_property(z=0, property="doping", ax=ax[2])
scene.plot_structures_property(z=0, ax=ax[3]) # eps
if scale:
scene.plot_structures_property(z=0, property="N_a", ax=ax[0], scale=scale)
scene.plot_structures_property(z=0, property="N_d", ax=ax[1], scale=scale)
scene.plot_structures_property(z=0, property="doping", ax=ax[2], scale=scale)
scene.plot_structures_property(z=0, ax=ax[3], scale=scale) # eps
else:
scene.plot_structures_property(z=0, property="N_a", ax=ax[0])
scene.plot_structures_property(z=0, property="N_d", ax=ax[1])
scene.plot_structures_property(z=0, property="doping", ax=ax[2])
scene.plot_structures_property(z=0, ax=ax[3]) # eps
if display:
plt.show()

# constant doping
const_doping = td.ConstantDoping(concentration=1e15)
mpm = mpm.updated_copy(charge=semicon.updated_copy(N_a=[const_doping], N_d=[const_doping]))
try_plotting(mpm, display=display_plots)
try_plotting(mpm, display=display_plots, scale="symlog")

# add some Gaussian doping
gaussian_box = td.GaussianDoping(
center=(0, 0, 0), size=(2, 2, 2), ref_con=1e15, concentration=1e18, width=0.1, source="xmin"
)
mpm = mpm.updated_copy(charge=semicon.updated_copy(N_a=[gaussian_box], N_d=[gaussian_box]))
try_plotting(mpm, display=display_plots)
try_plotting(mpm, display=display_plots, scale="symlog")

# now try with a custom doping
x = np.linspace(-1, 1, 30)
Expand All @@ -419,3 +428,60 @@ def try_plotting(mpm, display=False):
custom_box1 = td.CustomDoping(center=(0, 0, 0), size=(2, 2, 2), concentration=concentration)
mpm = mpm.updated_copy(charge=semicon.updated_copy(N_a=[custom_box1], N_d=[custom_box1]))
try_plotting(mpm, display=display_plots)
try_plotting(mpm, display=display_plots, scale="symlog")


def test_log_scale_with_custom_limits():
"""Test log scale with custom limits."""

# Create a scene with different permittivity values
scene = td.Scene(
structures=[
td.Structure(
geometry=td.Box(size=(1, 1, 1), center=(-1, 0, 0)),
medium=td.MultiPhysicsMedium(
optical=td.Medium(permittivity=1.0),
),
),
td.Structure(
geometry=td.Box(size=(1, 1, 1), center=(0, 0, 0)),
medium=td.MultiPhysicsMedium(
optical=td.Medium(permittivity=10.0),
),
),
td.Structure(
geometry=td.Box(size=(1, 1, 1), center=(1, 0, 0)),
medium=td.MultiPhysicsMedium(
optical=td.Medium(permittivity=100.0),
),
),
],
medium=td.MultiPhysicsMedium(
optical=td.Medium(permittivity=1.0),
),
)

# Test log scale with custom limits
_ = scene.plot_eps(x=0, scale="log", eps_lim=(1e-2, 100))
plt.close()

_ = scene.plot_eps(x=0, scale="log", eps_lim=(1e-5, 100))
plt.close()

with pytest.raises(SetupError, match="Log scale cannot be used with non-positive values."):
_ = scene.plot_eps(x=0, scale="log", eps_lim=(-1e-2, 100))
plt.close()

_ = scene.plot_structures_property(x=0, property="eps", scale="log", limits=(1e-2, 100))
plt.close()

with pytest.raises(SetupError, match="Log scale cannot be used with non-positive values."):
_ = scene.plot_structures_property(x=0, property="eps", scale="log", limits=(-2e-2, 100))
plt.close()

# Test that invalid scale raises error
with pytest.raises(
SetupError, match="The scale 'invalid' is not supported for plotting structures property."
):
_ = scene.plot_structures_property(x=0, property="eps", scale="invalid")
plt.close()
18 changes: 17 additions & 1 deletion tests/test_data/test_sim_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from tidy3d.components.data.sim_data import SimulationData
from tidy3d.components.file_util import replace_values
from tidy3d.components.monitor import FieldMonitor, FieldTimeMonitor, ModeMonitor
from tidy3d.exceptions import DataError, Tidy3dKeyError
from tidy3d.exceptions import DataError, SetupError, Tidy3dKeyError

from ..utils import get_nested_shape
from .test_data_arrays import FIELD_MONITOR, SIM, SIM_SYM
Expand Down Expand Up @@ -527,3 +527,19 @@ def test_to_mat_file(tmp_path):
sim_data = make_sim_data()
path = str(tmp_path / "test.mat")
sim_data.to_mat_file(path)


def test_plot_field_monitor_data_unsupported_scale():
"""Test plot_field_monitor_data with unsupported scale to trigger SetupError."""
sim_data = make_sim_data()

# Test with unsupported scale
with pytest.raises(
SetupError, match="The scale 'invalid' is not supported for plotting field data"
):
sim_data.plot_field_monitor_data(
field_monitor_data=sim_data.monitor_data["field"],
field_name="Ex",
val="real",
scale="invalid",
)
6 changes: 4 additions & 2 deletions tidy3d/components/data/sim_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from tidy3d.components.types import Ax, Axis, ColormapType, FieldVal, PlotScale, annotate_type
from tidy3d.components.viz import add_ax_if_none, equal_aspect
from tidy3d.constants import C_0, inf
from tidy3d.exceptions import DataError, FileError, Tidy3dKeyError
from tidy3d.exceptions import DataError, FileError, SetupError, Tidy3dKeyError
from tidy3d.log import log

from .data_array import FreqDataArray, TimeDataArray
Expand Down Expand Up @@ -540,7 +540,7 @@ def plot_field_monitor_data(
field_data = db_factor * np.log10(np.abs(field_data))
field_data.name += " (dB)"
cmap_type = "sequential"
else:
elif scale == "lin":
cmap_type = (
"cyclic"
if val == "phase"
Expand All @@ -550,6 +550,8 @@ def plot_field_monitor_data(
else "sequential"
)
)
else:
raise SetupError(f"The scale '{scale}' is not supported for plotting field data.")

# interp out any monitor.size==0 dimensions
monitor = field_monitor_data.monitor
Expand Down
Loading