|
5 | 5 | from abc import ABC |
6 | 6 | from typing import Tuple, Union |
7 | 7 |
|
| 8 | +import numpy as np |
8 | 9 | import pydantic.v1 as pd |
9 | 10 |
|
10 | 11 | from tidy3d.components.base import Tidy3dBaseModel, skip_if_fields_missing |
11 | 12 | from tidy3d.constants import MICROMETER |
12 | 13 | from tidy3d.exceptions import ValidationError |
13 | 14 |
|
| 15 | +from ..geometry.base import Box |
| 16 | +from ..types import Coordinate, annotate_type |
| 17 | + |
14 | 18 |
|
15 | 19 | class UnstructuredGrid(Tidy3dBaseModel, ABC): |
16 | 20 | """Abstract unstructured grid.""" |
@@ -60,6 +64,92 @@ class UniformUnstructuredGrid(UnstructuredGrid): |
60 | 64 | ) |
61 | 65 |
|
62 | 66 |
|
| 67 | +class GridRefinementRegion(Box): |
| 68 | + """Refinement region for the unstructured mesh. The cell size is enforced to be constant inside the region. |
| 69 | + The cell size outside of the region depends on the distance from the region.""" |
| 70 | + |
| 71 | + dl_internal: pd.PositiveFloat = pd.Field( |
| 72 | + ..., |
| 73 | + title="Internal mesh cell size", |
| 74 | + description="Mesh cell size inside the refinement region", |
| 75 | + units=MICROMETER, |
| 76 | + ) |
| 77 | + |
| 78 | + transition_thickness: pd.NonNegativeFloat = pd.Field( |
| 79 | + ..., |
| 80 | + title="Interface Distance", |
| 81 | + description="Thickness of a transition layer outside the box where the mesh cell size changes from the" |
| 82 | + "internal size to the external one.", |
| 83 | + units=MICROMETER, |
| 84 | + ) |
| 85 | + |
| 86 | + |
| 87 | +class GridRefinementLine(Tidy3dBaseModel, ABC): |
| 88 | + """Refinement line for the unstructured mesh. The cell size depends on the distance from the line.""" |
| 89 | + |
| 90 | + r1: Coordinate = pd.Field( |
| 91 | + ..., |
| 92 | + title="Start point of the line", |
| 93 | + description="Start point of the line in x, y, and z.", |
| 94 | + units=MICROMETER, |
| 95 | + ) |
| 96 | + |
| 97 | + r2: Coordinate = pd.Field( |
| 98 | + ..., |
| 99 | + title="End point of the line", |
| 100 | + description="End point of the line in x, y, and z.", |
| 101 | + units=MICROMETER, |
| 102 | + ) |
| 103 | + |
| 104 | + @pd.validator("r1", always=True) |
| 105 | + def _r1_not_inf(cls, val): |
| 106 | + """Make sure the point is not infinitiy.""" |
| 107 | + if any(np.isinf(v) for v in val): |
| 108 | + raise ValidationError("Point can not contain td.inf terms.") |
| 109 | + return val |
| 110 | + |
| 111 | + @pd.validator("r2", always=True) |
| 112 | + def _r2_not_inf(cls, val): |
| 113 | + """Make sure the point is not infinitiy.""" |
| 114 | + if any(np.isinf(v) for v in val): |
| 115 | + raise ValidationError("Point can not contain td.inf terms.") |
| 116 | + return val |
| 117 | + |
| 118 | + dl_near: pd.PositiveFloat = pd.Field( |
| 119 | + ..., |
| 120 | + title="Mesh cell size near the line", |
| 121 | + description="Mesh cell size near the line", |
| 122 | + units=MICROMETER, |
| 123 | + ) |
| 124 | + |
| 125 | + distance_near: pd.NonNegativeFloat = pd.Field( |
| 126 | + ..., |
| 127 | + title="Near distance", |
| 128 | + description="Distance from the line within which ``dl_near`` is enforced." |
| 129 | + "Typically the same as ``dl_near`` or its multiple.", |
| 130 | + units=MICROMETER, |
| 131 | + ) |
| 132 | + |
| 133 | + distance_bulk: pd.NonNegativeFloat = pd.Field( |
| 134 | + ..., |
| 135 | + title="Bulk distance", |
| 136 | + description="Distance from the line outside of which ``dl_bulk`` is enforced." |
| 137 | + "Typically twice of ``dl_bulk`` or its multiple. Use larger values for a smoother " |
| 138 | + "transition from ``dl_near`` to ``dl_bulk``.", |
| 139 | + units=MICROMETER, |
| 140 | + ) |
| 141 | + |
| 142 | + @pd.validator("distance_bulk", always=True) |
| 143 | + @skip_if_fields_missing(["distance_near"]) |
| 144 | + def names_exist_bcs(cls, val, values): |
| 145 | + """Error if distance_bulk is less than distance_near""" |
| 146 | + distance_near = values.get("distance_near") |
| 147 | + if distance_near > val: |
| 148 | + raise ValidationError("'distance_bulk' cannot be smaller than 'distance_near'.") |
| 149 | + |
| 150 | + return val |
| 151 | + |
| 152 | + |
63 | 153 | class DistanceUnstructuredGrid(UnstructuredGrid): |
64 | 154 | """Adaptive grid based on distance to material interfaces. Currently not recommended for larger |
65 | 155 | simulations. |
@@ -126,6 +216,14 @@ class DistanceUnstructuredGrid(UnstructuredGrid): |
126 | 216 | "``dl_bulk`` is used instead.", |
127 | 217 | ) |
128 | 218 |
|
| 219 | + mesh_refinements: Tuple[annotate_type(Union[GridRefinementRegion, GridRefinementLine]), ...] = ( |
| 220 | + pd.Field( |
| 221 | + (), |
| 222 | + title="Mesh refinement structures", |
| 223 | + description="List of regions/lines for which the mesh refinement will be applied", |
| 224 | + ) |
| 225 | + ) |
| 226 | + |
129 | 227 | @pd.validator("distance_bulk", always=True) |
130 | 228 | @skip_if_fields_missing(["distance_interface"]) |
131 | 229 | def names_exist_bcs(cls, val, values): |
|
0 commit comments