Skip to content

Commit 5ca813e

Browse files
committed
fix: Handling of zero values when using plot_fields with scale=dB
1 parent 9cd39b2 commit 5ca813e

File tree

4 files changed

+85
-2
lines changed

4 files changed

+85
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
- Fixed output range of `tidy3d.plugins.invdes.FilterAndProject` to be between 0 and 1.
2727
- Cropped adjoint monitor sizes in 2D simulations to planar geometry intersection.
2828
- Fixed `Batch.download()` silently succeeding when background downloads fail (e.g., gzip extraction errors).
29+
- Handling of zero values when using `sim_data.plot_field` with `scale=dB`.
2930

3031
## [2.10.0] - 2025-12-18
3132

tests/test_data/test_sim_data.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
import warnings
6+
57
import matplotlib.pyplot as plt
68
import numpy as np
79
import pydantic.v1 as pydantic
@@ -624,3 +626,49 @@ def test_plot_field_monitor_data_unsupported_scale():
624626
val="real",
625627
scale="invalid",
626628
)
629+
630+
631+
def test_plot_field_with_zeros_db_scale():
632+
"""Test that plotting field data with zeros using dB scale doesn't produce warnings."""
633+
sim_data = make_sim_data()
634+
635+
# Get the existing field data and modify it to include zeros
636+
field_monitor_data = sim_data["field"]
637+
ex_data = field_monitor_data.Ex
638+
639+
# Create modified data with some zeros
640+
values = ex_data.values.copy()
641+
values[np.abs(values) < np.abs(values).max() * 0.3] = 0 # Set small values to zero
642+
ex_with_zeros = ex_data.copy(data=values)
643+
644+
# Create new field data with zeros
645+
field_data_with_zeros = field_monitor_data.updated_copy(Ex=ex_with_zeros)
646+
f_sel = ex_data.f.values[0]
647+
x_sel = ex_data.x.values[0]
648+
649+
# Plot with dB scale - this should not produce divide by zero warnings
650+
with warnings.catch_warnings():
651+
warnings.filterwarnings("error", message="divide by zero")
652+
sim_data.plot_field_monitor_data(
653+
field_monitor_data=field_data_with_zeros,
654+
field_name="Ex",
655+
val="abs",
656+
scale="dB",
657+
f=f_sel,
658+
x=x_sel,
659+
)
660+
plt.close()
661+
662+
# Also test with vmin specified (zeros replaced with floor value)
663+
with warnings.catch_warnings():
664+
warnings.filterwarnings("error", message="divide by zero")
665+
sim_data.plot_field_monitor_data(
666+
field_monitor_data=field_data_with_zeros,
667+
field_name="Ex",
668+
val="abs",
669+
scale="dB",
670+
f=f_sel,
671+
x=x_sel,
672+
vmin=-50,
673+
)
674+
plt.close()

tidy3d/components/base_sim/data/sim_data.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pathlib
66
from abc import ABC
77
from os import PathLike
8-
from typing import Any, Union
8+
from typing import Any, Optional, Union
99

1010
import numpy as np
1111
import pydantic.v1 as pd
@@ -132,6 +132,40 @@ def _field_component_value(
132132

133133
return field_value
134134

135+
@staticmethod
136+
def _apply_log_scale(
137+
field_data: xr.DataArray,
138+
vmin: Optional[float] = None,
139+
db_factor: float = 1.0,
140+
) -> xr.DataArray:
141+
"""Prepare field data for log-scale plotting by handling zeros.
142+
143+
Takes absolute value of the data, replaces zeros with a fill value
144+
(to prevent log10(0) warnings), and applies log10 scaling.
145+
146+
Parameters
147+
----------
148+
field_data : xr.DataArray
149+
The field data to prepare.
150+
vmin : float, optional
151+
The minimum value for the color scale. If provided, zeros are replaced
152+
with ``10 ** (vmin / db_factor)`` instead of NaN.
153+
db_factor : float
154+
Factor to multiply the log10 result by (e.g., 20 for dB scale of field,
155+
10 for dB scale of power). Default is 1 (pure log10 scale).
156+
157+
Returns
158+
-------
159+
xr.DataArray
160+
The log-scaled field data.
161+
"""
162+
fill_val = np.nan
163+
if vmin is not None:
164+
fill_val = 10 ** (vmin / db_factor)
165+
field_data = np.abs(field_data)
166+
field_data = field_data.where((field_data > 0) | np.isnan(field_data), fill_val)
167+
return db_factor * np.log10(field_data)
168+
135169
def get_monitor_by_name(self, name: str) -> AbstractMonitor:
136170
"""Return monitor named 'name'."""
137171
return self.simulation.get_monitor_by_name(name)

tidy3d/components/data/sim_data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ def plot_field_monitor_data(
545545
("E", "abs^2"): 10,
546546
("H", "abs^2"): 10,
547547
}.get((field_name[0], val), 20)
548-
field_data = db_factor * np.log10(np.abs(field_data))
548+
field_data = self._apply_log_scale(field_data, vmin=vmin, db_factor=db_factor)
549549
field_data.name += " (dB)"
550550
cmap_type = "sequential"
551551
elif scale == "lin":

0 commit comments

Comments
 (0)