Skip to content

Commit 5114878

Browse files
committed
feat: Add log and symlog scale
1 parent bfae45a commit 5114878

File tree

6 files changed

+274
-35
lines changed

6 files changed

+274
-35
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- A new type of doping box has been introduced, `CustomDoping` which accepts a `SpatialDataArray` to define doping concentration. Unlike in the case where a `SpatialDataArray`, custom doping defined with `CustomDoping` have additive behavior, i.e., one can add other doping on top. This deprecates the `SpatialDataArray` as direct input for `N_a` and `N_d`.
1717
- Non-isothermal Charge simulations are now available. One can now run this type of simulations by using the `SteadyChargeDCAnalysis` as the `analysis_spec` of a `HeatChargeSimulation`. This type of simulations couple the heat equation with the drift-diffusion equations which allow to account for self heating behavior.
1818
- Because non-isothermal Charge simulations are now supported, new models for the effective density of states and bandgap energy have been introduced. These models are the following: `ConstantEffectiveDOS`, `IsotropicEffectiveDOS`, `MultiValleyEffectiveDOS`, `DualValleyEffectiveDOS`.
19+
- 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.
1920

2021
### Changed
2122
- `LayerRefinementSpec` defaults to assuming structures made of different materials are interior-disjoint for more efficient mesh generation.

tests/test_components/test_scene.py

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import tidy3d as td
1111
from tidy3d.components.scene import MAX_GEOMETRY_COUNT, MAX_NUM_MEDIUMS
12+
from tidy3d.exceptions import SetupError
1213

1314
from ..utils import SIM_FULL, cartesian_to_unstructured
1415

@@ -370,7 +371,7 @@ def test_plot_property():
370371
name="Si_MultiPhysics",
371372
)
372373

373-
def try_plotting(mpm, display=False):
374+
def try_plotting(mpm, display=False, scale=None):
374375
# Structure
375376
struct = td.Structure(
376377
geometry=td.Box(size=(2, 2, 2), center=(0, 0, 0)),
@@ -384,24 +385,32 @@ def try_plotting(mpm, display=False):
384385
)
385386

386387
_, ax = plt.subplots(1, 4, figsize=(20, 4))
387-
scene.plot_structures_property(z=0, property="N_a", ax=ax[0])
388-
scene.plot_structures_property(z=0, property="N_d", ax=ax[1])
389-
scene.plot_structures_property(z=0, property="doping", ax=ax[2])
390-
scene.plot_structures_property(z=0, ax=ax[3]) # eps
388+
if scale:
389+
scene.plot_structures_property(z=0, property="N_a", ax=ax[0], scale=scale)
390+
scene.plot_structures_property(z=0, property="N_d", ax=ax[1], scale=scale)
391+
scene.plot_structures_property(z=0, property="doping", ax=ax[2], scale=scale)
392+
scene.plot_structures_property(z=0, ax=ax[3], scale=scale) # eps
393+
else:
394+
scene.plot_structures_property(z=0, property="N_a", ax=ax[0])
395+
scene.plot_structures_property(z=0, property="N_d", ax=ax[1])
396+
scene.plot_structures_property(z=0, property="doping", ax=ax[2])
397+
scene.plot_structures_property(z=0, ax=ax[3]) # eps
391398
if display:
392399
plt.show()
393400

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

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

406415
# now try with a custom doping
407416
x = np.linspace(-1, 1, 30)
@@ -419,3 +428,60 @@ def try_plotting(mpm, display=False):
419428
custom_box1 = td.CustomDoping(center=(0, 0, 0), size=(2, 2, 2), concentration=concentration)
420429
mpm = mpm.updated_copy(charge=semicon.updated_copy(N_a=[custom_box1], N_d=[custom_box1]))
421430
try_plotting(mpm, display=display_plots)
431+
try_plotting(mpm, display=display_plots, scale="symlog")
432+
433+
434+
def test_log_scale_with_custom_limits():
435+
"""Test log scale with custom limits."""
436+
437+
# Create a scene with different permittivity values
438+
scene = td.Scene(
439+
structures=[
440+
td.Structure(
441+
geometry=td.Box(size=(1, 1, 1), center=(-1, 0, 0)),
442+
medium=td.MultiPhysicsMedium(
443+
optical=td.Medium(permittivity=1.0),
444+
),
445+
),
446+
td.Structure(
447+
geometry=td.Box(size=(1, 1, 1), center=(0, 0, 0)),
448+
medium=td.MultiPhysicsMedium(
449+
optical=td.Medium(permittivity=10.0),
450+
),
451+
),
452+
td.Structure(
453+
geometry=td.Box(size=(1, 1, 1), center=(1, 0, 0)),
454+
medium=td.MultiPhysicsMedium(
455+
optical=td.Medium(permittivity=100.0),
456+
),
457+
),
458+
],
459+
medium=td.MultiPhysicsMedium(
460+
optical=td.Medium(permittivity=1.0),
461+
),
462+
)
463+
464+
# Test log scale with custom limits
465+
_ = scene.plot_eps(x=0, scale="log", eps_lim=(1e-2, 100))
466+
plt.close()
467+
468+
_ = scene.plot_eps(x=0, scale="log", eps_lim=(1e-5, 100))
469+
plt.close()
470+
471+
with pytest.raises(SetupError, match="Log scale cannot be used with non-positive values."):
472+
_ = scene.plot_eps(x=0, scale="log", eps_lim=(-1e-2, 100))
473+
plt.close()
474+
475+
_ = scene.plot_structures_property(x=0, property="eps", scale="log", limits=(1e-2, 100))
476+
plt.close()
477+
478+
with pytest.raises(SetupError, match="Log scale cannot be used with non-positive values."):
479+
_ = scene.plot_structures_property(x=0, property="eps", scale="log", limits=(-2e-2, 100))
480+
plt.close()
481+
482+
# Test that invalid scale raises error
483+
with pytest.raises(
484+
SetupError, match="The scale 'invalid' is not supported for plotting structures property."
485+
):
486+
_ = scene.plot_structures_property(x=0, property="eps", scale="invalid")
487+
plt.close()

tests/test_data/test_sim_data.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from tidy3d.components.data.sim_data import SimulationData
1414
from tidy3d.components.file_util import replace_values
1515
from tidy3d.components.monitor import FieldMonitor, FieldTimeMonitor, ModeMonitor
16-
from tidy3d.exceptions import DataError, Tidy3dKeyError
16+
from tidy3d.exceptions import DataError, SetupError, Tidy3dKeyError
1717

1818
from ..utils import get_nested_shape
1919
from .test_data_arrays import FIELD_MONITOR, SIM, SIM_SYM
@@ -527,3 +527,19 @@ def test_to_mat_file(tmp_path):
527527
sim_data = make_sim_data()
528528
path = str(tmp_path / "test.mat")
529529
sim_data.to_mat_file(path)
530+
531+
532+
def test_plot_field_monitor_data_unsupported_scale():
533+
"""Test plot_field_monitor_data with unsupported scale to trigger SetupError."""
534+
sim_data = make_sim_data()
535+
536+
# Test with unsupported scale
537+
with pytest.raises(
538+
SetupError, match="The scale 'invalid' is not supported for plotting field data"
539+
):
540+
sim_data.plot_field_monitor_data(
541+
field_monitor_data=sim_data.monitor_data["field"],
542+
field_name="Ex",
543+
val="real",
544+
scale="invalid",
545+
)

tidy3d/components/data/sim_data.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from tidy3d.components.types import Ax, Axis, ColormapType, FieldVal, PlotScale, annotate_type
2828
from tidy3d.components.viz import add_ax_if_none, equal_aspect
2929
from tidy3d.constants import C_0, inf
30-
from tidy3d.exceptions import DataError, FileError, Tidy3dKeyError
30+
from tidy3d.exceptions import DataError, FileError, SetupError, Tidy3dKeyError
3131
from tidy3d.log import log
3232

3333
from .data_array import FreqDataArray, TimeDataArray
@@ -540,7 +540,7 @@ def plot_field_monitor_data(
540540
field_data = db_factor * np.log10(np.abs(field_data))
541541
field_data.name += " (dB)"
542542
cmap_type = "sequential"
543-
else:
543+
elif scale == "lin":
544544
cmap_type = (
545545
"cyclic"
546546
if val == "phase"
@@ -550,6 +550,8 @@ def plot_field_monitor_data(
550550
else "sequential"
551551
)
552552
)
553+
else:
554+
raise SetupError(f"The scale '{scale}' is not supported for plotting field data.")
553555

554556
# interp out any monitor.size==0 dimensions
555557
monitor = field_monitor_data.monitor

0 commit comments

Comments
 (0)