Skip to content

perf(autograd): optimize grey_dilation with striding #2589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Add support for `np.unwrap` in `tidy3d.plugins.autograd`.

### Changed
- Significantly improved performance of the `tidy3d.plugins.autograd.grey_dilation` morphological operation and its gradient calculation. The new implementation is orders of magnitude faster, especially for large arrays and kernel sizes.

### Fixed
- Arrow lengths are now scaled consistently in the X and Y directions, and their lengths no longer exceed the height of the plot window.
- Bug in `PlaneWave` defined with a negative `angle_theta` which would lead to wrong injection.
Expand Down Expand Up @@ -100,8 +103,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed
- Fixed `reverse` property of `td.Scene.plot_structures_property()` to also reverse the colorbar.

### Fixed
- Fixed bug in surface gradient computation where fields, instead of gradients, were being summed in frequency.

## [2.8.2] - 2025-04-09
Expand Down
68 changes: 66 additions & 2 deletions tests/test_plugins/autograd/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def test_morphology_val_size(self, rng, op, sp_op, mode, ary_size, kernel_size):
def test_morphology_val_grad(self, rng, op, sp_op, mode, ary_size, kernel_size):
"""Test gradients of morphological operations for various modes, array sizes, and kernel sizes."""
x = rng.random(ary_size)
check_grads(op, modes=["rev"], order=2)(x, size=kernel_size, mode=mode)
check_grads(op, modes=["rev"], order=1)(x, size=kernel_size, mode=mode)

@pytest.mark.parametrize(
"full",
Expand Down Expand Up @@ -245,7 +245,71 @@ def test_morphology_val_structure_grad(
):
"""Test gradients of morphological operations for various kernel structures."""
x, k = self._ary_and_kernel(rng, ary_size, kernel_size, full, square, flat)
check_grads(op, modes=["rev"], order=2)(x, size=kernel_size, mode=mode)
check_grads(op, modes=["rev"], order=1)(x, structure=k, mode=mode)


class TestMorphology1D:
"""Test morphological operations with 1D-like structuring elements."""

@pytest.mark.parametrize("h, w", [(1, 3), (3, 1), (1, 5), (5, 1)])
def test_1d_structuring_elements(self, rng, h, w):
"""Test grey dilation with 1D-like structuring elements on 2D arrays."""
x = rng.random((8, 8))

# Test with size parameter
size_tuple = (h, w)
result_size = grey_dilation(x, size=size_tuple)

# Verify output shape matches input
assert result_size.shape == x.shape

# Verify that dilation actually increases values (or keeps them the same)
assert np.all(result_size >= x)

# Test that we can also use structure parameter with 1D-like arrays
structure = np.ones((h, w))
result_struct = grey_dilation(x, structure=structure)
assert result_struct.shape == x.shape

def test_1d_gradient_flow(self, rng):
"""Test gradient flow through 1D-like structuring elements."""
x = rng.random((6, 6))

# Test horizontal 1D structure
check_grads(lambda x: grey_dilation(x, size=(1, 3)), modes=["rev"], order=1)(x)

# Test vertical 1D structure
check_grads(lambda x: grey_dilation(x, size=(3, 1)), modes=["rev"], order=1)(x)

# Test with structure parameter
struct_h = np.ones((1, 3))
struct_v = np.ones((3, 1))
check_grads(lambda x: grey_dilation(x, structure=struct_h), modes=["rev"], order=1)(x)
check_grads(lambda x: grey_dilation(x, structure=struct_v), modes=["rev"], order=1)(x)


class TestMorphologyExceptions:
"""Test exceptions in morphological operations."""

def test_no_size_or_structure(self, rng):
"""Test that an exception is raised when neither size nor structure is provided."""
x = rng.random((5, 5))
with pytest.raises(ValueError, match="Either size or structure must be provided"):
grey_dilation(x)

def test_even_structure_dimensions(self, rng):
"""Test that an exception is raised for even-dimensioned structuring elements."""
x = rng.random((5, 5))
k_even = np.ones((4, 4))
with pytest.raises(ValueError, match="Structuring element dimensions must be odd"):
grey_dilation(x, structure=k_even)

def test_both_size_and_structure(self, rng):
"""Test that an exception is raised when both size and structure are provided."""
x = rng.random((5, 5))
k = np.ones((3, 3))
with pytest.raises(ValueError, match="Cannot specify both size and structure"):
grey_dilation(x, size=3, structure=k)


@pytest.mark.parametrize(
Expand Down
Loading