|
22 | 22 | from tidy3d.components.autograd.constants import GRADIENT_DTYPE_FLOAT
|
23 | 23 | from tidy3d.components.autograd.derivative_utils import DerivativeInfo, integrate_within_bounds
|
24 | 24 | from tidy3d.components.base import Tidy3dBaseModel, cached_property
|
| 25 | +from tidy3d.components.geometry.bound_ops import bounds_intersection, bounds_union |
25 | 26 | from tidy3d.components.transformation import ReflectionFromPlane, RotationAroundAxis
|
26 | 27 | from tidy3d.components.types import (
|
27 | 28 | ArrayFloat2D,
|
|
51 | 52 | polygon_patch,
|
52 | 53 | set_default_labels_and_title,
|
53 | 54 | )
|
54 |
| -from tidy3d.constants import LARGE_NUMBER, MICROMETER, RADIAN, fp_eps, inf |
| 55 | +from tidy3d.constants import EPSILON_0, LARGE_NUMBER, MICROMETER, MU_0, RADIAN, fp_eps, inf |
55 | 56 | from tidy3d.exceptions import (
|
56 | 57 | SetupError,
|
57 | 58 | Tidy3dError,
|
|
62 | 63 | from tidy3d.log import log
|
63 | 64 | from tidy3d.packaging import verify_packages_import
|
64 | 65 |
|
65 |
| -from .bound_ops import bounds_intersection, bounds_union |
66 |
| - |
67 | 66 | POLY_GRID_SIZE = 1e-12
|
68 | 67 | POLY_TOLERANCE_RATIO = 1e-12
|
69 | 68 |
|
@@ -2439,11 +2438,12 @@ def _derivative_face(
|
2439 | 2438 |
|
2440 | 2439 | # normal and tangential dims
|
2441 | 2440 | dim_normal, dims_perp = self.pop_axis("xyz", axis=axis_normal)
|
2442 |
| - fld_normal, flds_perp = self.pop_axis(("Ex", "Ey", "Ez"), axis=axis_normal) |
| 2441 | + fld_E_normal, flds_E_perp = self.pop_axis(("Ex", "Ey", "Ez"), axis=axis_normal) |
| 2442 | + fld_H_normal, flds_H_perp = self.pop_axis(("Hx", "Hy", "Hz"), axis=axis_normal) |
2443 | 2443 |
|
2444 | 2444 | # fields and bounds
|
2445 |
| - D_normal = derivative_info.D_der_map[fld_normal] |
2446 |
| - Es_perp = tuple(derivative_info.E_der_map[key] for key in flds_perp) |
| 2445 | + D_normal = derivative_info.D_der_map[fld_E_normal] |
| 2446 | + Es_perp = tuple(derivative_info.E_der_map[key] for key in flds_E_perp) |
2447 | 2447 | bounds_normal, bounds_perp = self.pop_axis(
|
2448 | 2448 | np.array(derivative_info.bounds).T, axis=axis_normal
|
2449 | 2449 | )
|
@@ -2497,33 +2497,159 @@ def _derivative_face(
|
2497 | 2497 | delta_eps_perps = [eps_in - eps_out for eps_in, eps_out in zip(eps_in_perps, eps_out_perps)]
|
2498 | 2498 | delta_eps_inv_normal = 1.0 / eps_in_normal - 1.0 / eps_out_normal
|
2499 | 2499 |
|
2500 |
| - def integrate_face(arr: xr.DataArray) -> complex: |
| 2500 | + def integrate_face( |
| 2501 | + arr: xr.DataArray, |
| 2502 | + snap_coords_outside: bool = False, |
| 2503 | + singularity_correction=None, |
| 2504 | + integration_dims=dims_perp, |
| 2505 | + integration_bounds=bounds_perp, |
| 2506 | + ) -> complex: |
2501 | 2507 | """Interpolate and integrate a scalar field data over the face using bounds."""
|
2502 | 2508 |
|
2503 |
| - arr_at_face = arr.interp(**{dim_normal: float(coord_normal_face)}, assume_sorted=True) |
| 2509 | + edge_correction = 1.0 |
| 2510 | + |
| 2511 | + interpolation_point = coord_normal_face |
| 2512 | + if snap_coords_outside: |
| 2513 | + snap_coords_values = arr.coords[dim_normal].values |
| 2514 | + |
| 2515 | + index_face = np.argmin(np.abs(snap_coords_values - coord_normal_face)) |
| 2516 | + |
| 2517 | + if min_max_index == 0: |
| 2518 | + min_boundary_mapping = np.where(snap_coords_values > coord_normal_face)[0] |
| 2519 | + index_face = ( |
| 2520 | + 0 |
| 2521 | + if (len(min_boundary_mapping) == 0) |
| 2522 | + else np.maximum(0, min_boundary_mapping[0] - 1) |
| 2523 | + ) |
| 2524 | + |
| 2525 | + if snap_coords_values[index_face] > coord_normal_face: |
| 2526 | + log.warning("Was not able to snap coordinates outside of min face.") |
| 2527 | + else: |
| 2528 | + max_boundary_mapping = np.where(snap_coords_values < coord_normal_face)[0] |
| 2529 | + index_face = ( |
| 2530 | + 0 |
| 2531 | + if (len(max_boundary_mapping) == 0) |
| 2532 | + else np.minimum(len(snap_coords_values) - 1, max_boundary_mapping[-1] + 1) |
| 2533 | + ) |
| 2534 | + |
| 2535 | + if snap_coords_values[index_face] < coord_normal_face: |
| 2536 | + log.warning("Was not able to snap coordinates outside of max face.") |
| 2537 | + |
| 2538 | + interpolation_point = snap_coords_values[index_face] |
| 2539 | + |
| 2540 | + if singularity_correction: |
| 2541 | + edge_correction = singularity_correction( |
| 2542 | + np.abs(interpolation_point - coord_normal_face) |
| 2543 | + ) |
| 2544 | + |
| 2545 | + arr_at_face = ( |
| 2546 | + arr |
| 2547 | + if (len(arr.coords[dim_normal]) == 1) |
| 2548 | + else arr.interp(**{dim_normal: float(interpolation_point)}, assume_sorted=True) |
| 2549 | + ) |
2504 | 2550 |
|
2505 | 2551 | integral_result = integrate_within_bounds(
|
2506 | 2552 | arr=arr_at_face,
|
2507 |
| - dims=dims_perp, |
2508 |
| - bounds=bounds_perp, |
| 2553 | + dims=integration_dims, |
| 2554 | + bounds=integration_bounds, |
2509 | 2555 | )
|
2510 | 2556 |
|
2511 |
| - return complex(integral_result.sum("f")) |
| 2557 | + return complex(edge_correction * integral_result.sum("f")) |
2512 | 2558 |
|
2513 | 2559 | # compute vjp from field integrals
|
2514 | 2560 | vjp_value = 0.0
|
2515 | 2561 |
|
2516 |
| - # perform D-normal integral |
2517 |
| - integrand_D = -delta_eps_inv_normal * D_normal |
2518 |
| - integral_D = integrate_face(integrand_D) |
2519 |
| - vjp_value += integral_D |
| 2562 | + if derivative_info.is_medium_pec: |
| 2563 | + Hs_perp = tuple(derivative_info.H_der_map[key] for key in flds_H_perp) |
| 2564 | + |
| 2565 | + # detect whether the box is 2-dimensional |
| 2566 | + zero_size_map = [s == 0.0 for s in self.size] |
| 2567 | + |
| 2568 | + dimension = 3 - np.sum(zero_size_map) |
| 2569 | + if dimension < 2: |
| 2570 | + log.error( |
| 2571 | + "Derivative of PEC material with less than 2 dimensions is unsupported. " |
| 2572 | + "Specified PEC box is {dimension}-dimesional" |
| 2573 | + ) |
| 2574 | + |
| 2575 | + do_singularity_correction = False |
| 2576 | + is_2d = np.any(zero_size_map) |
| 2577 | + if is_2d: |
| 2578 | + zero_dim_idx = np.where(zero_size_map)[0][0] |
| 2579 | + zero_dimension = "xyz"[zero_dim_idx] |
| 2580 | + |
| 2581 | + # for singularity correction, need 2-dimensional box and that |
| 2582 | + # the face we are integrating over is the 1-dimensional (i.e. - |
| 2583 | + # the normal for the face is not the same as the flat dimension |
| 2584 | + do_singularity_correction = not (axis_normal == zero_dim_idx) |
| 2585 | + |
| 2586 | + # apply singularity correction only when integrating along the line |
| 2587 | + singularity_correction = ( |
| 2588 | + (lambda d: 0.5 * np.pi * d) if do_singularity_correction else (lambda d: 1.0) |
| 2589 | + ) |
| 2590 | + |
| 2591 | + integration_dims = dims_perp |
| 2592 | + integration_bounds = bounds_perp |
| 2593 | + # if we are correcting for singularity and integrating over the line, then adjust |
| 2594 | + # integration dimensions and bounds to exclude the flat dimension |
| 2595 | + if do_singularity_correction: |
| 2596 | + integration_dims = [dim for dim in dims_perp if (not (dim == zero_dimension))] |
| 2597 | + |
| 2598 | + integration_bounds = [] |
| 2599 | + zero_dim_idx = 0 |
| 2600 | + # find the index into the bounds that corresponds to the flat dimension |
| 2601 | + for dim in dims_perp: |
| 2602 | + if dim == zero_dimension: |
| 2603 | + break |
| 2604 | + zero_dim_idx += 1 |
| 2605 | + |
| 2606 | + # trim the zero dimension from the integration bounds |
| 2607 | + for bound in bounds_perp: |
| 2608 | + new_bound = [b for b_idx, b in enumerate(bound) if b_idx != zero_dim_idx] |
| 2609 | + |
| 2610 | + integration_bounds.append(new_bound) |
| 2611 | + |
| 2612 | + integrand_E = D_normal / np.real(eps_out_normal) |
| 2613 | + |
| 2614 | + integral_E = integrate_face( |
| 2615 | + integrand_E, |
| 2616 | + snap_coords_outside=True, |
| 2617 | + singularity_correction=singularity_correction, |
| 2618 | + integration_dims=integration_dims, |
| 2619 | + integration_bounds=integration_bounds, |
| 2620 | + ) |
2520 | 2621 |
|
2521 |
| - # perform E-perpendicular integrals |
2522 |
| - for E_perp, delta_eps_perp in zip(Es_perp, delta_eps_perps): |
2523 |
| - integrand_E = E_perp * delta_eps_perp |
2524 |
| - integral_E = integrate_face(integrand_E) |
2525 | 2622 | vjp_value += integral_E
|
2526 | 2623 |
|
| 2624 | + for H_perp_idx, H_perp in enumerate(Hs_perp): |
| 2625 | + integrand_H = MU_0 * H_perp / EPSILON_0 |
| 2626 | + |
| 2627 | + if (is_2d and not (flds_H_perp[H_perp_idx] == f"H{zero_dimension}")) and ( |
| 2628 | + dim_normal != zero_dimension |
| 2629 | + ): |
| 2630 | + continue |
| 2631 | + |
| 2632 | + integral_H = integrate_face( |
| 2633 | + integrand_H, |
| 2634 | + snap_coords_outside=True, |
| 2635 | + singularity_correction=singularity_correction, |
| 2636 | + integration_dims=integration_dims, |
| 2637 | + integration_bounds=integration_bounds, |
| 2638 | + ) |
| 2639 | + |
| 2640 | + vjp_value += integral_H |
| 2641 | + |
| 2642 | + else: |
| 2643 | + integrand_D = -delta_eps_inv_normal * D_normal |
| 2644 | + integral_D = integrate_face(integrand_D) |
| 2645 | + vjp_value += integral_D |
| 2646 | + |
| 2647 | + # perform E-perpendicular integrals |
| 2648 | + for E_perp, delta_eps_perp in zip(Es_perp, delta_eps_perps): |
| 2649 | + integrand_E = E_perp * delta_eps_perp |
| 2650 | + integral_E = integrate_face(integrand_E) |
| 2651 | + vjp_value += integral_E |
| 2652 | + |
2527 | 2653 | return np.real(vjp_value)
|
2528 | 2654 |
|
2529 | 2655 |
|
|
0 commit comments