From a463d685306b9cf57601b2852a31c05998354cb4 Mon Sep 17 00:00:00 2001 From: marcorudolphflex Date: Tue, 23 Dec 2025 10:55:05 +0100 Subject: [PATCH] fix(tidy3d): FXC-4644-tidy-3-d-client-add-support-for-num-py-2-4-0 --- tests/test_components/test_mode_interp.py | 4 +-- tests/test_plugins/autograd/test_functions.py | 7 +++- tidy3d/components/base.py | 33 ++++++++++++++++++- tidy3d/components/geometry/base.py | 11 +++---- tidy3d/components/geometry/utils.py | 2 +- tidy3d/components/medium.py | 5 +-- 6 files changed, 48 insertions(+), 14 deletions(-) diff --git a/tests/test_components/test_mode_interp.py b/tests/test_components/test_mode_interp.py index 67a087372c..b255f84e82 100644 --- a/tests/test_components/test_mode_interp.py +++ b/tests/test_components/test_mode_interp.py @@ -620,12 +620,10 @@ def test_mode_solver_data_interp_single_frequency(): field_data = getattr(data_interp, field_name) assert field_data is not None assert field_data.coords["f"].size == 1 - assert float(field_data.coords["f"]) == 1.5e14 + assert float(field_data.coords["f"].item()) == 1.5e14 # Check n_group_raw and dispersion_raw if present if data_interp.n_group_raw is not None: - print(data_interp.n_group_raw.shape) - print((1, original_num_modes)) assert data_interp.n_group_raw.shape == (1, original_num_modes) if data_interp.dispersion_raw is not None: assert data_interp.dispersion_raw.shape == (1, original_num_modes) diff --git a/tests/test_plugins/autograd/test_functions.py b/tests/test_plugins/autograd/test_functions.py index 432a36db0f..5778a6356d 100644 --- a/tests/test_plugins/autograd/test_functions.py +++ b/tests/test_plugins/autograd/test_functions.py @@ -31,6 +31,11 @@ from tidy3d.plugins.autograd.functions import _normalize_axes from tidy3d.plugins.autograd.types import PaddingType +try: + from numpy import trapezoid as np_trapezoid +except ImportError: # NumPy < 2.0 + from numpy import trapz as np_trapezoid + _mode_to_scipy = { "constant": "constant", "edge": "nearest", @@ -552,7 +557,7 @@ def test_trapz_val(self, rng, shape, axis, use_x): """Test trapz values against NumPy for different array dimensions and integration axes.""" y, x, dx = self.generate_y_x_dx(rng, shape, use_x) result_custom = trapz(y, x=x, dx=dx, axis=axis) - result_numpy = np.trapz(y, x=x, dx=dx, axis=axis) + result_numpy = np_trapezoid(y, x=x, dx=dx, axis=axis) npt.assert_allclose(result_custom, result_numpy) def test_trapz_grad(self, rng, shape, axis, use_x): diff --git a/tidy3d/components/base.py b/tidy3d/components/base.py index cc7a8d91c5..d152a2d539 100644 --- a/tidy3d/components/base.py +++ b/tidy3d/components/base.py @@ -8,11 +8,12 @@ import math import os import tempfile +from collections.abc import Callable from functools import wraps from math import ceil from os import PathLike from pathlib import Path -from typing import Any, Callable, Literal, Optional, Union +from typing import Any, Literal, Optional, Union, get_args, get_origin import h5py import numpy as np @@ -188,6 +189,36 @@ def __init__(self, **kwargs: Any) -> None: self._post_init_validators() log.end_capture(self) + @classmethod + def _field_allows_scalar(cls, field: ModelField) -> bool: + annotation = field.outer_type_ + + def allows_scalar(a: Any) -> bool: + origin = get_origin(a) + if origin is Union: + args = (arg for arg in get_args(a) if arg is not type(None)) + return any(allows_scalar(arg) for arg in args) + if origin is not None: + return False + return isinstance(a, type) and issubclass(a, (float, int, np.generic)) + + return allows_scalar(annotation) + + @pydantic.root_validator(pre=True) + def _coerce_numpy_scalars(cls, values: dict[str, Any]) -> dict[str, Any]: + if not isinstance(values, dict): + return values + + for name, field in cls.__fields__.items(): + if name not in values or not cls._field_allows_scalar(field): + continue + + value = values[name] + if isinstance(value, np.generic) or (isinstance(value, np.ndarray) and value.size == 1): + values[name] = value.item() + + return values + def _post_init_validators(self) -> None: """Call validators taking ``self`` that get run after init, implement in subclasses.""" diff --git a/tidy3d/components/geometry/base.py b/tidy3d/components/geometry/base.py index 9a2fc61401..af655f6819 100644 --- a/tidy3d/components/geometry/base.py +++ b/tidy3d/components/geometry/base.py @@ -5,9 +5,9 @@ import functools import pathlib from abc import ABC, abstractmethod -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence from os import PathLike -from typing import TYPE_CHECKING, Any, Callable, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union import autograd.numpy as np import pydantic.v1 as pydantic @@ -20,6 +20,7 @@ except ImportError: pass + from tidy3d.compat import _shapely_is_older_than from tidy3d.components.autograd import ( AutogradFieldMap, @@ -3544,13 +3545,11 @@ def inside_meshgrid( def _volume(self, bounds: Bound) -> float: """Returns object's volume within given bounds.""" - individual_volumes = (geometry.volume(bounds) for geometry in self.geometries) - return np.sum(individual_volumes) + return sum(geometry.volume(bounds) for geometry in self.geometries) def _surface_area(self, bounds: Bound) -> float: """Returns object's surface area within given bounds.""" - individual_areas = (geometry.surface_area(bounds) for geometry in self.geometries) - return np.sum(individual_areas) + return sum(geometry.surface_area(bounds) for geometry in self.geometries) @cached_property def _normal_2dmaterial(self) -> Axis: diff --git a/tidy3d/components/geometry/utils.py b/tidy3d/components/geometry/utils.py index 9a483278ec..bb45e7ba8b 100644 --- a/tidy3d/components/geometry/utils.py +++ b/tidy3d/components/geometry/utils.py @@ -736,7 +736,7 @@ def _shift_value_signed( # get the index of the grid cell where the obj lies obj_position = obj.center[normal_axis] - obj_pos_gt_grid_bounds = np.argwhere(obj_position > grid_boundaries) + obj_pos_gt_grid_bounds = np.argwhere(obj_position > grid_boundaries)[:, 0] # no obj index can be determined if len(obj_pos_gt_grid_bounds) == 0 or obj_position > grid_boundaries[-1]: diff --git a/tidy3d/components/medium.py b/tidy3d/components/medium.py index 0808ec9568..baca817911 100644 --- a/tidy3d/components/medium.py +++ b/tidy3d/components/medium.py @@ -4,8 +4,9 @@ import functools from abc import ABC, abstractmethod +from collections.abc import Callable from math import isclose -from typing import Any, Callable, Literal, Optional, Union +from typing import Any, Literal, Optional, Union import autograd.numpy as np @@ -3092,7 +3093,7 @@ def _compute_derivatives(self, derivative_info: DerivativeInfo) -> AutogradField for freq in freqs: dJ_deps_complex_f = dJ_deps_complex.sel(f=freq) vjps_f = self._get_vjps_from_params( - dJ_deps_complex=complex(dJ_deps_complex_f), + dJ_deps_complex=complex(dJ_deps_complex_f.item()), poles_vals=poles_vals, omega=2 * np.pi * freq, requested_paths=derivative_info.paths,