diff --git a/CHANGELOG.md b/CHANGELOG.md index 199e83ab02..5888f904b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support for multiple frequencies in `output_monitors` in `adjoint` plugin. - GDSII export functions to `Simulation`, `Structure`, and `Geometry`. +- Added support for two-photon absorption via `TwoPhotonAbsorption` class. Added `KerrNonlinearity` that implements Kerr effect without third-harmonic generation. ### Changed +- API for specifying one or more nonlinear models via `NonlinearSpec.models`. ### Fixed diff --git a/tests/sims/simulation_2_5_0rc2.json b/tests/sims/simulation_2_5_0rc2.json new file mode 100644 index 0000000000..2b5698ee64 --- /dev/null +++ b/tests/sims/simulation_2_5_0rc2.json @@ -0,0 +1,2020 @@ +{ + "type": "Simulation", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 8.0, + 8.0, + 8.0 + ], + "run_time": 1e-12, + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium", + "permittivity": 1.0, + "conductivity": 0.0 + }, + "symmetry": [ + 0, + 0, + 0 + ], + "structures": [ + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": "dieletric_box", + "type": "Structure", + "medium": { + "name": "dieletric", + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium", + "permittivity": 2.0, + "conductivity": 0.0 + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + "Infinity", + 1.0 + ] + }, + "name": "lossy_box", + "type": "Structure", + "medium": { + "name": "lossy_dieletric", + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium", + "permittivity": 1.0, + "conductivity": 3.0 + } + }, + { + "geometry": { + "type": "Sphere", + "radius": 1.0, + "center": [ + 1.0, + 0.0, + 1.0 + ] + }, + "name": "sellmeier_sphere", + "type": "Structure", + "medium": { + "name": "sellmeier", + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Sellmeier", + "coeffs": [ + [ + 1.03961212, + 0.00600069867 + ], + [ + 0.231792344, + 0.0200179144 + ] + ] + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": "lorentz_box", + "type": "Structure", + "medium": { + "name": "lorentz", + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Lorentz", + "eps_inf": 2.0, + "coeffs": [ + [ + 1.0, + 2.0, + 3.0 + ] + ] + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Debye", + "eps_inf": 2.0, + "coeffs": [ + [ + 1.0, + 3.0 + ] + ] + } + }, + { + "geometry": { + "type": "TriangleMesh", + "mesh_dataset": { + "type": "TriangleMeshDataset", + "surface_mesh": "TriangleMeshDataArray" + } + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Debye", + "eps_inf": 2.0, + "coeffs": [ + [ + 1.0, + 3.0 + ] + ] + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": "drude_box", + "type": "Structure", + "medium": { + "name": "drude", + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Drude", + "eps_inf": 2.0, + "coeffs": [ + [ + 1.0, + 3.0 + ] + ] + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 0.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium2D", + "ss": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "PoleResidue", + "eps_inf": 1.0, + "poles": [ + [ + { + "real": 0.0, + "imag": 0.0 + }, + { + "real": 254117040158918.28, + "imag": 0.0 + } + ] + ] + }, + "tt": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "PoleResidue", + "eps_inf": 1.0, + "poles": [ + [ + { + "real": 0.0, + "imag": 0.0 + }, + { + "real": 254117040158918.28, + "imag": 0.0 + } + ] + ] + } + } + }, + { + "geometry": { + "type": "GeometryGroup", + "geometries": [ + { + "type": "Box", + "center": [ + -1.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + } + ] + }, + "name": "pec_group", + "type": "Structure", + "medium": { + "name": "PEC", + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "PECMedium" + } + }, + { + "geometry": { + "type": "Cylinder", + "axis": 1, + "sidewall_angle": 0.0, + "reference_plane": "middle", + "radius": 1.0, + "center": [ + 1.0, + 0.0, + -1.0 + ], + "length": 2.0 + }, + "name": "anisotopic_cylinder", + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": null, + "nonlinear_spec": null, + "heat_spec": null, + "type": "AnisotropicMedium", + "xx": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium", + "permittivity": 1.0, + "conductivity": 0.0 + }, + "yy": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium", + "permittivity": 2.0, + "conductivity": 0.0 + }, + "zz": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium", + "permittivity": 3.0, + "conductivity": 0.0 + } + } + }, + { + "geometry": { + "type": "PolySlab", + "axis": 2, + "sidewall_angle": 0.0, + "reference_plane": "middle", + "slab_bounds": [ + -1.0, + 1.0 + ], + "dilation": 0.0, + "vertices": [ + [ + -1.5, + -1.5 + ], + [ + -0.5, + -1.5 + ], + [ + -0.5, + -0.5 + ] + ] + }, + "name": "pole_slab", + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "PoleResidue", + "eps_inf": 1.0, + "poles": [ + [ + { + "real": 0.0, + "imag": 6206417594288582.0 + }, + { + "real": -0.0, + "imag": -3.311074436985222e+16 + } + ] + ] + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.5, + 0.5 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "CustomMedium", + "interp_method": "nearest", + "subpixel": false, + "permittivity": "SpatialDataArray", + "conductivity": null, + "eps_dataset": null + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.5, + 0.5 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "CustomDrude", + "eps_inf": "SpatialDataArray", + "coeffs": [ + [ + "SpatialDataArray", + "SpatialDataArray" + ] + ], + "interp_method": "nearest", + "subpixel": false + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.5, + 0.5 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "CustomLorentz", + "eps_inf": "SpatialDataArray", + "coeffs": [ + [ + "SpatialDataArray", + "SpatialDataArray", + "SpatialDataArray" + ] + ], + "interp_method": "nearest", + "subpixel": false + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.5, + 0.5 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "CustomDebye", + "eps_inf": "SpatialDataArray", + "coeffs": [ + [ + "SpatialDataArray", + "SpatialDataArray" + ] + ], + "interp_method": "nearest", + "subpixel": false + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.5, + 0.5 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "CustomPoleResidue", + "eps_inf": "SpatialDataArray", + "poles": [ + [ + "SpatialDataArray", + "SpatialDataArray" + ] + ], + "interp_method": "nearest", + "subpixel": false + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.5, + 0.5 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "CustomSellmeier", + "coeffs": [ + [ + "SpatialDataArray", + "SpatialDataArray" + ] + ], + "interp_method": "nearest", + "subpixel": false + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.5, + 0.5 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": { + "models": [ + { + "type": "NonlinearSusceptibility", + "chi3": 0.1, + "numiters": 20 + } + ], + "num_iters": 20, + "type": "NonlinearSpec" + }, + "heat_spec": null, + "type": "Medium", + "permittivity": 1.0, + "conductivity": 0.0 + } + }, + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.5, + 0.5 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": { + "models": [ + { + "type": "NonlinearSusceptibility", + "chi3": 0.1, + "numiters": null + }, + { + "type": "TwoPhotonAbsorption", + "beta": 1.0 + }, + { + "type": "KerrNonlinearity", + "n2": 1.0 + } + ], + "num_iters": 10, + "type": "NonlinearSpec" + }, + "heat_spec": null, + "type": "Medium", + "permittivity": 1.0, + "conductivity": 0.0 + } + }, + { + "geometry": { + "type": "PolySlab", + "axis": 2, + "sidewall_angle": 0.0, + "reference_plane": "middle", + "slab_bounds": [ + -1.0, + 1.0 + ], + "dilation": 0.0, + "vertices": [ + [ + -1.5, + -1.5 + ], + [ + -0.5, + -1.5 + ], + [ + -0.5, + -0.5 + ] + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "PoleResidue", + "eps_inf": 1.0, + "poles": [ + [ + { + "real": 0.0, + "imag": 6206417594288582.0 + }, + { + "real": -0.0, + "imag": -3.311074436985222e+16 + } + ] + ] + } + }, + { + "geometry": { + "type": "TriangleMesh", + "mesh_dataset": { + "type": "TriangleMeshDataset", + "surface_mesh": "TriangleMeshDataArray" + } + }, + "name": "dieletric_mesh", + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium", + "permittivity": 5.0, + "conductivity": 0.0 + } + }, + { + "geometry": { + "type": "GeometryGroup", + "geometries": [ + { + "type": "TriangleMesh", + "mesh_dataset": { + "type": "TriangleMeshDataset", + "surface_mesh": "TriangleMeshDataArray" + } + }, + { + "type": "TriangleMesh", + "mesh_dataset": { + "type": "TriangleMeshDataset", + "surface_mesh": "TriangleMeshDataArray" + } + } + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium", + "permittivity": 5.0, + "conductivity": 0.0 + } + } + ], + "sources": [ + { + "type": "UniformCurrentSource", + "center": [ + 0.0, + 0.5, + 0.0 + ], + "size": [ + 0.0, + 0.0, + 0.0 + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "GaussianPulse", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 5.0, + "remove_dc_component": true + }, + "name": null, + "interpolate": true, + "polarization": "Hx" + }, + { + "type": "PointDipole", + "center": [ + 0.0, + 0.5, + 0.0 + ], + "size": [ + 0, + 0, + 0 + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "GaussianPulse", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 5.0, + "remove_dc_component": true + }, + "name": null, + "interpolate": true, + "polarization": "Ex" + }, + { + "type": "ModeSource", + "center": [ + 0.0, + 0.5, + 0.0 + ], + "size": [ + 2.0, + 0.0, + 2.0 + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "GaussianPulse", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 5.0, + "remove_dc_component": true + }, + "name": null, + "num_freqs": 1, + "direction": "-", + "mode_spec": { + "num_modes": 1, + "target_neff": null, + "num_pml": [ + 0, + 0 + ], + "filter_pol": null, + "angle_theta": 0.0, + "angle_phi": 0.0, + "precision": "single", + "bend_radius": null, + "bend_axis": null, + "track_freq": "central", + "group_index_step": false, + "type": "ModeSpec" + }, + "mode_index": 0 + }, + { + "type": "PlaneWave", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + "Infinity", + "Infinity" + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "GaussianPulse", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 5.0, + "remove_dc_component": true + }, + "name": null, + "direction": "+", + "angle_theta": 0.0, + "angle_phi": 0.0, + "pol_angle": 0.1 + }, + { + "type": "GaussianBeam", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + 3.0, + 3.0 + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "GaussianPulse", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 5.0, + "remove_dc_component": true + }, + "name": null, + "num_freqs": 1, + "direction": "+", + "angle_theta": 0.0, + "angle_phi": 0.0, + "pol_angle": 1.5707963267948966, + "waist_radius": 1.0, + "waist_distance": 0.0 + }, + { + "type": "AstigmaticGaussianBeam", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + 3.0, + 3.0 + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "GaussianPulse", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 5.0, + "remove_dc_component": true + }, + "name": null, + "num_freqs": 1, + "direction": "+", + "angle_theta": 0.0, + "angle_phi": 0.0, + "pol_angle": 1.5707963267948966, + "waist_sizes": [ + 1.0, + 2.0 + ], + "waist_distances": [ + 3.0, + 4.0 + ] + }, + { + "type": "CustomFieldSource", + "center": [ + 0.0, + 1.0, + 2.0 + ], + "size": [ + 2.0, + 2.0, + 0.0 + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "GaussianPulse", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 5.0, + "remove_dc_component": true + }, + "name": null, + "field_dataset": { + "type": "FieldDataset", + "Ex": "ScalarFieldDataArray", + "Ey": null, + "Ez": null, + "Hx": null, + "Hy": null, + "Hz": null + } + }, + { + "type": "CustomCurrentSource", + "center": [ + 0.0, + 1.0, + 2.0 + ], + "size": [ + 2.0, + 2.0, + 0.0 + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "GaussianPulse", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 5.0, + "remove_dc_component": true + }, + "name": null, + "interpolate": true, + "current_dataset": { + "type": "FieldDataset", + "Ex": "ScalarFieldDataArray", + "Ey": null, + "Ez": null, + "Hx": null, + "Hy": null, + "Hz": null + } + }, + { + "type": "TFSF", + "center": [ + 1.0, + 2.0, + -3.0 + ], + "size": [ + 2.5, + 2.5, + 0.5 + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "GaussianPulse", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 5.0, + "remove_dc_component": true + }, + "name": null, + "direction": "+", + "angle_theta": 0.5235987755982988, + "angle_phi": 0.6283185307179586, + "pol_angle": 0.0, + "injection_axis": 2 + }, + { + "type": "UniformCurrentSource", + "center": [ + 0.0, + 0.5, + 0.0 + ], + "size": [ + 0.0, + 0.0, + 0.0 + ], + "source_time": { + "amplitude": 1.0, + "phase": 0.0, + "type": "CustomSourceTime", + "freq0": 200000000000000.0, + "fwidth": 40000000000000.0, + "offset": 0.0, + "source_time_dataset": { + "type": "TimeDataset", + "values": "TimeDataArray" + } + }, + "name": null, + "interpolate": true, + "polarization": "Hx" + } + ], + "boundary_spec": { + "x": { + "plus": { + "name": null, + "type": "PML", + "num_layers": 20, + "parameters": { + "sigma_order": 3, + "sigma_min": 0.0, + "sigma_max": 1.5, + "type": "PMLParams", + "kappa_order": 3, + "kappa_min": 1.0, + "kappa_max": 3.0, + "alpha_order": 1, + "alpha_min": 0.0, + "alpha_max": 0.0 + } + }, + "minus": { + "name": null, + "type": "Absorber", + "num_layers": 100, + "parameters": { + "sigma_order": 3, + "sigma_min": 0.0, + "sigma_max": 6.4, + "type": "AbsorberParams" + } + }, + "type": "Boundary" + }, + "y": { + "plus": { + "name": null, + "type": "BlochBoundary", + "bloch_vec": 1.0 + }, + "minus": { + "name": null, + "type": "BlochBoundary", + "bloch_vec": 1.0 + }, + "type": "Boundary" + }, + "z": { + "plus": { + "name": null, + "type": "Periodic" + }, + "minus": { + "name": null, + "type": "Periodic" + }, + "type": "Boundary" + }, + "type": "BoundarySpec" + }, + "monitors": [ + { + "type": "FieldMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + 0.0, + 0.0 + ], + "name": "field", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": true, + "freqs": [ + 150000000000000.0, + 200000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + }, + "fields": [ + "Ex" + ] + }, + { + "type": "FieldTimeMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + 0.0, + 0.0 + ], + "name": "field_time", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": true, + "start": 0.0, + "stop": null, + "interval": 100, + "fields": [ + "Ex", + "Ey", + "Ez", + "Hx", + "Hy", + "Hz" + ] + }, + { + "type": "FluxMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 0.0 + ], + "name": "flux", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": true, + "freqs": [ + 200000000000000.0, + 250000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + }, + "normal_dir": "+", + "exclude_surfaces": null + }, + { + "type": "FluxTimeMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 0.0 + ], + "name": "flux_time", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": true, + "start": 0.0, + "stop": null, + "interval": 1, + "normal_dir": "+", + "exclude_surfaces": null + }, + { + "type": "PermittivityMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 0.1 + ], + "name": "eps", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": false, + "freqs": [ + 100000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + } + }, + { + "type": "ModeMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 0.0 + ], + "name": "mode", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": false, + "freqs": [ + 200000000000000.0, + 250000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + }, + "mode_spec": { + "num_modes": 1, + "target_neff": null, + "num_pml": [ + 0, + 0 + ], + "filter_pol": null, + "angle_theta": 0.0, + "angle_phi": 0.0, + "precision": "single", + "bend_radius": null, + "bend_axis": null, + "track_freq": "central", + "group_index_step": false, + "type": "ModeSpec" + } + }, + { + "type": "ModeSolverMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 0.0 + ], + "name": "mode_solver", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": true, + "freqs": [ + 200000000000000.0, + 250000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + }, + "mode_spec": { + "num_modes": 1, + "target_neff": null, + "num_pml": [ + 0, + 0 + ], + "filter_pol": null, + "angle_theta": 0.0, + "angle_phi": 0.0, + "precision": "single", + "bend_radius": null, + "bend_axis": null, + "track_freq": "central", + "group_index_step": false, + "type": "ModeSpec" + }, + "direction": "+" + }, + { + "type": "FieldProjectionAngleMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + 2.0, + 2.0 + ], + "name": "proj_angle", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": true, + "freqs": [ + 250000000000000.0, + 300000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + }, + "normal_dir": "+", + "exclude_surfaces": null, + "custom_origin": [ + 1.0, + 2.0, + 3.0 + ], + "far_field_approx": true, + "proj_distance": 1000000.0, + "theta": [ + -1.5707963267948966, + -1.5390630676677268, + -1.5073298085405573, + -1.4755965494133876, + -1.443863290286218, + -1.4121300311590483, + -1.3803967720318788, + -1.348663512904709, + -1.3169302537775396, + -1.2851969946503699, + -1.2534637355232003, + -1.2217304763960306, + -1.189997217268861, + -1.1582639581416914, + -1.1265306990145216, + -1.0947974398873521, + -1.0630641807601826, + -1.0313309216330129, + -0.9995976625058433, + -0.9678644033786736, + -0.936131144251504, + -0.9043978851243344, + -0.8726646259971648, + -0.8409313668699951, + -0.8091981077428254, + -0.7774648486156558, + -0.7457315894884862, + -0.7139983303613165, + -0.6822650712341469, + -0.6505318121069773, + -0.6187985529798077, + -0.5870652938526381, + -0.5553320347254684, + -0.5235987755982987, + -0.4918655164711292, + -0.46013225734395946, + -0.42839899821678995, + -0.3966657390896202, + -0.3649324799624507, + -0.333199220835281, + -0.30146596170811146, + -0.26973270258094173, + -0.23799944345377222, + -0.2062661843266025, + -0.17453292519943298, + -0.14279966607226324, + -0.11106640694509373, + -0.079333147817924, + -0.047599888690754266, + -0.015866629563584755, + 0.015866629563584977, + 0.04759988869075449, + 0.07933314781792422, + 0.11106640694509373, + 0.14279966607226346, + 0.17453292519943298, + 0.2062661843266027, + 0.23799944345377222, + 0.26973270258094195, + 0.30146596170811146, + 0.3331992208352812, + 0.3649324799624507, + 0.39666573908962044, + 0.42839899821678995, + 0.4601322573439597, + 0.4918655164711292, + 0.5235987755982991, + 0.5553320347254687, + 0.5870652938526382, + 0.6187985529798077, + 0.6505318121069776, + 0.6822650712341471, + 0.7139983303613167, + 0.7457315894884862, + 0.7774648486156561, + 0.8091981077428256, + 0.8409313668699951, + 0.8726646259971647, + 0.9043978851243346, + 0.9361311442515041, + 0.9678644033786736, + 0.9995976625058436, + 1.031330921633013, + 1.0630641807601826, + 1.0947974398873521, + 1.126530699014522, + 1.1582639581416916, + 1.189997217268861, + 1.2217304763960306, + 1.2534637355232006, + 1.28519699465037, + 1.3169302537775396, + 1.348663512904709, + 1.380396772031879, + 1.4121300311590486, + 1.443863290286218, + 1.475596549413388, + 1.5073298085405575, + 1.539063067667727, + 1.5707963267948966 + ], + "phi": [ + 0.0, + 1.5707963267948966 + ] + }, + { + "type": "FieldProjectionCartesianMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + 2.0, + 2.0 + ], + "name": "proj_cartesian", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": true, + "freqs": [ + 250000000000000.0, + 300000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + }, + "normal_dir": "+", + "exclude_surfaces": null, + "custom_origin": [ + 1.0, + 2.0, + 3.0 + ], + "far_field_approx": true, + "proj_axis": 2, + "proj_distance": 5.0, + "x": [ + -1.0, + 0.0, + 1.0 + ], + "y": [ + -2.0, + -1.0, + 0.0, + 1.0, + 2.0 + ] + }, + { + "type": "FieldProjectionKSpaceMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + 2.0, + 2.0 + ], + "name": "proj_kspace", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": true, + "freqs": [ + 250000000000000.0, + 300000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + }, + "normal_dir": "+", + "exclude_surfaces": null, + "custom_origin": [ + 1.0, + 2.0, + 3.0 + ], + "far_field_approx": true, + "proj_axis": 2, + "proj_distance": 1000000.0, + "ux": [ + 0.1, + 0.2 + ], + "uy": [ + 0.3, + 0.4, + 0.5 + ] + }, + { + "type": "FieldProjectionAngleMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + 2.0, + 2.0 + ], + "name": "proj_angle_exact", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": true, + "freqs": [ + 250000000000000.0, + 300000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + }, + "normal_dir": "+", + "exclude_surfaces": null, + "custom_origin": [ + 1.0, + 2.0, + 3.0 + ], + "far_field_approx": true, + "proj_distance": 1000000.0, + "theta": [ + -1.5707963267948966, + -1.5390630676677268, + -1.5073298085405573, + -1.4755965494133876, + -1.443863290286218, + -1.4121300311590483, + -1.3803967720318788, + -1.348663512904709, + -1.3169302537775396, + -1.2851969946503699, + -1.2534637355232003, + -1.2217304763960306, + -1.189997217268861, + -1.1582639581416914, + -1.1265306990145216, + -1.0947974398873521, + -1.0630641807601826, + -1.0313309216330129, + -0.9995976625058433, + -0.9678644033786736, + -0.936131144251504, + -0.9043978851243344, + -0.8726646259971648, + -0.8409313668699951, + -0.8091981077428254, + -0.7774648486156558, + -0.7457315894884862, + -0.7139983303613165, + -0.6822650712341469, + -0.6505318121069773, + -0.6187985529798077, + -0.5870652938526381, + -0.5553320347254684, + -0.5235987755982987, + -0.4918655164711292, + -0.46013225734395946, + -0.42839899821678995, + -0.3966657390896202, + -0.3649324799624507, + -0.333199220835281, + -0.30146596170811146, + -0.26973270258094173, + -0.23799944345377222, + -0.2062661843266025, + -0.17453292519943298, + -0.14279966607226324, + -0.11106640694509373, + -0.079333147817924, + -0.047599888690754266, + -0.015866629563584755, + 0.015866629563584977, + 0.04759988869075449, + 0.07933314781792422, + 0.11106640694509373, + 0.14279966607226346, + 0.17453292519943298, + 0.2062661843266027, + 0.23799944345377222, + 0.26973270258094195, + 0.30146596170811146, + 0.3331992208352812, + 0.3649324799624507, + 0.39666573908962044, + 0.42839899821678995, + 0.4601322573439597, + 0.4918655164711292, + 0.5235987755982991, + 0.5553320347254687, + 0.5870652938526382, + 0.6187985529798077, + 0.6505318121069776, + 0.6822650712341471, + 0.7139983303613167, + 0.7457315894884862, + 0.7774648486156561, + 0.8091981077428256, + 0.8409313668699951, + 0.8726646259971647, + 0.9043978851243346, + 0.9361311442515041, + 0.9678644033786736, + 0.9995976625058436, + 1.031330921633013, + 1.0630641807601826, + 1.0947974398873521, + 1.126530699014522, + 1.1582639581416916, + 1.189997217268861, + 1.2217304763960306, + 1.2534637355232006, + 1.28519699465037, + 1.3169302537775396, + 1.348663512904709, + 1.380396772031879, + 1.4121300311590486, + 1.443863290286218, + 1.475596549413388, + 1.5073298085405575, + 1.539063067667727, + 1.5707963267948966 + ], + "phi": [ + 0.0, + 1.5707963267948966 + ] + }, + { + "type": "DiffractionMonitor", + "center": [ + 0.0, + 0.0, + 0.0 + ], + "size": [ + 0.0, + "Infinity", + "Infinity" + ], + "name": "diffraction", + "interval_space": [ + 1, + 1, + 1 + ], + "colocate": false, + "freqs": [ + 100000000000000.0, + 200000000000000.0 + ], + "apodization": { + "start": null, + "end": null, + "width": null, + "type": "ApodizationSpec" + }, + "normal_dir": "+" + } + ], + "grid_spec": { + "grid_x": { + "type": "AutoGrid", + "min_steps_per_wvl": 10.0, + "max_scale": 1.4, + "dl_min": 0.0, + "mesher": { + "type": "GradedMesher" + } + }, + "grid_y": { + "type": "CustomGrid", + "dl": [ + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04 + ], + "custom_offset": null + }, + "grid_z": { + "type": "UniformGrid", + "dl": 0.05 + }, + "wavelength": null, + "override_structures": [ + { + "geometry": { + "type": "Box", + "center": [ + -1.0, + 0.0, + 0.0 + ], + "size": [ + 1.0, + 1.0, + 1.0 + ] + }, + "name": null, + "type": "Structure", + "medium": { + "name": null, + "frequency_range": null, + "allow_gain": false, + "nonlinear_spec": null, + "heat_spec": null, + "type": "Medium", + "permittivity": 2.0, + "conductivity": 0.0 + } + } + ], + "type": "GridSpec" + }, + "shutoff": 0.0001, + "subpixel": false, + "normalize_index": 0, + "courant": 0.8, + "version": "2.5.0rc2" +} \ No newline at end of file diff --git a/tests/test_components/test_medium.py b/tests/test_components/test_medium.py index 69cf7053cc..b602b4d060 100644 --- a/tests/test_components/test_medium.py +++ b/tests/test_components/test_medium.py @@ -543,13 +543,60 @@ def test_perturbation_medium(): ) -def test_nonlinear_medium(): - med = td.Medium(nonlinear_spec=td.NonlinearSusceptibility(chi3=1.5, numiters=20)) +def test_nonlinear_medium(log_capture): + med = td.Medium( + nonlinear_spec=td.NonlinearSpec( + models=[ + td.NonlinearSusceptibility(chi3=1.5), + td.TwoPhotonAbsorption(beta=1), + ], + num_iters=20, + ) + ) + + assert_log_level(log_capture, None) + + # warn about deprecated api + med = td.Medium(nonlinear_spec=td.NonlinearSusceptibility(chi3=1.5)) + assert_log_level(log_capture, "WARNING") + # warn about deprecated numiters + log_capture.clear() + med = td.Medium( + nonlinear_spec=td.NonlinearSpec(models=[td.NonlinearSusceptibility(chi3=1, numiters=2)]) + ) + assert_log_level(log_capture, "WARNING") + assert med.nonlinear_spec.num_iters == 2 + + # unsupported material types with pytest.raises(pydantic.ValidationError): - med = td.PoleResidue( - poles=[(-1, 1)], nonlinear_spec=td.NonlinearSusceptibility(chi3=1.5, numiters=20) - ) + med = td.PoleResidue(poles=[(-1, 1)], nonlinear_spec=td.NonlinearSusceptibility(chi3=1.5)) + # numiters too large with pytest.raises(pydantic.ValidationError): med = td.Medium(nonlinear_spec=td.NonlinearSusceptibility(chi3=1.5, numiters=200)) + with pytest.raises(pydantic.ValidationError): + med = td.Medium( + nonlinear_spec=td.NonlinearSpec( + num_iters=200, models=[td.NonlinearSusceptibility(chi3=1.5)] + ) + ) + + # duplicate models + with pytest.raises(pydantic.ValidationError): + med = td.Medium( + nonlinear_spec=td.NonlinearSpec( + models=[ + td.NonlinearSusceptibility(chi3=1.5), + td.NonlinearSusceptibility(chi3=1), + ] + ) + ) + + # active materials + with pytest.raises(pydantic.ValidationError): + med = td.Medium(nonlinear_spec=td.NonlinearSpec(models=[td.TwoPhotonAbsorption(beta=-1)])) + + med = td.Medium( + nonlinear_spec=td.NonlinearSpec(models=[td.TwoPhotonAbsorption(beta=-1)]), allow_gain=True + ) diff --git a/tests/utils.py b/tests/utils.py index 496aba5e57..aec34c487a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -213,6 +213,22 @@ nonlinear_spec=td.NonlinearSusceptibility(chi3=0.1, numiters=20), ), ), + td.Structure( + geometry=td.Box( + size=(1, 1, 1), + center=(-1.0, 0.5, 0.5), + ), + medium=td.Medium( + nonlinear_spec=td.NonlinearSpec( + num_iters=10, + models=[ + td.NonlinearSusceptibility(chi3=0.1), + td.TwoPhotonAbsorption(beta=1), + td.KerrNonlinearity(n2=1), + ], + ) + ), + ), td.Structure( geometry=td.PolySlab( vertices=[(-1.5, -1.5), (-0.5, -1.5), (-0.5, -0.5)], slab_bounds=[-1, 1] diff --git a/tidy3d/__init__.py b/tidy3d/__init__.py index 974a357fec..feb58cf474 100644 --- a/tidy3d/__init__.py +++ b/tidy3d/__init__.py @@ -16,7 +16,7 @@ from .components.medium import CustomMedium, CustomPoleResidue from .components.medium import CustomSellmeier, FullyAnisotropicMedium from .components.medium import CustomLorentz, CustomDrude, CustomDebye, CustomAnisotropicMedium -from .components.medium import NonlinearSusceptibility +from .components.medium import NonlinearSusceptibility, TwoPhotonAbsorption, KerrNonlinearity from .components.transformation import RotationAroundAxis from .components.medium import PerturbationMedium, PerturbationPoleResidue from .components.parameter_perturbation import ParameterPerturbation @@ -90,7 +90,7 @@ from .material_library.parametric_materials import Graphene # for docs -from .components.medium import AbstractMedium, NonlinearSpec +from .components.medium import AbstractMedium, NonlinearSpec, NonlinearModel from .components.geometry.base import Geometry from .components.source import Source, SourceTime from .components.monitor import Monitor @@ -180,7 +180,10 @@ def set_logging_level(level: str) -> None: "LinearChargePerturbation", "CustomChargePerturbation", "NonlinearSpec", + "NonlinearModel", "NonlinearSusceptibility", + "TwoPhotonAbsorption", + "KerrNonlinearity", "Structure", "MeshOverrideStructure", "ModeSpec", diff --git a/tidy3d/components/medium.py b/tidy3d/components/medium.py index ac0c8c4da1..dcb5676dc7 100644 --- a/tidy3d/components/medium.py +++ b/tidy3d/components/medium.py @@ -82,24 +82,68 @@ def _eps_model(self, frequency: float) -> complex: """ Medium Definitions """ -class NonlinearSpec(ABC, Tidy3dBaseModel): - """Abstract specification for adding a nonlinearity to a medium. +class NonlinearModel(ABC, Tidy3dBaseModel): + """Abstract model for a nonlinear material response, used as part of a :class:`.NonlinearSpec`.""" + + def _validate_medium(self, medium_type: type): + """Check that the model is compatible with the medium.""" + if medium_type.__name__ != "Medium": + raise ValidationError( + f"'NonlinearModel' of class {type(self)} is not currently supported " + f"for medium class {medium_type}." + ) + + def _validate_passive(self): + """Check that the model is passive""" + pass + + +class NonlinearSusceptibility(NonlinearModel): + """Model for an instantaneous nonlinear chi3 susceptibility. + The expression for the instantaneous nonlinear polarization is given below. - Note + Note ---- - The nonlinear constitutive relation is solved iteratively; it may not converge - for strong nonlinearities. Increasing `numiters` can help with convergence. + .. math:: + + P_{NL}(t) = \\epsilon_0 \\chi_3 |E(t)|^2 E(t) + + Note + ---- + For complex fields (e.g. when using Bloch boundary conditions), the nonlinearity + is applied separately to the real and imaginary parts, so that the above equation + holds when both E and :math:`P_{NL}` are replaced by their real or imaginary parts. + The nonlinearity is applied to the real and imaginary components separately since + each of those represents a physical field. + + Note + ---- + Different field components do not interact nonlinearly. For example, + when calculating :math:`P_{NL, x}`, we approximate :math:`|E|^2 \\approx |E_x|^2`. + This approximation is valid when the E field is predominantly polarized along one + of the x, y, or z axes. + + Example + ------- + >>> nonlinear_susceptibility = NonlinearSusceptibility(chi3=1) """ + chi3: float = pd.Field( + 0, title="Chi3", description="Chi3 nonlinear susceptibility.", units="um^2 / V^2" + ) + numiters: pd.PositiveInt = pd.Field( - 1, + None, title="Number of iterations", - description="Number of iterations for solving nonlinear constitutive relation.", + description="Number of iterations for solving nonlinear constitutive relation. " + "Deprecated; use 'NonlinearSpec.num_iters' instead.", ) @pd.validator("numiters", always=True) def _validate_numiters(cls, val): """Check that numiters is not too large.""" + if val is None: + return val if val > NONLINEAR_MAX_NUMITERS: raise ValidationError( "'NonlinearSpec.numiters' must be less than " @@ -108,28 +152,72 @@ def _validate_numiters(cls, val): return val -class NonlinearSusceptibility(NonlinearSpec): - """Specification adding an instantaneous nonlinear susceptibility to a medium. - The expression for the instantaneous nonlinear polarization is given below. +class TwoPhotonAbsorption(NonlinearModel): + """Model for two-photon absorption (TPA) nonlinearity. + The expression for the frequency-domain nonlinear polarization is given below. Note ---- .. math:: - P_{NL} = \\epsilon_0 \\chi_3 |E|^2 E + P_{NL}(\\omega) = -\\frac{c_0^2 \\epsilon_0^2 n_0^2 \\beta}{2 i \\omega} + |E(\\omega)|^2 E(\\omega) Note ---- - The nonlinear constitutive relation is solved iteratively; it may not converge - for strong nonlinearities. Increasing `numiters` can help with convergence. + The frequency-domain nonlinearity is implemented in the time domain using + complex-valued fields. Note ---- - For complex fields (e.g. when using Bloch boundary conditions), the nonlinearity - is applied separately to the real and imaginary parts, so that the above equation - holds when both E and :math:`P_{NL}` are replaced by their real or imaginary parts. - The nonlinearity is only applied to the real-valued fields since they are the - physical fields. + Different field components do not interact nonlinearly. For example, + when calculating :math:`P_{NL, x}`, we approximate :math:`|E|^2 \\approx |E_x|^2`. + This approximation is valid when the E field is predominantly polarized along one + of the x, y, or z axes. + + Example + ------- + >>> tpa_model = TwoPhotonAbsorption(beta=1) + """ + + beta: float = pd.Field( + 0, + title="TPA coefficient", + description="Coefficient for two-photon absorption (TPA).", + units="um / W", + ) + + def _validate_passive(self): + """Whether the model is passive""" + if self.beta < 0: + raise ValidationError( + "For passive medium, 'beta' in 'TwoPhotonAbsorption' " + "must be non-negative. To simulate gain medium, please set " + "'allow_gain=True' in the medium class. Caution: simulations containing " + "gain medium are unstable, and are likely to diverge." + ) + + +class KerrNonlinearity(NonlinearModel): + """Model for Kerr nonlinearity. + The expression for the frequency-domain nonlinear polarization is given below. + + Note + ---- + .. math:: + + P_{NL}(\\omega) = 2 \\epsilon_0 n_0 n_2 |E(\\omega)|^2 E(\\omega) + + Note + ---- + Using :class:`.NonlinearSusceptibility` with chi3 will result in Kerr nonlinearity + and third-harmonic generation. This class only implements the Kerr nonlinearity. + The relationship between the parameters is :math:`n_2 = \\frac{3}{8 n_0} \\chi_3`. + + Note + ---- + The frequency-domain nonlinearity is implemented in the time domain using + complex-valued fields. Note ---- @@ -140,15 +228,84 @@ class NonlinearSusceptibility(NonlinearSpec): Example ------- - >>> medium = Medium(permittivity=2, nonlinear_spec=NonlinearSusceptibility(chi3=1)) + >>> kerr_model = KerrNonlinearity(n2=1) """ - chi3: float = pd.Field( - ..., title="Chi3", description="Chi3 nonlinear susceptibility.", units="um^2 / V^2" + n2: float = pd.Field( + 0, + title="Kerr coefficient", + description="Coefficient for Kerr nonlinearity.", + units="um^2 / V^2", + ) + + +NonlinearModelType = Union[NonlinearSusceptibility, TwoPhotonAbsorption, KerrNonlinearity] + + +class NonlinearSpec(ABC, Tidy3dBaseModel): + """Abstract specification for adding nonlinearities to a medium. + + Note + ---- + The nonlinear constitutive relation is solved iteratively; it may not converge + for strong nonlinearities. Increasing `num_iters` can help with convergence. + + Example + ------- + >>> nonlinear_susceptibility = NonlinearSusceptibility(chi3=1) + >>> nonlinear_spec = NonlinearSpec(models=[nonlinear_susceptibility]) + >>> medium = Medium(permittivity=2, nonlinear_spec=nonlinear_spec) + """ + + models: Tuple[NonlinearModelType, ...] = pd.Field( + (), + title="Nonlinear models", + description="The nonlinear models present in this nonlinear spec. " + "Nonlinear models of different types are additive. " + "Multiple nonlinear models of the same type are not allowed.", + ) + + num_iters: pd.PositiveInt = pd.Field( + 1, + title="Number of iterations", + description="Number of iterations for solving nonlinear constitutive relation.", ) + @pd.validator("models", always=True) + def _no_duplicate_models(cls, val): + """Ensure each type of model appears at most once.""" + if val is None: + return val + models = [model.__class__ for model in val] + models_unique = set(models) + if len(models) != len(models_unique): + raise ValidationError( + "Multiple 'NonlinearModels' of the same type " + "were found in a single 'NonlinearSpec'. Please ensure that " + "each type of 'NonlinearModel' appears at most once in a single 'NonlinearSpec'." + ) + return val -NonlinearSpecType = Union[NonlinearSusceptibility] + @pd.validator("num_iters", always=True) + def _validate_num_iters(cls, val, values): + """Check that num_iters is not too large.""" + if val > NONLINEAR_MAX_NUMITERS: + raise ValidationError( + "'NonlinearSpec.numiters' must be less than " + f"{NONLINEAR_MAX_NUMITERS}, currently {val}." + ) + # also check consistency with old numiters, for backwards compatibility + if values.get("models") is not None: + for model in values["models"]: + if isinstance(model, NonlinearSusceptibility) and model.numiters is not None: + log.warning( + "The API for the number of nonlinear iterations has changed. " + "The old usage 'NonlinearSusceptibility.numiters' is deprecated " + "and will be removed in a future release. The new usage is " + "'NonlinearSpec.num_iters'." + ) + return model.numiters + return val class AbstractMedium(ABC, Tidy3dBaseModel): @@ -173,21 +330,31 @@ class AbstractMedium(ABC, Tidy3dBaseModel): "useful in some cases.", ) - nonlinear_spec: NonlinearSpecType = pd.Field( + nonlinear_spec: Union[NonlinearSpec, NonlinearSusceptibility] = pd.Field( None, title="Nonlinear Spec", description="Nonlinear spec applied on top of the base medium properties.", ) @pd.validator("nonlinear_spec", always=True) - def _validate_nonlinear_spec(cls, val): + def _validate_nonlinear_spec(cls, val, values): """Check compatibility with nonlinear_spec.""" if val is None: return val - raise ValidationError( - f"A 'nonlinear_spec' of class {type(val)} is not " - f"currently supported for medium class {cls}." - ) + if isinstance(val, NonlinearModel): + log.warning( + "The API for 'nonlinear_spec' has changed. " + "The old usage 'nonlinear_spec=model' is deprecated and will be removed " + "in a future release. The new usage is " + "'nonlinear_spec=NonlinearSpec(models=[model])'." + ) + val = NonlinearSpec(models=[val]) + if val.models is not None: + for model in val.models: + model._validate_medium(cls) + if not values["allow_gain"]: + model._validate_passive() + return val heat_spec: Optional[HeatSpecType] = pd.Field( None, @@ -647,16 +814,6 @@ class Medium(AbstractMedium): units=CONDUCTIVITY, ) - @pd.validator("nonlinear_spec", always=True) - def _validate_nonlinear_spec(cls, val): - """Check compatibility with nonlinear_spec.""" - if val is None or isinstance(val, NonlinearSusceptibility): - return val - raise ValidationError( - f"A 'nonlinear_spec' of class {type(val)} is not " - f"currently supported for medium class {cls}." - ) - @pd.validator("conductivity", always=True) def _passivity_validation(cls, val, values): """Assert passive medium if `allow_gain` is False.""" @@ -740,16 +897,6 @@ class CustomIsotropicMedium(AbstractCustomMedium, Medium): units=CONDUCTIVITY, ) - @pd.validator("nonlinear_spec", always=True) - def _validate_nonlinear_spec(cls, val): - """Check compatibility with nonlinear_spec.""" - if val is None: - return val - raise ValidationError( - f"A 'nonlinear_spec' of class {type(val)} is not " - f"currently supported for medium class {cls}." - ) - @pd.validator("permittivity", always=True) def _eps_inf_greater_no_less_than_one(cls, val): """Assert any eps_inf must be >=1"""