Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WI add tests #54

Merged
merged 8 commits into from
Mar 3, 2022
9 changes: 4 additions & 5 deletions nidn/fdtd_integration/compute_spectrum_fdtd.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ def compute_spectrum_fdtd(permittivity, cfg: DotMap):
transmission_spectrum = []
reflection_spectrum = []
physical_wavelengths, norm_freq = get_frequency_points(cfg)
logger.debug("Wavelenghts in spectrum")
logger.debug("Wavelenghts in spectrum : ")
logger.debug(physical_wavelengths)

logger.debug("Number of layers: ")
logger.debug(len(permittivity[0,0,:,0]))
# For each wavelength, calculate transmission and reflection coefficents
disable_progress_bar = logger._core.min_level >= 20
for i in tqdm(range(len(physical_wavelengths)), disable=disable_progress_bar):
Expand Down Expand Up @@ -59,8 +60,6 @@ def compute_spectrum_fdtd(permittivity, cfg: DotMap):
)
transmission_signal.append(transmission_material)
reflection_signal.append(reflection_material)
time = [i for i in range(len(transmission_signal[0]))]

# Calculate transmission and reflection coefficients,
# by using the signals from the free space simulation and the material simulation
(
Expand Down Expand Up @@ -120,7 +119,7 @@ def _get_abs_value_from_3D_signal(signal):
abs_value = []
for i in range(len(signal)):
abs_value.append(
sqrt(tensor(signal[i][0] ** 2 + signal[i][1] ** 2 + signal[i][2] ** 2))
sqrt(signal[i][0] ** 2 + signal[i][1] ** 2 + signal[i][2] ** 2)
)
return abs_value

Expand Down
9 changes: 5 additions & 4 deletions nidn/fdtd_integration/init_fdtd.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ def init_fdtd(cfg: DotMap, include_object, wavelength, permittivity):
cfg.FDTD_reflection_detector_x
+ cfg.N_layers * cfg.PER_LAYER_THICKNESS[0]
)
+ 2
torbjornstoro marked this conversation as resolved.
Show resolved Hide resolved
),
int(cfg.FDTD_reflection_detector_x * scaling),
)
grid = _add_source(
grid,
int(cfg.FDTD_source[0] * scaling),
int(cfg.FDTD_source[1] * scaling),
int(cfg.FDTD_source_position[0] * scaling),
int(cfg.FDTD_source_position[1] * scaling),
wavelength / SPEED_OF_LIGHT,
cfg.FDTD_use_pulsesource,
cfg.FDTD_use_pointsource,
Expand Down Expand Up @@ -116,7 +117,7 @@ def _add_source(grid, source_x, source_y, period, use_pulse_source, use_point_so
if use_point_source:
grid[source_x, source_y, 0] = fdtd.PointSource(
period=period,
name="linesource",
name="pointsource",
pulse=use_pulse_source,
cycle=1,
hanning_dt=1e-15,
Expand Down Expand Up @@ -158,7 +159,7 @@ def _add_object(grid, object_start_x, object_end_x, permittivity, frequency):
Returns:
fdtd.Grid: The grid with the added object
"""
# Not sure whether the conductivity should be relative or absolute, i.e. if it should be multiplied with EPS_0.
# Not sure whether the conductivity should be relative or absolute, i.e. if it should be multiplied with EPS_0. Multiplied with 2pi to get w(angular frequency)?
# Since the permittivity is set to 1 for the free space grid, I'll leave it at an relative value for now. Also, permittivity for object is relative.
grid[object_start_x:object_end_x, :, :] = fdtd.AbsorbingObject(
permittivity=permittivity.real,
Expand Down
2 changes: 1 addition & 1 deletion nidn/plots/plot_spectra.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from matplotlib import pyplot as plt
from matplotlib.ticker import FormatStrFormatter
from ..utils.convert_units import freq_to_wl, wl_to_phys_wl
from ..trcwa.compute_spectrum import compute_spectrum
from ..utils.compute_spectrum import compute_spectrum
from ..training.model.model_to_eps_grid import model_to_eps_grid


Expand Down
25 changes: 25 additions & 0 deletions nidn/tests/calculate_coefficient_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from numpy import sin, pi
from ..fdtd_integration.calculate_transmission_reflection_coefficients import (
calculate_transmission_reflection_coefficients,
)
from ..utils.load_default_cfg import load_default_cfg


def test_calculate_coefficient():
cfg = load_default_cfg()
time_points = [0.002 * pi * i for i in range(1000)]
signal_a = 2 * sin(time_points)
signal_b = sin(time_points)
signal_array = [signal_a, signal_b]
(
transmission_coefficient_ms,
reflection_coefficient_ms,
) = calculate_transmission_reflection_coefficients(
signal_array, signal_array, "mean square", cfg
)
# TODO: Add test for fdtd, when the method is complete
torbjornstoro marked this conversation as resolved.
Show resolved Hide resolved
assert transmission_coefficient_ms - 0.25 == 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for completeness also check reflection_coefficient.

Would it make sense to check here also if they are all >= 0 and <= 1.0? ( We ought to have some way to deal with this in the code as well btw 🤔 But maybe can happen when you look into the FFT? (Does that have an issue btw? Then could add a todo there for checking that spectrum values are between 0 and 1 and throwing at least a warning if they aren't.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this should be done in the test, or in the compute_spectrum_fdtd function? (The check that all values are betweenn 0 and 1)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still assert it here that they are all between 0 and 1. Doesn't hurt and serves as a sanity check if the check inside the codebase is accidentally circumvented



if __name__ == "__main__":
test_calculate_coefficient()
153 changes: 153 additions & 0 deletions nidn/tests/fdtd_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
from torch import zeros, tensor, cfloat
torbjornstoro marked this conversation as resolved.
Show resolved Hide resolved
from numpy import subtract

from nidn.utils.global_constants import SPEED_OF_LIGHT

from ..fdtd_integration.init_fdtd import init_fdtd
torbjornstoro marked this conversation as resolved.
Show resolved Hide resolved
from ..materials.layer_builder import LayerBuilder
from ..trcwa.compute_target_frequencies import compute_target_frequencies
from ..utils.load_default_cfg import load_default_cfg
from ..utils.compute_spectrum import compute_spectrum


def test_fdtd_grid_creation():
"""Test that the simulation is created in the right way, whith the correcto objects and correct realtive placement of them"""
torbjornstoro marked this conversation as resolved.
Show resolved Hide resolved
# Create grid with multiple uniform layer
cfg = load_default_cfg()
cfg.N_layers = 4
cfg.N_freq = 1
eps_grid = zeros(1, 1, cfg.N_layers, cfg.N_freq, dtype=cfloat)
cfg.Nx = 1
cfg.Ny = 1
# Note: something went wrong when smalles wavelength was 1e-5, guess its the grid scaling
gomezzz marked this conversation as resolved.
Show resolved Hide resolved
cfg.target_frequencies = compute_target_frequencies(
cfg.physical_wavelength_range[0],
cfg.physical_wavelength_range[1],
cfg.N_freq,
"linear",
)
layer_builder = LayerBuilder(cfg)
eps_grid[:, :, 0, :] = layer_builder.build_uniform_layer("titanium_oxide")
eps_grid[:, :, 1, :] = layer_builder.build_uniform_layer("zirconium")
eps_grid[:, :, 2, :] = layer_builder.build_uniform_layer("gallium_arsenide")
eps_grid[:, :, 3, :] = layer_builder.build_uniform_layer("silicon_nitride")
grid, transmission_detector, reflection_detetctor = init_fdtd(
cfg,
include_object=True,
wavelength=cfg.physical_wavelength_range[0],
permittivity=eps_grid,
)
# Check that it was made properly
assert len(grid.objects) == 4
for i in range(len(grid.objects)):
assert grid.objects[i].permittivity == eps_grid[0, 0, i, 0].real
assert (
grid.objects[i].conductivity[0, 0, 0, 0]
- (
eps_grid[0, 0, i, 0].imag
* SPEED_OF_LIGHT
/ cfg.physical_wavelength_range[0]
)
< 1e-8
)

assert len(grid.detectors) == 2
# Check that the reflection detector is placed before the first layer, and the transmission detector is placed after the last layer
assert transmission_detector.x[0] >= grid.objects[-1].x.stop
assert reflection_detetctor.x[0] <= grid.objects[0].x.start

assert len(grid.sources) == 1
# If periodic boundaries in both x and y, it is two, if pml in x and periodic in y there is 3 and 4 if pml in both directions (I think)
assert len(grid.boundaries) >= 2


def test_fdtd_simulation_single_layer():
"""Test that checks that the calculate_spectrum function returns the correct spectrum for a single layer"""
# Create grid with uniform layer
cfg = load_default_cfg()
cfg.N_freq = 5
cfg.N_layers = 1
eps_grid = zeros(1, 1, cfg.N_layers, cfg.N_freq, dtype=cfloat)
# Note: something went wrong when smalles wavelength was 1e-5, guess its the grid scaling
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need the note? Actually, you don't set the wl here any more do you? Maybe would be better to to avoid breaking things when we change the default WL in the cfg. (then we might also not know anymore which values were supposed to be here either :D )

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note is not needed, created an issue about that. And indeed, the wavelength is not set, just using default

Copy link
Collaborator Author

@torbjornstoro torbjornstoro Mar 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add a check for the wavelength in the validate_cfg

cfg.target_frequencies = compute_target_frequencies(
cfg.physical_wavelength_range[0],
cfg.physical_wavelength_range[1],
cfg.N_freq,
"linear",
)
cfg.solver = "FDTD"
layer_builder = LayerBuilder(cfg)
eps_grid[:, :, 0, :] = layer_builder.build_uniform_layer("titanium_oxide")
transmission_spectrum, reflection_spectrum = compute_spectrum(eps_grid, cfg)
validated_transmission_spectrum = [
tensor(0.0),
tensor(0.5564),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is not a lot of digits. Why not more precise values?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the output from running the compute_spectrum in my notebook, which I just copied. Can probably set higher precision for the output?

tensor(0.5902),
tensor(0.4664),
tensor(0.4211),
]
validated_reflection_spectrum = [
tensor(0.9515),
tensor(0.1605),
tensor(0.3508),
tensor(0.3171),
tensor(0.3437),
torbjornstoro marked this conversation as resolved.
Show resolved Hide resolved
]
assert all(
abs(subtract(transmission_spectrum, validated_transmission_spectrum)) < 1e-4
torbjornstoro marked this conversation as resolved.
Show resolved Hide resolved
)
assert all(abs(subtract(reflection_spectrum, validated_reflection_spectrum)) < 1e-4)


def test_fdtd_simulation_four_layers():
"""Test that checks that the calculate_spectrum function returns the correct spectrum for a simulation with four layers"""
# Create grid with four uniform layer
cfg = load_default_cfg()
cfg.N_layers = 4
cfg.FDTD_niter = 400
cfg.N_freq = 5
eps_grid = zeros(1, 1, cfg.N_layers, cfg.N_freq, dtype=cfloat)
cfg.target_frequencies = compute_target_frequencies(
cfg.physical_wavelength_range[0],
cfg.physical_wavelength_range[1],
cfg.N_freq,
"linear",
)
cfg.solver = "FDTD"
layer_builder = LayerBuilder(cfg)
eps_grid[:, :, 0, :] = layer_builder.build_uniform_layer("titanium_oxide")
eps_grid[:, :, 1, :] = layer_builder.build_uniform_layer("zirconium")
eps_grid[:, :, 2, :] = layer_builder.build_uniform_layer("gallium_arsenide")
eps_grid[:, :, 3, :] = layer_builder.build_uniform_layer("silicon_nitride")
transmission_spectrum, reflection_spectrum = compute_spectrum(eps_grid, cfg)
validated_transmission_spectrum = [
torbjornstoro marked this conversation as resolved.
Show resolved Hide resolved
tensor(0.0),
tensor(0.0),
tensor(0.0),
tensor(0.0),
tensor(0.0),
]
validated_reflection_spectrum = [
tensor(0.9515),
tensor(0.4005),
tensor(0.4919),
tensor(0.6812),
tensor(0.2888),
]
assert all(
abs(subtract(transmission_spectrum, validated_transmission_spectrum)) < 1e-4
)
assert all(abs(subtract(reflection_spectrum, validated_reflection_spectrum)) < 1e-4)


def test_single_patterned_layer():
"""Test that a pattern layer returns teh correct spectrum"""
# TODO: Test patterned layer, must be implemented first
pass


if __name__ == "__main__":
test_fdtd_grid_creation()
test_fdtd_simulation_single_layer()
test_fdtd_simulation_four_layers()
test_single_patterned_layer()
74 changes: 62 additions & 12 deletions nidn/training/utils/validate_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,17 @@ def _validate_config(cfg: DotMap):
"reg_loss_weight",
"add_noise",
"noise_scale",
"solver",
"TRCWA_L_grid",
"TRCWA_NG",
"FDTD_grid",
"FDTD_use_pointsource",
"FDTD_use_pulsesource",
"FDTD_pml_thickness",
"FDTD_source_position",
"FDTD_free_space_distance",
"FDTD_reflection_detector_x",
"FDTD_niter",
"target_reflectance_spectrum",
"target_transmittance_spectrum",
"freq_distribution",
Expand All @@ -60,6 +69,7 @@ def _validate_config(cfg: DotMap):
"eps_oversampling",
"seed",
"TRCWA_NG",
"FDTD_niter",
]
float_keys = [
"L",
Expand All @@ -70,12 +80,24 @@ def _validate_config(cfg: DotMap):
"siren_omega",
"noise_scale",
"reg_loss_weight",
"FDTD_free_space_distance",
"FDTD_reflection_detector_x",
"FDTD_pml_thickness",
]
boolean_keys = ["use_regularization_loss", "add_noise", "use_gpu", "avoid_zero_eps"]
string_keys = ["model_type", "type", "name", "freq_distribution"]
boolean_keys = [
"use_regularization_loss",
"add_noise",
"use_gpu",
"avoid_zero_eps",
"FDTD_use_pulsesource",
"FDTD_use_pointsource",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aren't these mutually exclusive? If so, shouldn't rather be something like FDTD_source_type = "pulse" or "point" ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the pulse is about continuous wave or not. point is about pointsource or linesource, both which can be continuous or have a pulse. But I can change the naming/structure for clarity

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, well depends on the logic behind. "use_pointsource" to me sounds as if it is true I use a point source, otherwise I use none.

If it is like that fine, otherwise it should probably be sourcetype with FDTD_source_type="point" or "line"

Analogously for pulse

]
string_keys = ["model_type", "type", "name", "freq_distribution", "solver"]
list_keys = [
"PER_LAYER_THICKNESS",
"TRCWA_L_grid",
"FDTD_grid",
"FDTD_source_position",
"target_reflectance_spectrum",
"target_transmittance_spectrum",
"physical_wavelength_range",
Expand Down Expand Up @@ -111,6 +133,14 @@ def _validate_config(cfg: DotMap):
if not (cfg.physical_wavelength_range[0] < cfg.physical_wavelength_range[1]):
raise ValueError(f"physical_wavelength_range must be ordered from low to high")

if (
cfg.FDTD_pml_thickness + cfg.FDTD_free_space_distance
< cfg.FDTD_reflection_detector_x
) or (cfg.FDTD_reflection_detector_x < cfg.FDTD_pml_thickness):
raise ValueError(
f"Reflection detector must be placed in the free space before an eventual object, and after the pml layer"
)

positive_value_keys = [
"L",
"n_neurons",
Expand All @@ -124,6 +154,10 @@ def _validate_config(cfg: DotMap):
"noise_scale",
"TRCWA_NG",
"reg_loss_weight",
"FDTD_niter",
"FDTD_free_space_distance",
"FDTD_reflection_detector_x",
"FDTD_pml_thickness",
]
for key in positive_value_keys:
if not (cfg[key] > 0):
Expand All @@ -142,24 +176,32 @@ def _validate_config(cfg: DotMap):
if not cfg.TRCWA_L_grid[0][0] > cfg.TRCWA_L_grid[0][1]:
raise ValueError(f"TRCWA_L_grid dim0 must be ordered from high to low")

if not all(cfg.TRCWA_L_grid) >= 0:
raise ValueError(f"TRCWA_L_grid must be positive")

if not all(cfg.target_transmittance_spectrum) >= 0:
raise ValueError(f"target_transmittance_spectrum must be positive")
all_positive_list_keys = [
"TRCWA_L_grid",
"PER_LAYER_THICKNESS",
"FDTD_grid",
"FDTD_source_position",
]
all_positive_or_zero_list_keys = [
"target_transmittance_spectrum",
"target_reflectance_spectrum",
]

if not all(cfg.target_reflectance_spectrum) >= 0:
raise ValueError(f"target_reflectance_spectrum must be positive")
for key in all_positive_list_keys:
if not (all(cfg[key]) > 0.0):
raise ValueError(f"All elements in {key} must be a positive integer")
for key in all_positive_or_zero_list_keys:
if not (all(cfg[key]) >= 0.0):
raise ValueError(
f"All elements in {key} must be a positive integer or zero"
)

if not len(cfg.target_transmittance_spectrum) == cfg.N_freq:
raise ValueError(f"target_transmittance_spectrum must have length N_freq")

if not len(cfg.target_reflectance_spectrum) == cfg.N_freq:
raise ValueError(f"target_reflectance_spectrum must have length N_freq")

if not (all(cfg.TRCWA_PER_LAYER_THICKNESS) > 0.0):
raise ValueError(f"thickness must a positive number.")

if not (
len(cfg.PER_LAYER_THICKNESS) == cfg.N_layers
or len(cfg.PER_LAYER_THICKNESS) == 1
Expand All @@ -168,3 +210,11 @@ def _validate_config(cfg: DotMap):

if not (cfg.freq_distribution == "linear" or cfg.freq_distribution == "log"):
raise ValueError(f"freq_distribution must be either 'linear' or 'log'")

if not len(cfg.FDTD_grid) == 3:
raise ValueError(f"FDTD_grid must me 3-dimentional")

if not (len(cfg.FDTD_source_position) == 2 or len(cfg.FDTD_source_position) == 3):
raise ValueError(
f"The FDTD source needs either 2- or 3-dimensional coordinates"
)
Loading