diff --git a/docs/.documenter-siteinfo.json b/docs/.documenter-siteinfo.json index f03c537..d13a00e 100644 --- a/docs/.documenter-siteinfo.json +++ b/docs/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.0","generation_timestamp":"2024-01-25T16:10:05","documenter_version":"1.2.1"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.0","generation_timestamp":"2024-01-30T23:44:56","documenter_version":"1.2.1"}} \ No newline at end of file diff --git a/docs/guide/index.html b/docs/guide/index.html index 2e3a931..ef9d113 100644 --- a/docs/guide/index.html +++ b/docs/guide/index.html @@ -1,2 +1,2 @@ -- · FDTDEngine

Engineers run simulations to improve designs. Each time the design changes, the simulation is re-run. This can be done systematically in "parameter sweeps" where different combinations of parameter values are simulated to determine the best design. However, this scales exponentially wrt the number of parameters or DOFs.

General workflow

We use gradient descent, the same as in machine learning. In lieu of optimizing neural network parameters, we're optimizing geometry (or source) parameters. In each training iteration, we generate geometry, run the simulation, calculate the objective metric, and do a backward pass to derive the gradient wrt the geometry parameters. We then do a gradient based parameter update in preparation for the next iteration.

The geometry is thus the first step. It typically has a static component which we can't change such as interfacing waveguides. Then there's a design component which we can change or optimize. The user is responsible for generating the design geometry wrt design parameters. If any pattern is allowed in the design region, our sister package Jello.jl can be used as a length scale controlled geometry generator. In any case, the result needs to be a 2d/3d array of each relevant materials property eg permitivity.

With geometry ready, we can run the simulation. Duration is roughly the time it takes to reach steady state, such as how long it take for the signal to reach output port. The objective is usually a steady state metric which can be computed using values from the final period. We optimize geometry for some objective.

+- · FDTDEngine.jl

Engineers run simulations to improve designs. Each time the design changes, the simulation is re-run. This can be done systematically in "parameter sweeps" where different combinations of parameter values are simulated to determine the best design. However, this scales exponentially wrt the number of parameters or DOFs.

General workflow

We use gradient descent, the same as in machine learning. In lieu of optimizing neural network parameters, we're optimizing geometry (or source) parameters. In each training iteration, we generate geometry, run the simulation, calculate the objective metric, and do a backward pass to derive the gradient wrt the geometry parameters. We then do a gradient based parameter update in preparation for the next iteration.

The geometry is thus the first step. It typically has a static component which we can't change such as interfacing waveguides. Then there's a design component which we can change or optimize. The user is responsible for generating the design geometry wrt design parameters. If any pattern is allowed in the design region, our sister package Jello.jl can be used as a length scale controlled geometry generator. In any case, the result needs to be a 2d/3d array of each relevant materials property eg permitivity.

With geometry ready, we can run the simulation. Duration is roughly the time it takes to reach steady state, such as how long it take for the signal to reach output port. The objective is usually a steady state metric which can be computed using values from the final period. We optimize geometry for some objective.

diff --git a/docs/index.html b/docs/index.html index 3864c15..80de03d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,19 +1,13 @@ -FDTDEngine.jl · FDTDEngine

FDTDEngine.jl

Only 3d works in latest patch. 2d/1d will be fixed in future. Prerelease. Expect breaking changes

Overview

Differentiable FDTD package for inverse design & topology optimization in photonics, acoustics and RF. Uses automatic differentiation by Zygote.jl for adjoint optimization. Integrates with Jello.jl to generate length scale controlled paramaterized geometry . Staggered Yee grid update with fully featured boundary conditions & sources. Customizable physics to potentially incorporate dynamics like heat transfer, charge transport.

Periodic scattering

Quarter wavelength antenna

Inverse design of compact silicon photonic splitter (coming soon)

<!– –> <!– –>

Quickstart

We do a quick 3d simulation of plane wave scattering on periodic array of dielectric spheres (first gallery movie)

"""
-simulation of plane wave scattering on periodic array of dielectric spheres
-"""
-
-using UnPack, LinearAlgebra, GLMakie
-using FDTDEngine
-include("$(pwd())/scripts/plot_recipes.jl")
-
+FDTDEngine.jl · FDTDEngine.jl

FDTDEngine.jl

Only 3d works in latest patch. 2d/1d will be fixed in future. Prerelease. Expect breaking changes

Overview

Differentiable FDTD package for inverse design & topology optimization in photonics, acoustics and RF. Uses automatic differentiation by Zygote.jl for adjoint optimization. Integrates with Jello.jl to generate length scale controlled paramaterized geometry . Staggered Yee grid update with fully featured boundary conditions & sources. Customizable physics to potentially incorporate dynamics like heat transfer, charge transport.

Periodic scattering

Quarter wavelength antenna

Inverse design of compact silicon photonic splitter (coming soon)

In progress, split ratio isn't correct

Quickstart

We do a quick 3d simulation of plane wave scattering on periodic array of dielectric spheres (first gallery movie)

using UnPack, LinearAlgebra, GLMakie
+using FDTDEngine,FDTDToolkit
 
 F = Float32
 name = "3d_scattering"
-T = 4.0f0 # simulation duration in [periods]
+T = 8.0f0 # simulation duration in [periods]
 nres = 16
 dx = 1.0f0 / nres # pixel resolution in [wavelengths]
-Courant = 0.25f0 # Courant number
+Courant = 0.8 / √3 # Courant number
 
 "geometry"
 l = 2 # domain physical size length
@@ -32,14 +26,16 @@
     # PlaneWave(t -> t < 1 ? cos(F(2π) * t) : 0.0f0, -1; Jz=1)
 ]
 configs = setup(boundaries, sources, monitors, dx, sz; F, Courant, T)
-@unpack μ, σ, σm, dt, geometry_padding, geometry_splits, field_padding, source_effects, monitor_instances, fields, step, power = configs
+@unpack μ, σ, σm, dt, geometry_padding, geometry_splits, field_padding, source_effects, monitor_instances, fields, power = configs
 
 ϵ, μ, σ, σm = apply(geometry_padding; ϵ, μ, σ, σm)
 p = apply(geometry_splits; ϵ, μ, σ, σm)
 u0 = collect(values(fields))
 
 # run simulation
-@showtime sol = accumulate((u, t) -> step(u, p, t, configs), 0:dt:T, init=u0)
+t = 0:dt:T
+sol = similar([u0], length(t))
+@showtime sol = accumulate!((u, t) -> step!(u, p, t, configs), sol, t, init=u0)
 
 # make movie
 Ez = map(sol) do u
@@ -47,6 +43,8 @@
 end
 ϵz = p[1][3]
 dir = @__DIR__
-recordsim(Ez, ϵz, configs, "$dir/$(name)_nres_$nres.mp4", title="$name"; playback=1, bipolar=true)
-

<!– –>

Installation

Install via Pkg.add(url="https://github.com/paulxshen/FDTDEngine.jl"). You can additionally access plotting and movie making scripts via include("_your_path/scripts/plot_recipes.jl")

Implementation

Supports 1d (Ez, Hy), 2d TMz (Ez, Hx, Hy), 2d TEz (Hz, Ex, Ey) and 3d. Length and time are in units of wavelength and period. This normalization allows usage of relative permitivity and permeability in equations . Fields including electric, magnetic and current density are simply bundled as a vector of arrays . Boundary conditions pad the field arrays . PML paddings are multilayered, stateful and permanent, increasing size of field and geometry arrays. All other boundaries only add transient single layers which are subsequently consumed by finite differencing every update step. Paddings are coordinated to implictly implement staggered Yee's grid for finite differencing.

Sources

If a source has fewer nonzero dimensions than the simulation domain, its signal will get normalized along its singleton dimensions. For example, all planar sources in 3d or line sources in 2d will get scaled up by a factor of 1/dx. This way, discretisation would not affect radiated power.

PlaneWaveType
function PlaneWave(f, dims; fields...)

Constructs plane wave source

Args

  • f: time function
  • dims: eg -1 for wave coming from -x edge
  • fields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)
source
GaussianBeamType
function GaussianBeam(f, σ, center, dims; fields...)

Constructs gaussian beam source

Args

  • f: time function
  • σ: std dev length
  • dims: eg 1 for x direction
  • fields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)
source
SourceType
function Source(f, center, bounds; fields...)
-function Source(f, center, L::AbstractVector{<:Real}; fields...)

Constructs custom centered source. Can be used to specify modal sources

Args

  • f: time function
  • L: source dimensions in [wavelengths]
  • fields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)
source

Boundaries

Unspecified boundaries default to PML

PMLType
function PML(dims, d=0.25f0, σ=20.0f0)

Constructs perfectly matched layers (PML aka ABC, RBC) boundary of depth d wavelengths

source
PECType
PEC(dims)

perfect electrical conductor

source
PMCType
PMC(dims)

perfect magnetic conductor

source

Monitors

MonitorType
function Monitor(span, normal=nothing)

Constructs monitor which can span a point, line, surface, or volume

Args

  • span
  • normal: flux monitor direction
source

Physics

step1Function
function step1(u, p, t, configs)

Updates fields for 1D (Ez, Hy)

source
stepTMzFunction
function stepTMz(u, p, t, configs)

Updates fields for 2d TMz

source
stepTEzFunction
function stepTEz(u, p, t, configs)

Updates fields for 2d TEz (Hz, Ex, Ey)

source
step3Function
function step3(u, p, t, configs)

Updates fields for 3d

source

Tutorials

see examples/

Generative inverse design

Please contact us for latest scripts.

Community

Discussion & updates at Julia Discourse

Contributors

Paul Shen <pxshen@alumni.stanford.edu>

+° = π / 180 +recordsim(Ez, ϵz, configs, "$dir/$(name)_nres_$nres.mp4", title="$name"; elevation=30°, playback=1, bipolar=true) +

Installation

Install via

Pkg.add(url="https://github.com/paulxshen/FDTDEngine.jl")
+Pkg.add(url="https://github.com/paulxshen/FDTDToolkit.jl")

FDTDToolkit.jl contains visualization utilities

Implementation

Supports 1d (Ez, Hy), 2d TMz (Ez, Hx, Hy), 2d TEz (Hz, Ex, Ey) and 3d. Length and time are in units of wavelength and period. This normalization allows usage of relative permitivity and permeability in equations . Fields including electric, magnetic and current density are simply bundled as a vector of arrays . Boundary conditions pad the field arrays . PML paddings are multilayered, stateful and permanent, increasing size of field and geometry arrays. All other boundaries only add transient single layers which are subsequently consumed by finite differencing every update step. Paddings are coordinated to implictly implement staggered Yee's grid for finite differencing.

Sources

If a source has fewer nonzero dimensions than the simulation domain, its signal will get normalized along its singleton dimensions. For example, all planar sources in 3d or line sources in 2d will get scaled up by a factor of 1/dx. This way, discretisation would not affect radiated power.

PlaneWaveType
function PlaneWave(f, dims; fields...)

Constructs plane wave source

Args

  • f: time function
  • dims: eg -1 for wave coming from -x edge
  • fields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)
source
SourceType
function Source(f, center, bounds; fields...)
+function Source(f, center, L::AbstractVector{<:Real}; fields...)

Constructs custom source. Can be used to specify uniform or modal sources

Args

  • f: time function
  • L: source dimensions in [wavelengths]
  • fields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)
source

<!– GaussianBeam –>

Boundaries

Unspecified boundaries default to PML

PMLType
function PML(dims, d=0.25f0, σ=20.0f0)

Constructs perfectly matched layers (PML aka ABC, RBC) boundary of depth d wavelengths Doesn't need to be explictly declared as all unspecified boundaries default to PML

source
PECType
PEC(dims)

perfect electrical conductor dims: eg -1 for -x side

source
PMCType
PMC(dims)

perfect magnetic conductor

source

Monitors

MonitorType
function Monitor(span, normal=nothing)

Constructs monitor which can span a point, line, surface, or volume

Args

  • span
  • normal: flux monitor direction
source

Physics

step3!Function
function step3(u, p, t, configs)

Updates fields for 3d

source

<!– step1 –> <!– stepTMz –> <!– stepTEz –>

Tutorials

see examples/

Generative inverse design

Please contact us for latest scripts.

Community

Discussion & updates at Julia Discourse

Contributors

Paul Shen <pxshen@alumni.stanford.edu>

diff --git a/docs/search_index.js b/docs/search_index.js index d25a288..3916e29 100644 --- a/docs/search_index.js +++ b/docs/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"guide/#","page":"-","title":"","text":"","category":"section"},{"location":"guide/","page":"-","title":"-","text":"Engineers run simulations to improve designs. Each time the design changes, the simulation is re-run. This can be done systematically in \"parameter sweeps\" where different combinations of parameter values are simulated to determine the best design. However, this scales exponentially wrt the number of parameters or DOFs. ","category":"page"},{"location":"guide/#General-workflow","page":"-","title":"General workflow","text":"","category":"section"},{"location":"guide/","page":"-","title":"-","text":"We use gradient descent, the same as in machine learning. In lieu of optimizing neural network parameters, we're optimizing geometry (or source) parameters. In each training iteration, we generate geometry, run the simulation, calculate the objective metric, and do a backward pass to derive the gradient wrt the geometry parameters. We then do a gradient based parameter update in preparation for the next iteration.","category":"page"},{"location":"guide/","page":"-","title":"-","text":"The geometry is thus the first step. It typically has a static component which we can't change such as interfacing waveguides. Then there's a design component which we can change or optimize. The user is responsible for generating the design geometry wrt design parameters. If any pattern is allowed in the design region, our sister package Jello.jl can be used as a length scale controlled geometry generator. In any case, the result needs to be a 2d/3d array of each relevant materials property eg permitivity. ","category":"page"},{"location":"guide/","page":"-","title":"-","text":"With geometry ready, we can run the simulation. Duration is roughly the time it takes to reach steady state, such as how long it take for the signal to reach output port. The objective is usually a steady state metric which can be computed using values from the final period. We optimize geometry for some objective. ","category":"page"},{"location":"#FDTDEngine.jl","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Only 3d works in latest patch. 2d/1d will be fixed in future. Prerelease. Expect breaking changes","category":"page"},{"location":"#Overview","page":"FDTDEngine.jl","title":"Overview","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Differentiable FDTD package for inverse design & topology optimization in photonics, acoustics and RF. Uses automatic differentiation by Zygote.jl for adjoint optimization. Integrates with Jello.jl to generate length scale controlled paramaterized geometry . Staggered Yee grid update with fully featured boundary conditions & sources. Customizable physics to potentially incorporate dynamics like heat transfer, charge transport.","category":"page"},{"location":"#Gallery","page":"FDTDEngine.jl","title":"Gallery","text":"","category":"section"},{"location":"#Periodic-scattering","page":"FDTDEngine.jl","title":"Periodic scattering","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"(Image: )","category":"page"},{"location":"#Quarter-wavelength-antenna","page":"FDTDEngine.jl","title":"Quarter wavelength antenna","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"(Image: )","category":"page"},{"location":"#Inverse-design-of-compact-silicon-photonic-splitter-(coming-soon)","page":"FDTDEngine.jl","title":"Inverse design of compact silicon photonic splitter (coming soon)","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":" ","category":"page"},{"location":"#Quickstart","page":"FDTDEngine.jl","title":"Quickstart","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"We do a quick 3d simulation of plane wave scattering on periodic array of dielectric spheres (first gallery movie)","category":"page"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"\"\"\"\nsimulation of plane wave scattering on periodic array of dielectric spheres\n\"\"\"\n\nusing UnPack, LinearAlgebra, GLMakie\nusing FDTDEngine\ninclude(\"$(pwd())/scripts/plot_recipes.jl\")\n\n\nF = Float32\nname = \"3d_scattering\"\nT = 4.0f0 # simulation duration in [periods]\nnres = 16\ndx = 1.0f0 / nres # pixel resolution in [wavelengths]\nCourant = 0.25f0 # Courant number\n\n\"geometry\"\nl = 2 # domain physical size length\nsz = nres .* (l, l, l) # domain voxel dimensions\nϵ1 = 1 #\nϵ2 = 2.25f0 # \nb = F.([norm(v .- sz ./ 2) < 0.5 / dx for v = Base.product(Base.oneto.(sz)...)]) # sphere\nϵ = ϵ2 * b + ϵ1 * (1 .- b)\n\n\"setup\"\nboundaries = [Periodic(2), Periodic(3)]# unspecified boundaries default to PML\n# n = [1, 0, 0]\nmonitors = []\nsources = [\n PlaneWave(t -> cos(F(2π) * t), -1; Jz=1) # Jz excited plane wave from -x plane (eg -1)\n # PlaneWave(t -> t < 1 ? cos(F(2π) * t) : 0.0f0, -1; Jz=1)\n]\nconfigs = setup(boundaries, sources, monitors, dx, sz; F, Courant, T)\n@unpack μ, σ, σm, dt, geometry_padding, geometry_splits, field_padding, source_effects, monitor_instances, fields, step, power = configs\n\nϵ, μ, σ, σm = apply(geometry_padding; ϵ, μ, σ, σm)\np = apply(geometry_splits; ϵ, μ, σ, σm)\nu0 = collect(values(fields))\n\n# run simulation\n@showtime sol = accumulate((u, t) -> step(u, p, t, configs), 0:dt:T, init=u0)\n\n# make movie\nEz = map(sol) do u\n u[3]\nend\nϵz = p[1][3]\ndir = @__DIR__\nrecordsim(Ez, ϵz, configs, \"$dir/$(name)_nres_$nres.mp4\", title=\"$name\"; playback=1, bipolar=true)\n","category":"page"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"","category":"page"},{"location":"#Installation","page":"FDTDEngine.jl","title":"Installation","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Install via Pkg.add(url=\"https://github.com/paulxshen/FDTDEngine.jl\"). You can additionally access plotting and movie making scripts via include(\"_your_path/scripts/plot_recipes.jl\") ","category":"page"},{"location":"#Implementation","page":"FDTDEngine.jl","title":"Implementation","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Supports 1d (Ez, Hy), 2d TMz (Ez, Hx, Hy), 2d TEz (Hz, Ex, Ey) and 3d. Length and time are in units of wavelength and period. This normalization allows usage of relative permitivity and permeability in equations . Fields including electric, magnetic and current density are simply bundled as a vector of arrays . Boundary conditions pad the field arrays . PML paddings are multilayered, stateful and permanent, increasing size of field and geometry arrays. All other boundaries only add transient single layers which are subsequently consumed by finite differencing every update step. Paddings are coordinated to implictly implement staggered Yee's grid for finite differencing.","category":"page"},{"location":"#Sources","page":"FDTDEngine.jl","title":"Sources","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"If a source has fewer nonzero dimensions than the simulation domain, its signal will get normalized along its singleton dimensions. For example, all planar sources in 3d or line sources in 2d will get scaled up by a factor of 1/dx. This way, discretisation would not affect radiated power.","category":"page"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"PlaneWave\nGaussianBeam\nSource","category":"page"},{"location":"#PlaneWave","page":"FDTDEngine.jl","title":"PlaneWave","text":"function PlaneWave(f, dims; fields...)\n\nConstructs plane wave source\n\nArgs\n\nf: time function\ndims: eg -1 for wave coming from -x edge\nfields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)\n\n\n\n\n\n","category":"type"},{"location":"#GaussianBeam","page":"FDTDEngine.jl","title":"GaussianBeam","text":"function GaussianBeam(f, σ, center, dims; fields...)\n\nConstructs gaussian beam source\n\nArgs\n\nf: time function\nσ: std dev length\ndims: eg 1 for x direction\nfields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)\n\n\n\n\n\n","category":"type"},{"location":"#Source","page":"FDTDEngine.jl","title":"Source","text":"function Source(f, center, bounds; fields...)\nfunction Source(f, center, L::AbstractVector{<:Real}; fields...)\n\nConstructs custom centered source. Can be used to specify modal sources\n\nArgs\n\nf: time function\nL: source dimensions in [wavelengths]\nfields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)\n\n\n\n\n\n","category":"type"},{"location":"#Boundaries","page":"FDTDEngine.jl","title":"Boundaries","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Unspecified boundaries default to PML ","category":"page"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Periodic\nPML\nPEC\nPMC","category":"page"},{"location":"#Periodic","page":"FDTDEngine.jl","title":"Periodic","text":"Periodic(dims)\n\nperiodic boundary\n\n\n\n\n\n","category":"type"},{"location":"#PML","page":"FDTDEngine.jl","title":"PML","text":"function PML(dims, d=0.25f0, σ=20.0f0)\n\nConstructs perfectly matched layers (PML aka ABC, RBC) boundary of depth d wavelengths \n\n\n\n\n\n","category":"type"},{"location":"#PEC","page":"FDTDEngine.jl","title":"PEC","text":"PEC(dims)\n\nperfect electrical conductor\n\n\n\n\n\n","category":"type"},{"location":"#PMC","page":"FDTDEngine.jl","title":"PMC","text":"PMC(dims)\n\nperfect magnetic conductor\n\n\n\n\n\n","category":"type"},{"location":"#Monitors","page":"FDTDEngine.jl","title":"Monitors","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Monitor","category":"page"},{"location":"#Monitor","page":"FDTDEngine.jl","title":"Monitor","text":"function Monitor(span, normal=nothing)\n\nConstructs monitor which can span a point, line, surface, or volume\n\nArgs\n\nspan\nnormal: flux monitor direction\n\n\n\n\n\n","category":"type"},{"location":"#Physics","page":"FDTDEngine.jl","title":"Physics","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"step1\nstepTMz\nstepTEz\nstep3","category":"page"},{"location":"#step1","page":"FDTDEngine.jl","title":"step1","text":"function step1(u, p, t, configs)\n\nUpdates fields for 1D (Ez, Hy)\n\n\n\n\n\n","category":"function"},{"location":"#stepTMz","page":"FDTDEngine.jl","title":"stepTMz","text":"function stepTMz(u, p, t, configs)\n\nUpdates fields for 2d TMz\n\n\n\n\n\n","category":"function"},{"location":"#stepTEz","page":"FDTDEngine.jl","title":"stepTEz","text":"function stepTEz(u, p, t, configs)\n\nUpdates fields for 2d TEz (Hz, Ex, Ey)\n\n\n\n\n\n","category":"function"},{"location":"#step3","page":"FDTDEngine.jl","title":"step3","text":"function step3(u, p, t, configs)\n\nUpdates fields for 3d\n\n\n\n\n\n","category":"function"},{"location":"#Tutorials","page":"FDTDEngine.jl","title":"Tutorials","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"see examples/","category":"page"},{"location":"#Generative-inverse-design","page":"FDTDEngine.jl","title":"Generative inverse design","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Please contact us for latest scripts.","category":"page"},{"location":"#Community","page":"FDTDEngine.jl","title":"Community","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Discussion & updates at Julia Discourse","category":"page"},{"location":"#Contributors","page":"FDTDEngine.jl","title":"Contributors","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Paul Shen ","category":"page"}] +[{"location":"guide/#","page":"-","title":"","text":"","category":"section"},{"location":"guide/","page":"-","title":"-","text":"Engineers run simulations to improve designs. Each time the design changes, the simulation is re-run. This can be done systematically in \"parameter sweeps\" where different combinations of parameter values are simulated to determine the best design. However, this scales exponentially wrt the number of parameters or DOFs. ","category":"page"},{"location":"guide/#General-workflow","page":"-","title":"General workflow","text":"","category":"section"},{"location":"guide/","page":"-","title":"-","text":"We use gradient descent, the same as in machine learning. In lieu of optimizing neural network parameters, we're optimizing geometry (or source) parameters. In each training iteration, we generate geometry, run the simulation, calculate the objective metric, and do a backward pass to derive the gradient wrt the geometry parameters. We then do a gradient based parameter update in preparation for the next iteration.","category":"page"},{"location":"guide/","page":"-","title":"-","text":"The geometry is thus the first step. It typically has a static component which we can't change such as interfacing waveguides. Then there's a design component which we can change or optimize. The user is responsible for generating the design geometry wrt design parameters. If any pattern is allowed in the design region, our sister package Jello.jl can be used as a length scale controlled geometry generator. In any case, the result needs to be a 2d/3d array of each relevant materials property eg permitivity. ","category":"page"},{"location":"guide/","page":"-","title":"-","text":"With geometry ready, we can run the simulation. Duration is roughly the time it takes to reach steady state, such as how long it take for the signal to reach output port. The objective is usually a steady state metric which can be computed using values from the final period. We optimize geometry for some objective. ","category":"page"},{"location":"#FDTDEngine.jl","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Only 3d works in latest patch. 2d/1d will be fixed in future. Prerelease. Expect breaking changes","category":"page"},{"location":"#Overview","page":"FDTDEngine.jl","title":"Overview","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Differentiable FDTD package for inverse design & topology optimization in photonics, acoustics and RF. Uses automatic differentiation by Zygote.jl for adjoint optimization. Integrates with Jello.jl to generate length scale controlled paramaterized geometry . Staggered Yee grid update with fully featured boundary conditions & sources. Customizable physics to potentially incorporate dynamics like heat transfer, charge transport.","category":"page"},{"location":"#Gallery","page":"FDTDEngine.jl","title":"Gallery","text":"","category":"section"},{"location":"#Periodic-scattering","page":"FDTDEngine.jl","title":"Periodic scattering","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"(Image: )","category":"page"},{"location":"#Quarter-wavelength-antenna","page":"FDTDEngine.jl","title":"Quarter wavelength antenna","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"(Image: )","category":"page"},{"location":"#Inverse-design-of-compact-silicon-photonic-splitter-(coming-soon)","page":"FDTDEngine.jl","title":"Inverse design of compact silicon photonic splitter (coming soon)","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"In progress, split ratio isn't correct (Image: ) (Image: )","category":"page"},{"location":"#Quickstart","page":"FDTDEngine.jl","title":"Quickstart","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"We do a quick 3d simulation of plane wave scattering on periodic array of dielectric spheres (first gallery movie)","category":"page"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"using UnPack, LinearAlgebra, GLMakie\nusing FDTDEngine,FDTDToolkit\n\nF = Float32\nname = \"3d_scattering\"\nT = 8.0f0 # simulation duration in [periods]\nnres = 16\ndx = 1.0f0 / nres # pixel resolution in [wavelengths]\nCourant = 0.8 / √3 # Courant number\n\n\"geometry\"\nl = 2 # domain physical size length\nsz = nres .* (l, l, l) # domain voxel dimensions\nϵ1 = 1 #\nϵ2 = 2.25f0 # \nb = F.([norm(v .- sz ./ 2) < 0.5 / dx for v = Base.product(Base.oneto.(sz)...)]) # sphere\nϵ = ϵ2 * b + ϵ1 * (1 .- b)\n\n\"setup\"\nboundaries = [Periodic(2), Periodic(3)]# unspecified boundaries default to PML\n# n = [1, 0, 0]\nmonitors = []\nsources = [\n PlaneWave(t -> cos(F(2π) * t), -1; Jz=1) # Jz excited plane wave from -x plane (eg -1)\n # PlaneWave(t -> t < 1 ? cos(F(2π) * t) : 0.0f0, -1; Jz=1)\n]\nconfigs = setup(boundaries, sources, monitors, dx, sz; F, Courant, T)\n@unpack μ, σ, σm, dt, geometry_padding, geometry_splits, field_padding, source_effects, monitor_instances, fields, power = configs\n\nϵ, μ, σ, σm = apply(geometry_padding; ϵ, μ, σ, σm)\np = apply(geometry_splits; ϵ, μ, σ, σm)\nu0 = collect(values(fields))\n\n# run simulation\nt = 0:dt:T\nsol = similar([u0], length(t))\n@showtime sol = accumulate!((u, t) -> step!(u, p, t, configs), sol, t, init=u0)\n\n# make movie\nEz = map(sol) do u\n u[3]\nend\nϵz = p[1][3]\ndir = @__DIR__\n° = π / 180\nrecordsim(Ez, ϵz, configs, \"$dir/$(name)_nres_$nres.mp4\", title=\"$name\"; elevation=30°, playback=1, bipolar=true)\n","category":"page"},{"location":"#Installation","page":"FDTDEngine.jl","title":"Installation","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Install via ","category":"page"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Pkg.add(url=\"https://github.com/paulxshen/FDTDEngine.jl\")\nPkg.add(url=\"https://github.com/paulxshen/FDTDToolkit.jl\")","category":"page"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"FDTDToolkit.jl contains visualization utilities","category":"page"},{"location":"#Implementation","page":"FDTDEngine.jl","title":"Implementation","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Supports 1d (Ez, Hy), 2d TMz (Ez, Hx, Hy), 2d TEz (Hz, Ex, Ey) and 3d. Length and time are in units of wavelength and period. This normalization allows usage of relative permitivity and permeability in equations . Fields including electric, magnetic and current density are simply bundled as a vector of arrays . Boundary conditions pad the field arrays . PML paddings are multilayered, stateful and permanent, increasing size of field and geometry arrays. All other boundaries only add transient single layers which are subsequently consumed by finite differencing every update step. Paddings are coordinated to implictly implement staggered Yee's grid for finite differencing.","category":"page"},{"location":"#Sources","page":"FDTDEngine.jl","title":"Sources","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"If a source has fewer nonzero dimensions than the simulation domain, its signal will get normalized along its singleton dimensions. For example, all planar sources in 3d or line sources in 2d will get scaled up by a factor of 1/dx. This way, discretisation would not affect radiated power.","category":"page"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"PlaneWave\nSource","category":"page"},{"location":"#PlaneWave","page":"FDTDEngine.jl","title":"PlaneWave","text":"function PlaneWave(f, dims; fields...)\n\nConstructs plane wave source\n\nArgs\n\nf: time function\ndims: eg -1 for wave coming from -x edge\nfields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)\n\n\n\n\n\n","category":"type"},{"location":"#Source","page":"FDTDEngine.jl","title":"Source","text":"function Source(f, center, bounds; fields...)\nfunction Source(f, center, L::AbstractVector{<:Real}; fields...)\n\nConstructs custom source. Can be used to specify uniform or modal sources\n\nArgs\n\nf: time function\nL: source dimensions in [wavelengths]\nfields: which fields to excite & their scaling constants (typically a current source, eg Jz=1)\n\n\n\n\n\n","category":"type"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"","category":"page"},{"location":"#Boundaries","page":"FDTDEngine.jl","title":"Boundaries","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Unspecified boundaries default to PML ","category":"page"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Periodic\nPML\nPEC\nPMC","category":"page"},{"location":"#Periodic","page":"FDTDEngine.jl","title":"Periodic","text":"Periodic(dims)\n\nperiodic boundary\n\n\n\n\n\n","category":"type"},{"location":"#PML","page":"FDTDEngine.jl","title":"PML","text":"function PML(dims, d=0.25f0, σ=20.0f0)\n\nConstructs perfectly matched layers (PML aka ABC, RBC) boundary of depth d wavelengths Doesn't need to be explictly declared as all unspecified boundaries default to PML\n\n\n\n\n\n","category":"type"},{"location":"#PEC","page":"FDTDEngine.jl","title":"PEC","text":"PEC(dims)\n\nperfect electrical conductor dims: eg -1 for -x side\n\n\n\n\n\n","category":"type"},{"location":"#PMC","page":"FDTDEngine.jl","title":"PMC","text":"PMC(dims)\n\nperfect magnetic conductor\n\n\n\n\n\n","category":"type"},{"location":"#Monitors","page":"FDTDEngine.jl","title":"Monitors","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Monitor","category":"page"},{"location":"#Monitor","page":"FDTDEngine.jl","title":"Monitor","text":"function Monitor(span, normal=nothing)\n\nConstructs monitor which can span a point, line, surface, or volume\n\nArgs\n\nspan\nnormal: flux monitor direction\n\n\n\n\n\n","category":"type"},{"location":"#Physics","page":"FDTDEngine.jl","title":"Physics","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"step3!","category":"page"},{"location":"#step3!","page":"FDTDEngine.jl","title":"step3!","text":"function step3(u, p, t, configs)\n\nUpdates fields for 3d\n\n\n\n\n\n","category":"function"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":" ","category":"page"},{"location":"#Tutorials","page":"FDTDEngine.jl","title":"Tutorials","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"see examples/","category":"page"},{"location":"#Generative-inverse-design","page":"FDTDEngine.jl","title":"Generative inverse design","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Please contact us for latest scripts.","category":"page"},{"location":"#Community","page":"FDTDEngine.jl","title":"Community","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Discussion & updates at Julia Discourse","category":"page"},{"location":"#Contributors","page":"FDTDEngine.jl","title":"Contributors","text":"","category":"section"},{"location":"","page":"FDTDEngine.jl","title":"FDTDEngine.jl","text":"Paul Shen ","category":"page"}] } diff --git a/examples/3d_dipole_antenna/3d_quarter_wavelength_antenna_nres_16.mp4 b/examples/3d_dipole_antenna/3d_quarter_wavelength_antenna_nres_16.mp4 index 6fcf978..7bc3da9 100644 Binary files a/examples/3d_dipole_antenna/3d_quarter_wavelength_antenna_nres_16.mp4 and b/examples/3d_dipole_antenna/3d_quarter_wavelength_antenna_nres_16.mp4 differ diff --git a/examples/3d_periodic_scattering/3d_scattering_nres_16.mp4 b/examples/3d_periodic_scattering/3d_scattering_nres_16.mp4 index 0a71ef6..eeba144 100644 Binary files a/examples/3d_periodic_scattering/3d_scattering_nres_16.mp4 and b/examples/3d_periodic_scattering/3d_scattering_nres_16.mp4 differ diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/_nres_16.mp4 b/makedocs/inverse_design_3d_silicon_photonics_splitter/_nres_16.mp4 similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/_nres_16.mp4 rename to makedocs/inverse_design_3d_silicon_photonics_splitter/_nres_16.mp4 diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/inverse_design_3d_silicon_photonics_splitter.jl b/makedocs/inverse_design_3d_silicon_photonics_splitter/inverse_design_3d_silicon_photonics_splitter.jl similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/inverse_design_3d_silicon_photonics_splitter.jl rename to makedocs/inverse_design_3d_silicon_photonics_splitter/inverse_design_3d_silicon_photonics_splitter.jl diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/layout.jl b/makedocs/inverse_design_3d_silicon_photonics_splitter/layout.jl similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/layout.jl rename to makedocs/inverse_design_3d_silicon_photonics_splitter/layout.jl diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/mode.bson b/makedocs/inverse_design_3d_silicon_photonics_splitter/mode.bson similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/mode.bson rename to makedocs/inverse_design_3d_silicon_photonics_splitter/mode.bson diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/model b/makedocs/inverse_design_3d_silicon_photonics_splitter/model similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/model rename to makedocs/inverse_design_3d_silicon_photonics_splitter/model diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/post_training_nres_16.mp4 b/makedocs/inverse_design_3d_silicon_photonics_splitter/post_training_nres_16.mp4 similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/post_training_nres_16.mp4 rename to makedocs/inverse_design_3d_silicon_photonics_splitter/post_training_nres_16.mp4 diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/pre_training_nres_16.mp4 b/makedocs/inverse_design_3d_silicon_photonics_splitter/pre_training_nres_16.mp4 similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/pre_training_nres_16.mp4 rename to makedocs/inverse_design_3d_silicon_photonics_splitter/pre_training_nres_16.mp4 diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/silicon_photonics_splitter_nres_16.mp4 b/makedocs/inverse_design_3d_silicon_photonics_splitter/silicon_photonics_splitter_nres_16.mp4 similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/silicon_photonics_splitter_nres_16.mp4 rename to makedocs/inverse_design_3d_silicon_photonics_splitter/silicon_photonics_splitter_nres_16.mp4 diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/vis.jl b/makedocs/inverse_design_3d_silicon_photonics_splitter/vis.jl similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/vis.jl rename to makedocs/inverse_design_3d_silicon_photonics_splitter/vis.jl diff --git a/examples/inverse_design_3d_silicon_photonics_splitter/waveguide_bend_nres_16.mp4 b/makedocs/inverse_design_3d_silicon_photonics_splitter/waveguide_bend_nres_16.mp4 similarity index 100% rename from examples/inverse_design_3d_silicon_photonics_splitter/waveguide_bend_nres_16.mp4 rename to makedocs/inverse_design_3d_silicon_photonics_splitter/waveguide_bend_nres_16.mp4 diff --git a/makedocs/make.jl b/makedocs/make.jl index 6c2dbfa..a8a93ce 100644 --- a/makedocs/make.jl +++ b/makedocs/make.jl @@ -3,7 +3,7 @@ include("../src/FDTDEngine.jl") using .FDTDEngine makedocs( - sitename="FDTDEngine", + sitename="FDTDEngine.jl", format=Documenter.HTML(), # modules=[FDTDEngine], pages=[ diff --git a/makedocs/src/index.md b/makedocs/src/index.md index d7bfe8d..2bc4ee4 100644 --- a/makedocs/src/index.md +++ b/makedocs/src/index.md @@ -15,21 +15,15 @@ In progress, split ratio isn't correct ## Quickstart We do a quick 3d simulation of plane wave scattering on periodic array of dielectric spheres (first gallery movie) ```julia -""" -simulation of plane wave scattering on periodic array of dielectric spheres -""" - using UnPack, LinearAlgebra, GLMakie -using FDTDEngine -include("$(pwd())/scripts/plot_recipes.jl") - +using FDTDEngine,FDTDToolkit F = Float32 name = "3d_scattering" -T = 4.0f0 # simulation duration in [periods] +T = 8.0f0 # simulation duration in [periods] nres = 16 dx = 1.0f0 / nres # pixel resolution in [wavelengths] -Courant = 0.25f0 # Courant number +Courant = 0.8 / √3 # Courant number "geometry" l = 2 # domain physical size length @@ -48,14 +42,16 @@ sources = [ # PlaneWave(t -> t < 1 ? cos(F(2π) * t) : 0.0f0, -1; Jz=1) ] configs = setup(boundaries, sources, monitors, dx, sz; F, Courant, T) -@unpack μ, σ, σm, dt, geometry_padding, geometry_splits, field_padding, source_effects, monitor_instances, fields, step, power = configs +@unpack μ, σ, σm, dt, geometry_padding, geometry_splits, field_padding, source_effects, monitor_instances, fields, power = configs ϵ, μ, σ, σm = apply(geometry_padding; ϵ, μ, σ, σm) p = apply(geometry_splits; ϵ, μ, σ, σm) u0 = collect(values(fields)) # run simulation -@showtime sol = accumulate((u, t) -> step(u, p, t, configs), 0:dt:T, init=u0) +t = 0:dt:T +sol = similar([u0], length(t)) +@showtime sol = accumulate!((u, t) -> step!(u, p, t, configs), sol, t, init=u0) # make movie Ez = map(sol) do u @@ -63,11 +59,18 @@ Ez = map(sol) do u end ϵz = p[1][3] dir = @__DIR__ -recordsim(Ez, ϵz, configs, "$dir/$(name)_nres_$nres.mp4", title="$name"; playback=1, bipolar=true) +° = π / 180 +recordsim(Ez, ϵz, configs, "$dir/$(name)_nres_$nres.mp4", title="$name"; elevation=30°, playback=1, bipolar=true) ``` ## Installation -Install via `Pkg.add(url="https://github.com/paulxshen/FDTDEngine.jl")`. You can additionally access plotting and movie making scripts via `include("_your_path/scripts/plot_recipes.jl")` +Install via +``` +Pkg.add(url="https://github.com/paulxshen/FDTDEngine.jl") +Pkg.add(url="https://github.com/paulxshen/FDTDToolkit.jl") +``` +`FDTDToolkit.jl` contains visualization utilities + ## Implementation Supports 1d (Ez, Hy), 2d TMz (Ez, Hx, Hy), 2d TEz (Hz, Ex, Ey) and 3d. Length and time are in units of wavelength and period. This normalization allows usage of relative permitivity and permeability in equations . Fields including electric, magnetic and current density are simply bundled as a vector of arrays . Boundary conditions pad the field arrays . PML paddings are multilayered, stateful and permanent, increasing size of field and geometry arrays. All other boundaries only add transient single layers which are subsequently consumed by finite differencing every update step. Paddings are coordinated to implictly implement staggered Yee's grid for finite differencing. @@ -94,11 +97,11 @@ Monitor ## Physics ```@docs -step1 -stepTMz -stepTEz -step3 +step3! ``` + + + ## Tutorials see `examples/` diff --git a/src/FDTDEngine.jl b/src/FDTDEngine.jl index d7bad7f..bf201fb 100644 --- a/src/FDTDEngine.jl +++ b/src/FDTDEngine.jl @@ -1,7 +1,8 @@ module FDTDEngine include("main.jl") export reindex, sandwich -export Del, stepTMz, step1, step3, stepTEz +export Del, step3!, step! +# stepTMz, step1, , stepTEz export Periodic, PML, PEC, PMC, Padding export PlaneWave, GaussianBeam, UniformSource, Source, place export Monitor diff --git a/src/sources.jl b/src/sources.jl index c511c32..e910fa5 100644 --- a/src/sources.jl +++ b/src/sources.jl @@ -107,7 +107,7 @@ function SourceEffect(s::PlaneWave, dx, sizes, starts, sz0) starts[k] .+ (dims < 0 ? 0 : [i == abs(dims) ? sizes[k][i] - 1 : 0 for i = 1:d]) for k = keys(starts)]) _g = Dict([k => place(zeros(F, sizes[k]), g[k], starts[k]) for k = keys(fields)]) - center = NamedTuple([k => round.(Int, starts[k] .+ size(g[k]) ./ 2) for k = keys(starts)]) + center = NamedTuple([k => round.(Int, starts[k] .+ size(first(values(g))) ./ 2) for k = keys(starts)]) SourceEffect(f, g, _g, fields, starts, center, label) end @@ -122,7 +122,7 @@ function SourceEffect(s::GaussianBeam, dx, sizes, starts, stop) SourceEffect(f, g, _g, fields, start) end function SourceEffect(s::Source, dx, sizes, starts, stop) - @unpack f, fields, center, bounds = s + @unpack f, fields, center, bounds, label = s # R = round.(Int, L ./ 2 / dx) # I = range.(-R, R) # I = [round(Int, a / dx):round(Int, b / dx) for (a, b) = bounds] @@ -132,7 +132,8 @@ function SourceEffect(s::Source, dx, sizes, starts, stop) o = -1 .+ index(center, dx) .- round.(Int, (length.(I) .- 1) ./ 2) starts = NamedTuple([k => starts[k] .+ o for k = keys(starts)]) _g = Dict([k => place(zeros(F, sizes[k]), g[k], starts[k]) for k = keys(fields)]) - SourceEffect(f, g, _g, fields, starts) + center = NamedTuple([k => round.(Int, starts[k] .+ size(first(values(g))) ./ 2) for k = keys(starts)]) + SourceEffect(f, g, _g, fields, starts, center, label) # n = max.(1, round.(Int, L ./ dx)) # g = ones(n...) / dx^count(L .== 0) # start = start .+ round.(Int, center ./ dx .- (n .- 1) ./ 2)