Python 3D FDTD模拟器
翻译自:https://github.com/flaport/fdtd
未获得作者翻译授权,只是为方便自己查看。
一个用Python编写的三维电磁FDTD模拟器。FDTD模拟器有一个可选的PyTorch后端,支持GPU上的FDTD模拟。
安装
fdtd
库可以用pip
安装:
pip install fdtd
可以通过克隆存储库来安装开发版本:
git clone http://github.com/flaport/fdtd
并与pip
连接
pip install -e fdtd
可以安装开发依赖项:
pip install -e fdtd[dev]
依赖项
- python 3.6+
- numpy
- scipy
- matplotlib
- tqdm
- pytorch (optional)
贡献
欢迎所有改进或添加(例如新的对象、源或检测器)。请提出拉拔请求 😊。
文档
在这里阅读文档: https://fdtd.readthedocs.org
导入
fdtd
库简单地导入如下:
import fdtd
设置后端
fdtd
库允许选择后端。numpy
后端是默认的,但也有几个额外的PyTorch
后端:
"numpy"
(默认为float64数组)"torch"
(默认为float64数组)"torch.float32"
"torch.float64"
"torch.cuda"
(默认为float64数组)"torch.cuda.float32"
"torch.cuda.float64"
例如,这是如何选择torch
后端:
fdtd.set_backend("torch")
一般来说,numpy
后端首选用于具有float64
精度的标准CPU计算。一般来说,在FDTD模拟中,float64
的精度总是优于float32
,然而,float32
可能会提供显著的性能提升。
cuda
后端只适用于带有GPU的计算机。
FDTD网格
FDTD网格定义了仿真区域。
# signature
fdtd.Grid(
shape: Tuple[Number, Number, Number],
grid_spacing: float = 155e-9,
permittivity: float = 1.0,
permeability: float = 1.0,
courant_number: float = None,
)
网格是由它的shape
定义的,它只是一个Number
类型(整数或浮点数)的3D元组。如果形状以浮动形式给出,则表示网格的宽度、高度和长度(以米为单位)。如果形状以整数形式给出,则用grid_spacing
表示网格的宽度、高度和长度。在内部,这些数字将被转换为三个整数:grid.Nx
, grid.Ny
和grid.Nz
。
可以给出一个grid_spacing
。出于稳定性考虑,建议选择比网格中最小波长至少小10倍的网格间距。这意味着对于包含波长为1550nm的光源和折射率为3.1的材料的栅格,推荐的grid_spacing
间距是50pm
对于有一下形式的permittivity
和permeability
的浮点数或数组:
(grid.Nx, grid.Ny, grid.Nz)
- or
(grid.Nx, grid.Ny, grid.Nz, 1)
- or
(grid.Nx, grid.Ny, grid.Nz, 3)
在最后一种情况下,这种形状暗示了每个长轴(所谓的单轴或双轴材料)的不同介电常数的可能性。在内部,这些变量将(出于性能原因)转换为它们的反向 grid.inverse_permittivity
阵列和grid.inverse_permeability
阵列的形状(grid.Nx, grid.Ny, grid.Nz, 3)
。在制作网格之后,可以改变这些数组。
最后,网格的courant_number
决定了仿真的time_step
与grid_spacing
之间的关系。如果没有给出,则选择为 Courant-Friedrichs-Lewy Condition
所允许的最大值:
1
为 1D
模拟, 1/√2
为2D
模拟 , 1/√3
为 3D
模拟 (根据网格的形状计算维数)。出于稳定性考虑,建议不要更改此值。
grid = fdtd.Grid(
shape = (25e-6, 15e-6, 1), # 25um x 15um x 1 (grid_spacing) --> 2D FDTD
)
print(grid)
Grid(shape=(161,97,1), grid_spacing=1.55e-07, courant_number=0.70)
向网格中添加对象
另一种局部改变栅极 permittivity
或permeability
的方法是向栅极添加一个Object
。
# signature
fdtd.Object(
permittivity: Tensorlike,
name: str = None
)
一个对象用修改的更新方程定义网格的一部分,允许引入例如吸收材料或双轴材料,轴之间的混合通过Pockels coefficients
或更多。在这种情况下,我们将使一个物体的permittivity
不同于它所在的网格。
就像网格一样,Object
期望permittivity
为float或以下可能形状的数组
(obj.Nx, obj.Ny, obj.Nz)
- or
(obj.Nx, obj.Ny, obj.Nz, 1)
- or
(obj.Nx, obj.Ny, obj.Nz, 3)
注意, obj.Nx
, obj.Ny
and obj.Nz
没有给出给对象构造函数。它们是从它在网格中的位置派生出来的:
grid[11:32, 30:84, 0] = fdtd.Object(permittivity=1.7**2, name="object")
这里发生了几件事。首先,对象在网格中被赋予空间[11:32,30:84,0]
。因为它被赋予了这个空间,对象的Nx
, Ny
和Nz
会被自动设置。此外,通过为对象提供一个名称,该名称将在网格中可用:
print(grid.object)
Object(name='object')
@ x=11:32, y=30:84, z=0:1
第二个对象可以添加到网格中:
grid[13e-6:18e-6, 5e-6:8e-6, 0] = fdtd.Object(permittivity=1.5**2)
这里选择了一个带有浮点数的切片。在对象注册期间,这些浮点数将被整数Nx
、Ny
和Nz
替换。由于该对象没有接收到名称,因此该对象不能作为网格的属性使用。然而,它仍然可以通过 grid.objects
列表:
print(grid.objects)
[Object(name='object'), Object(name=None)]
这个列表存储所有对象(例如类型为fdtd.Object
的对象),按照它们被添加到网格的顺序
向网格中添加一个源
类似于将对象添加到网格中,一个fdtd.LineSource
也可以添加:
# signature
fdtd.LineSource(
period: Number = 15, # timesteps or seconds
power: float = 1.0,
phase_shift: float = 0.0,
name: str = None,
)
就像fdtd.Object
一样,一个fdtd.LineSource
的大小由它在网格中的位置来定义:
grid[7.5e-6:8.0e-6, 11.8e-6:13.0e-6, 0] = fdtd.LineSource(
period = 1550e-9 / (3e8), name="source"
)
然而,需要注意的是,在这种情况下,一个LineSource
被添加到网格中,也就是说,源跨越了由切片定义的立方体的对角线。在内部,这些片将被转换为列表,以确保以下行为:
print(grid.source)
LineSource(period=14, power=1.0, phase_shift=0.0, name='source')
@ x=[48, ... , 51], y=[76, ... , 83], z=[0, ... , 0]
请注意,我们也可以首先提供列表来索引网格。这个特性对于创建任意形状的LineSource
非常有用。
给网格添加一个检测器
# signature
fdtd.LineDetector(
name=None
)
向网格中添加检测器的工作原理与添加源相同
grid[12e-6, :, 0] = fdtd.LineDetector(name="detector")
print(grid.detector)
LineDetector(name='detector')
@ x=[77, ... , 77], y=[0, ... , 96], z=[0, ... , 0]
添加网格边界
# signature
fdtd.PML(
a: float = 1e-8, # stability factor
name: str = None
)
虽然,有一个对象,源和探测器,以进行FDTD模拟在原则上是足够的,人们还需要定义一个网格边界,以防止场被反射。其中一个可以添加到网格的边界是 Perfectly Matched Layer or PML
完美匹配层。这些基本上是吸收边界。
# x boundaries
grid[0:10, :, :] = fdtd.PML(name="pml_xlow")
grid[-10:, :, :] = fdtd.PML(name="pml_xhigh")
# y boundaries
grid[:, 0:10, :] = fdtd.PML(name="pml_ylow")
grid[:, -10:, :] = fdtd.PML(name="pml_yhigh")
网格的总结
可以通过打印出网格来显示网格的简单摘要:
print(grid)
Grid(shape=(161,97,1), grid_spacing=1.55e-07, courant_number=0.70)
sources:
LineSource(period=14, power=1.0, phase_shift=0.0, name='source')
@ x=[48, ... , 51], y=[76, ... , 83], z=[0, ... , 0]
detectors:
LineDetector(name='detector')
@ x=[77, ... , 77], y=[0, ... , 96], z=[0, ... , 0]
boundaries:
PML(name='pml_xlow')
@ x=0:10, y=:, z=:
PML(name='pml_xhigh')
@ x=-10:, y=:, z=:
PML(name='pml_ylow')
@ x=:, y=0:10, z=:
PML(name='pml_yhigh')
@ x=:, y=-10:, z=:
objects:
Object(name='object')
@ x=11:32, y=30:84, z=0:1
Object(name=None)
@ x=84:116, y=32:52, z=0:1
运行一个仿真
运行一个模拟就像使用grid.run
方法一样简单。
grid.run(
total_time: Number,
progress_bar: bool = True
)
与网格中的长度一样,模拟的total_time
可以指定为整数(time_steps
的数量)或浮点数(以秒为单位)。
grid.run(total_time=100)
网格可视化
Le让我们把网格形象化。这可以通过grid.visualize
来实现方法:
# signature
grid.visualize(
grid,
x=None,
y=None,
z=None,
cmap="Blues",
pbcolor="C3",
pmlcolor=(0, 0, 0, 0.1),
objcolor=(1, 0, 0