|
1 | 1 | """Tests the simulation and its validators."""
|
2 | 2 |
|
| 3 | +import shutil |
| 4 | +from pathlib import Path |
| 5 | + |
3 | 6 | import gdstk
|
4 | 7 | import matplotlib.pyplot as plt
|
5 | 8 | import numpy as np
|
6 | 9 | import pydantic.v1 as pydantic
|
7 | 10 | import pytest
|
8 | 11 | import tidy3d as td
|
| 12 | +from matplotlib.testing.decorators import check_figures_equal |
9 | 13 | from tidy3d.components import simulation
|
10 | 14 | from tidy3d.components.scene import MAX_GEOMETRY_COUNT, MAX_NUM_MEDIUMS
|
11 | 15 | from tidy3d.components.simulation import MAX_NUM_SOURCES
|
@@ -693,6 +697,208 @@ def test_plot_eps_bounds():
|
693 | 697 | plt.close()
|
694 | 698 |
|
695 | 699 |
|
| 700 | +class TestAnisotropicPlotting: |
| 701 | + """Tests for plotting anisotropic media""" |
| 702 | + |
| 703 | + diag_comps = ["xx", "yy", "zz"] |
| 704 | + offdiag_comps = ["xy", "yx", "xz", "zx", "yz", "zy"] |
| 705 | + allcomps = diag_comps + offdiag_comps |
| 706 | + |
| 707 | + medium_diag = td.AnisotropicMedium( |
| 708 | + xx=td.Medium(permittivity=5), yy=td.Medium(permittivity=10), zz=td.Medium(permittivity=15) |
| 709 | + ) |
| 710 | + |
| 711 | + medium_fullyani = td.FullyAnisotropicMedium(permittivity=[[6, 2, 3], [2, 7, 4], [3, 4, 8]]) |
| 712 | + |
| 713 | + @pytest.fixture(scope="class") |
| 714 | + def medium_customani(self): |
| 715 | + """based this custom medium on |
| 716 | + https://docs.flexcompute.com/projects/tidy3d/en/latest/api/_autosummary/tidy3d.CustomAnisotropicMedium.html |
| 717 | + """ |
| 718 | + Nx, Ny, Nz = 100, 100, 100 |
| 719 | + x = np.linspace(-1, 1, Nx) |
| 720 | + y = np.linspace(-1, 1, Ny) |
| 721 | + z = np.linspace(-1, 1, Nz) |
| 722 | + coords = dict(x=x, y=y, z=z) |
| 723 | + permittivity = td.SpatialDataArray(2 * np.ones((Nx, Ny, Nz)), coords=coords) |
| 724 | + conductivity = td.SpatialDataArray(np.ones((Nx, Ny, Nz)), coords=coords) |
| 725 | + medium_xx = td.CustomMedium(permittivity=permittivity, conductivity=conductivity) |
| 726 | + medium_yy = td.CustomMedium(permittivity=2 * permittivity, conductivity=conductivity) |
| 727 | + |
| 728 | + # make the zz component a spatially varying medium |
| 729 | + # define coordinate array |
| 730 | + x_mesh, y_mesh, _ = np.meshgrid(x, y, z, indexing="ij") |
| 731 | + r_mesh = np.sqrt(x_mesh**2 + y_mesh**2) # radial distance |
| 732 | + |
| 733 | + # index of refraction array |
| 734 | + # assign the refractive index value to the array according to the desired profile |
| 735 | + n_data = np.ones((Nx, Ny, Nz)) |
| 736 | + n0 = 2 |
| 737 | + A = 0.5 |
| 738 | + r = 1 |
| 739 | + n_data[r_mesh <= r] = n0 * (1 - A * r_mesh[r_mesh <= r] ** 2) |
| 740 | + # convert to dataset array |
| 741 | + n_dataset = td.SpatialDataArray(n_data, coords=dict(x=x, y=y, z=z)) |
| 742 | + medium_zz = td.CustomMedium.from_nk(n_dataset, interp_method="nearest") |
| 743 | + |
| 744 | + return td.CustomAnisotropicMedium(xx=medium_xx, yy=medium_yy, zz=medium_zz) |
| 745 | + |
| 746 | + @pytest.fixture(scope="module", autouse=True) |
| 747 | + def cleanup_figures(self): |
| 748 | + yield # Run tests first |
| 749 | + fig_dir = Path().resolve() / "result_images" |
| 750 | + shutil.rmtree(fig_dir, ignore_errors=False) |
| 751 | + |
| 752 | + def make_sim(self, medium): |
| 753 | + L = 5 |
| 754 | + |
| 755 | + source = td.UniformCurrentSource( |
| 756 | + center=(0, 0, -L / 3), |
| 757 | + size=(L, L / 2, 0), |
| 758 | + polarization="Ex", |
| 759 | + source_time=td.GaussianPulse( |
| 760 | + freq0=td.C_0, |
| 761 | + fwidth=10e14, |
| 762 | + ), |
| 763 | + ) |
| 764 | + structures = (td.Structure(geometry=td.Sphere(center=(0, 0, 0), radius=1), medium=medium),) |
| 765 | + |
| 766 | + return td.Simulation( |
| 767 | + size=(L, L, L), |
| 768 | + grid_spec=td.GridSpec.uniform(dl=0.01), |
| 769 | + structures=structures, |
| 770 | + sources=[source], |
| 771 | + run_time=1e-12, |
| 772 | + ) |
| 773 | + |
| 774 | + @pytest.mark.parametrize("eps_comp", ("xyz", "123", "", 5)) |
| 775 | + def test_bad_eps_arg(self, eps_comp): |
| 776 | + """Tests that an incorrect component raises the proper exception.""" |
| 777 | + with pytest.raises(ValueError, match=f"eps_component '{eps_comp}' is not supported. "): |
| 778 | + self.make_sim(self.medium_diag).plot_eps(x=0, eps_component=eps_comp) |
| 779 | + |
| 780 | + @pytest.mark.parametrize( |
| 781 | + "eps_comp", |
| 782 | + [ |
| 783 | + None, |
| 784 | + ] |
| 785 | + + diag_comps, |
| 786 | + ) |
| 787 | + def test_plot_anisotropic_medium(self, eps_comp): |
| 788 | + """Test plotting diagonal components of a diagonally anisotropic medium succeeds or not. |
| 789 | + diagonal components and ``None`` should succeed. |
| 790 | + """ |
| 791 | + self.make_sim(self.medium_diag).plot_eps(x=0, eps_component=eps_comp) |
| 792 | + |
| 793 | + @pytest.mark.parametrize("eps_comp", offdiag_comps) |
| 794 | + def test_plot_anisotropic_medium_offdiagfail(self, eps_comp): |
| 795 | + """Tests that plotting off-diagonal components of a diagonally anisotropic medium raises an exception.""" |
| 796 | + with pytest.raises( |
| 797 | + ValueError, |
| 798 | + match=f"Plotting component '{eps_comp}' of a diagonally-anisotropic permittivity tensor is not supported", |
| 799 | + ): |
| 800 | + self.make_sim(self.medium_diag).plot_eps(x=0, eps_component=eps_comp) |
| 801 | + |
| 802 | + @pytest.mark.parametrize( |
| 803 | + "eps_comp1,eps_comp2", |
| 804 | + ( |
| 805 | + pytest.param("xx", "yy", marks=pytest.mark.xfail), |
| 806 | + pytest.param("xx", "zz", marks=pytest.mark.xfail), |
| 807 | + pytest.param("yy", "zz", marks=pytest.mark.xfail), |
| 808 | + ), |
| 809 | + ) |
| 810 | + @check_figures_equal(extensions=("png",)) |
| 811 | + def test_plot_anisotropic_medium_diff(self, fig_test, fig_ref, eps_comp1, eps_comp2): |
| 812 | + """Tests that the plots of different components of an AnisotropicMedium are actually different.""" |
| 813 | + sim = self.make_sim(self.medium_diag) |
| 814 | + |
| 815 | + ax1 = fig_test.add_subplot() |
| 816 | + sim.plot_eps(x=0, eps_component=eps_comp1, ax=ax1) |
| 817 | + ax2 = fig_ref.add_subplot() |
| 818 | + sim.plot_eps(x=0, eps_component=eps_comp2, ax=ax2) |
| 819 | + |
| 820 | + @pytest.mark.parametrize( |
| 821 | + "eps_comp", |
| 822 | + [ |
| 823 | + None, |
| 824 | + ] |
| 825 | + + diag_comps |
| 826 | + + offdiag_comps, |
| 827 | + ) |
| 828 | + def test_plot_fully_anisotropic_medium(self, eps_comp): |
| 829 | + """Test plotting all components of a fully anisotropic medium. |
| 830 | + All plots should succeed. |
| 831 | + """ |
| 832 | + sim = self.make_sim(self.medium_fullyani) |
| 833 | + sim.plot_eps(x=0, eps_component=eps_comp) |
| 834 | + |
| 835 | + # Test parameters for comparing plots of a FullyAnisotropicMedium |
| 836 | + fullyani_testplot_diff_params = [] |
| 837 | + for eps_comp1 in allcomps: |
| 838 | + for eps_comp2 in allcomps: |
| 839 | + if eps_comp1 == eps_comp2 or eps_comp1[::-1] == eps_comp2: |
| 840 | + # Same components, or transposed components (eg. xy and yx) should plot the same |
| 841 | + fullyani_testplot_diff_params.append((eps_comp1, eps_comp2)) |
| 842 | + else: |
| 843 | + # All other component pairs should plot differently |
| 844 | + fullyani_testplot_diff_params.append( |
| 845 | + pytest.param(eps_comp1, eps_comp2, marks=pytest.mark.xfail) |
| 846 | + ) |
| 847 | + |
| 848 | + @pytest.mark.parametrize("eps_comp1,eps_comp2", fullyani_testplot_diff_params) |
| 849 | + @check_figures_equal(extensions=("png",)) |
| 850 | + def test_plot_fully_anisotropic_medium_diff(self, fig_test, fig_ref, eps_comp1, eps_comp2): |
| 851 | + """Tests that the plots of different components of a FullyAnisotropicMedium are actually different.""" |
| 852 | + sim = self.make_sim(self.medium_fullyani) |
| 853 | + |
| 854 | + ax1 = fig_test.add_subplot() |
| 855 | + sim.plot_eps(x=0, eps_component=eps_comp1, ax=ax1) |
| 856 | + ax2 = fig_ref.add_subplot() |
| 857 | + sim.plot_eps(x=0, eps_component=eps_comp2, ax=ax2) |
| 858 | + |
| 859 | + @pytest.mark.parametrize( |
| 860 | + "eps_comp", |
| 861 | + [ |
| 862 | + None, |
| 863 | + ] |
| 864 | + + diag_comps, |
| 865 | + ) |
| 866 | + def test_plot_customanisotropic_medium(self, eps_comp, medium_customani): |
| 867 | + """Test plotting diagonal components of a diagonally anisotropic custom medium. |
| 868 | + diagonal components and ``None`` should succeed. |
| 869 | + """ |
| 870 | + self.make_sim(medium_customani).plot_eps(x=0, eps_component=eps_comp) |
| 871 | + |
| 872 | + @pytest.mark.parametrize("eps_comp", offdiag_comps) |
| 873 | + def test_plot_customanisotropic_medium_offdiagfail(self, eps_comp, medium_customani): |
| 874 | + """Tests that plotting off-diagonal components of a diagonally anisotropic custom medium raises an exception.""" |
| 875 | + with pytest.raises( |
| 876 | + ValueError, |
| 877 | + match=f"Plotting component '{eps_comp}' of a diagonally-anisotropic permittivity tensor is not supported.", |
| 878 | + ): |
| 879 | + self.make_sim(medium_customani).plot_eps(x=0, eps_component=eps_comp) |
| 880 | + |
| 881 | + @pytest.mark.parametrize( |
| 882 | + "eps_comp1,eps_comp2", |
| 883 | + ( |
| 884 | + pytest.param("xx", "yy", marks=pytest.mark.xfail), |
| 885 | + pytest.param("xx", "zz", marks=pytest.mark.xfail), |
| 886 | + pytest.param("yy", "zz", marks=pytest.mark.xfail), |
| 887 | + ), |
| 888 | + ) |
| 889 | + @check_figures_equal(extensions=("png",)) |
| 890 | + def test_plot_customanisotropic_medium_diff( |
| 891 | + self, fig_test, fig_ref, eps_comp1, eps_comp2, medium_customani |
| 892 | + ): |
| 893 | + """Tests that the plots of different components of an AnisotropicMedium are actually different.""" |
| 894 | + sim = self.make_sim(medium_customani) |
| 895 | + |
| 896 | + ax1 = fig_test.add_subplot() |
| 897 | + sim.plot_eps(x=0, eps_component=eps_comp1, ax=ax1) |
| 898 | + ax2 = fig_ref.add_subplot() |
| 899 | + sim.plot_eps(x=0, eps_component=eps_comp2, ax=ax2) |
| 900 | + |
| 901 | + |
696 | 902 | def test_plot():
|
697 | 903 | SIM_FULL.plot(x=0)
|
698 | 904 | plt.close()
|
|
0 commit comments