@@ -1876,3 +1876,337 @@ def test_RF_license_suppression():
18761876 with AssertLogLevel (None ):
18771877 mode_spec = td .MicrowaveModeSpec ._default_without_license_warning ()
18781878 td .config .microwave .suppress_rf_license_warning = original_setting
1879+
1880+
1881+ def test_microwave_mode_data_reordering_with_transmission_line_data ():
1882+ """Test that transmission_line_data is correctly reordered when modes are reordered."""
1883+ from tidy3d .components .data .data_array import (
1884+ CurrentFreqModeDataArray ,
1885+ ImpedanceFreqModeDataArray ,
1886+ ModeIndexDataArray ,
1887+ ScalarModeFieldDataArray ,
1888+ VoltageFreqModeDataArray ,
1889+ )
1890+ from tidy3d .components .microwave .data .dataset import TransmissionLineDataset
1891+
1892+ # Setup coordinates
1893+ x = [- 1 , 1 , 3 ]
1894+ y = [- 2 , 0 ]
1895+ z = [- 3 , - 1 , 1 , 3 , 5 ]
1896+ f = [2e14 , 3e14 ]
1897+ mode_index = np .arange (3 )
1898+
1899+ grid = td .Grid (boundaries = td .Coords (x = x , y = y , z = z ))
1900+ field_coords = {"x" : x [:- 1 ], "y" : y [:- 1 ], "z" : z [:- 1 ], "f" : f , "mode_index" : mode_index }
1901+ index_coords = {"f" : f , "mode_index" : mode_index }
1902+
1903+ # Create field data with distinct values for each mode
1904+ field_values = np .zeros ((2 , 1 , 4 , 2 , 3 ), dtype = complex )
1905+ for mode_idx in range (3 ):
1906+ # Each mode gets a unique value to track reordering
1907+ field_values [:, :, :, :, mode_idx ] = (mode_idx + 1 ) * (1 + 1j )
1908+
1909+ field = ScalarModeFieldDataArray (field_values , coords = field_coords )
1910+
1911+ # Create mode index data with distinct values for each mode
1912+ index_values = np .zeros ((2 , 3 ), dtype = complex )
1913+ for mode_idx in range (3 ):
1914+ index_values [:, mode_idx ] = (mode_idx + 1 ) * 1.5 + 0.1j
1915+ index_data = ModeIndexDataArray (index_values , coords = index_coords )
1916+
1917+ # Create transmission line data with distinct values for each mode
1918+ impedance_values = np .zeros ((2 , 3 ))
1919+ voltage_values = np .zeros ((2 , 3 ), dtype = complex )
1920+ current_values = np .zeros ((2 , 3 ), dtype = complex )
1921+
1922+ for mode_idx in range (3 ):
1923+ # Each mode gets unique impedance, voltage, and current values
1924+ impedance_values [:, mode_idx ] = 50 * (mode_idx + 1 )
1925+ voltage_values [:, mode_idx ] = (mode_idx + 1 ) * (10 + 5j )
1926+ current_values [:, mode_idx ] = (mode_idx + 1 ) * (0.2 + 0.1j )
1927+
1928+ impedance_data = ImpedanceFreqModeDataArray (impedance_values , coords = index_coords )
1929+ voltage_data = VoltageFreqModeDataArray (voltage_values , coords = index_coords )
1930+ current_data = CurrentFreqModeDataArray (current_values , coords = index_coords )
1931+
1932+ tl_data = TransmissionLineDataset (
1933+ Z0 = impedance_data , voltage_coeffs = voltage_data , current_coeffs = current_data
1934+ )
1935+
1936+ # Create monitor
1937+ monitor = td .MicrowaveModeSolverMonitor (
1938+ center = (0 , 0 , 0 ),
1939+ size = (2 , 0 , 6 ),
1940+ freqs = [2e14 , 3e14 ],
1941+ mode_spec = td .MicrowaveModeSpec (num_modes = 3 , impedance_specs = td .AutoImpedanceSpec ()),
1942+ name = "microwave_mode_solver" ,
1943+ )
1944+
1945+ # Create MicrowaveModeSolverData
1946+ data = td .MicrowaveModeSolverData (
1947+ monitor = monitor ,
1948+ Ex = field ,
1949+ Ey = field ,
1950+ Ez = field ,
1951+ Hx = field ,
1952+ Hy = field ,
1953+ Hz = field ,
1954+ n_complex = index_data ,
1955+ grid_expanded = grid ,
1956+ transmission_line_data = tl_data ,
1957+ )
1958+
1959+ # Define a reordering: reverse the mode order for each frequency
1960+ # Shape: (num_freqs, num_modes) = (2, 3)
1961+ # Original order: [0, 1, 2] -> New order: [2, 1, 0]
1962+ sort_inds_2d = np .array ([[2 , 1 , 0 ], [2 , 1 , 0 ]])
1963+
1964+ # Apply mode reordering
1965+ reordered_data = data ._apply_mode_reorder (sort_inds_2d )
1966+
1967+ # Verify that the main mode data is reordered correctly
1968+ # Original mode 2 should now be at index 0
1969+ original_mode_2_value = (2 + 1 ) * (1 + 1j ) # Mode 2 had value 3*(1+1j)
1970+ assert np .allclose (
1971+ reordered_data .Ex .isel (mode_index = 0 , x = 0 , y = 0 , z = 0 ).values , original_mode_2_value
1972+ ), "Main field data not reordered correctly"
1973+
1974+ # Original mode 0 should now be at index 2
1975+ original_mode_0_value = (0 + 1 ) * (1 + 1j ) # Mode 0 had value 1*(1+1j)
1976+ assert np .allclose (
1977+ reordered_data .Ex .isel (mode_index = 2 , x = 0 , y = 0 , z = 0 ).values , original_mode_0_value
1978+ ), "Main field data not reordered correctly"
1979+
1980+ # Verify that transmission_line_data is also reordered correctly
1981+ assert reordered_data .transmission_line_data is not None , (
1982+ "transmission_line_data should not be None"
1983+ )
1984+
1985+ # Check Z0 reordering
1986+ # Original mode 2 had Z0 = 50 * 3 = 150
1987+ assert np .allclose (reordered_data .transmission_line_data .Z0 .isel (mode_index = 0 ).values , 150.0 ), (
1988+ "transmission_line_data.Z0 not reordered correctly"
1989+ )
1990+
1991+ # Original mode 0 had Z0 = 50 * 1 = 50
1992+ assert np .allclose (reordered_data .transmission_line_data .Z0 .isel (mode_index = 2 ).values , 50.0 ), (
1993+ "transmission_line_data.Z0 not reordered correctly"
1994+ )
1995+
1996+ # Check voltage_coeffs reordering
1997+ # Original mode 2 had voltage = 3 * (10 + 5j)
1998+ assert np .allclose (
1999+ reordered_data .transmission_line_data .voltage_coeffs .isel (mode_index = 0 ).values ,
2000+ 3 * (10 + 5j ),
2001+ ), "transmission_line_data.voltage_coeffs not reordered correctly"
2002+
2003+ # Original mode 0 had voltage = 1 * (10 + 5j)
2004+ assert np .allclose (
2005+ reordered_data .transmission_line_data .voltage_coeffs .isel (mode_index = 2 ).values ,
2006+ 1 * (10 + 5j ),
2007+ ), "transmission_line_data.voltage_coeffs not reordered correctly"
2008+
2009+ # Check current_coeffs reordering
2010+ # Original mode 2 had current = 3 * (0.2 + 0.1j)
2011+ assert np .allclose (
2012+ reordered_data .transmission_line_data .current_coeffs .isel (mode_index = 0 ).values ,
2013+ 3 * (0.2 + 0.1j ),
2014+ ), "transmission_line_data.current_coeffs not reordered correctly"
2015+
2016+ # Original mode 0 had current = 1 * (0.2 + 0.1j)
2017+ assert np .allclose (
2018+ reordered_data .transmission_line_data .current_coeffs .isel (mode_index = 2 ).values ,
2019+ 1 * (0.2 + 0.1j ),
2020+ ), "transmission_line_data.current_coeffs not reordered correctly"
2021+
2022+ # Verify mode index data is also reordered
2023+ # Original mode 2 had n_complex = 3 * 1.5 + 0.1j
2024+ assert np .allclose (reordered_data .n_complex .isel (mode_index = 0 ).values , 3 * 1.5 + 0.1j ), (
2025+ "n_complex not reordered correctly"
2026+ )
2027+
2028+
2029+ def test_microwave_mode_data_interpolation ():
2030+ """Test that MicrowaveModeSolverData interpolation correctly handles transmission_line_data."""
2031+ from tidy3d .components .data .data_array import (
2032+ CurrentFreqModeDataArray ,
2033+ ImpedanceFreqModeDataArray ,
2034+ ModeIndexDataArray ,
2035+ ScalarModeFieldDataArray ,
2036+ VoltageFreqModeDataArray ,
2037+ )
2038+ from tidy3d .components .microwave .data .dataset import TransmissionLineDataset
2039+
2040+ # Setup coordinates with sparse frequencies
2041+ x = [- 1 , 1 , 3 ]
2042+ y = [- 2 , 0 ]
2043+ z = [- 3 , - 1 , 1 , 3 , 5 ]
2044+ f_sparse = np .array ([1e14 , 1.5e14 , 2e14 ]) # 3 source frequencies
2045+ mode_index = np .arange (2 )
2046+
2047+ grid = td .Grid (boundaries = td .Coords (x = x , y = y , z = z ))
2048+ field_coords = {"x" : x [:- 1 ], "y" : y [:- 1 ], "z" : z [:- 1 ], "f" : f_sparse , "mode_index" : mode_index }
2049+ index_coords = {"f" : f_sparse , "mode_index" : mode_index }
2050+
2051+ # Create field data with frequency-dependent values
2052+ field_values = np .zeros (
2053+ (len (x ) - 1 , len (y ) - 1 , len (z ) - 1 , len (f_sparse ), len (mode_index )), dtype = complex
2054+ )
2055+ for f_idx , freq in enumerate (f_sparse ):
2056+ for mode_idx in range (len (mode_index )):
2057+ # Value depends on both frequency and mode: (freq/1e14) * (mode+1) * (1+1j)
2058+ field_values [:, :, :, f_idx , mode_idx ] = (freq / 1e14 ) * (mode_idx + 1 ) * (1 + 1j )
2059+
2060+ field = ScalarModeFieldDataArray (field_values , coords = field_coords )
2061+
2062+ # Create mode index data with frequency dependence
2063+ index_values = np .zeros ((len (f_sparse ), len (mode_index )), dtype = complex )
2064+ for f_idx , freq in enumerate (f_sparse ):
2065+ for mode_idx in range (len (mode_index )):
2066+ # n_eff increases with frequency: 1.5 + (freq/1e14)*0.1 + mode_idx*0.2
2067+ index_values [f_idx , mode_idx ] = 1.5 + (freq / 1e14 ) * 0.1 + mode_idx * 0.2 + 0.01j
2068+ index_data = ModeIndexDataArray (index_values , coords = index_coords )
2069+
2070+ # Create transmission line data with frequency dependence
2071+ impedance_values = np .zeros ((len (f_sparse ), len (mode_index )))
2072+ voltage_values = np .zeros ((len (f_sparse ), len (mode_index )), dtype = complex )
2073+ current_values = np .zeros ((len (f_sparse ), len (mode_index )), dtype = complex )
2074+
2075+ for f_idx , freq in enumerate (f_sparse ):
2076+ for mode_idx in range (len (mode_index )):
2077+ # Impedance varies with frequency: 50 + (freq/1e14)*10 + mode_idx*20
2078+ impedance_values [f_idx , mode_idx ] = 50 + (freq / 1e14 ) * 10 + mode_idx * 20
2079+ # Voltage varies with frequency
2080+ voltage_values [f_idx , mode_idx ] = ((freq / 1e14 ) + mode_idx ) * (10 + 5j )
2081+ # Current varies with frequency
2082+ current_values [f_idx , mode_idx ] = ((freq / 1e14 ) + mode_idx ) * (0.2 + 0.1j )
2083+
2084+ impedance_data = ImpedanceFreqModeDataArray (impedance_values , coords = index_coords )
2085+ voltage_data = VoltageFreqModeDataArray (voltage_values , coords = index_coords )
2086+ current_data = CurrentFreqModeDataArray (current_values , coords = index_coords )
2087+
2088+ tl_data = TransmissionLineDataset (
2089+ Z0 = impedance_data , voltage_coeffs = voltage_data , current_coeffs = current_data
2090+ )
2091+
2092+ # Create monitor
2093+ monitor = td .MicrowaveModeSolverMonitor (
2094+ center = (0 , 0 , 0 ),
2095+ size = (2 , 0 , 6 ),
2096+ freqs = f_sparse ,
2097+ mode_spec = td .MicrowaveModeSpec (num_modes = 2 , impedance_specs = td .AutoImpedanceSpec ()),
2098+ name = "microwave_mode_solver" ,
2099+ )
2100+
2101+ # Create MicrowaveModeSolverData
2102+ data = td .MicrowaveModeSolverData (
2103+ monitor = monitor ,
2104+ Ex = field ,
2105+ Ey = field ,
2106+ Ez = field ,
2107+ Hx = field ,
2108+ Hy = field ,
2109+ Hz = field ,
2110+ n_complex = index_data ,
2111+ grid_expanded = grid ,
2112+ transmission_line_data = tl_data ,
2113+ )
2114+
2115+ # Interpolate to denser frequency grid
2116+ f_dense = np .linspace (1e14 , 2e14 , 11 )
2117+
2118+ # Test linear interpolation
2119+ data_interp_linear = data .interp_in_freq (freqs = f_dense , method = "linear" , renormalize = False )
2120+
2121+ # Verify that interpolated data has correct shape
2122+ assert len (data_interp_linear .monitor .freqs ) == len (f_dense ), (
2123+ "Interpolated data should have new frequency count"
2124+ )
2125+ assert data_interp_linear .Ex .shape [- 1 ] == len (mode_index ), "Mode count should be preserved"
2126+ assert data_interp_linear .Ex .shape [- 2 ] == len (f_dense ), (
2127+ "Frequency dimension should match target"
2128+ )
2129+
2130+ # Verify that transmission_line_data is also interpolated
2131+ assert data_interp_linear .transmission_line_data is not None , (
2132+ "transmission_line_data should be interpolated"
2133+ )
2134+ assert len (data_interp_linear .transmission_line_data .Z0 .coords ["f" ]) == len (f_dense ), (
2135+ "transmission_line_data.Z0 should be interpolated to new frequencies"
2136+ )
2137+ assert len (data_interp_linear .transmission_line_data .voltage_coeffs .coords ["f" ]) == len (
2138+ f_dense
2139+ ), "transmission_line_data.voltage_coeffs should be interpolated to new frequencies"
2140+ assert len (data_interp_linear .transmission_line_data .current_coeffs .coords ["f" ]) == len (
2141+ f_dense
2142+ ), "transmission_line_data.current_coeffs should be interpolated to new frequencies"
2143+
2144+ # Test interpolation accuracy at midpoint
2145+ f_mid = 1.5e14
2146+
2147+ # Check that field interpolation is reasonable
2148+ # At f=1.5e14, mode 0 should have value approximately (1.5) * 1 * (1+1j) = 1.5*(1+1j)
2149+ field_at_mid_mode0 = data_interp_linear .Ex .sel (
2150+ f = f_mid , mode_index = 0 , x = 0 , y = 0 , z = 0 , method = "nearest"
2151+ ).values
2152+ expected_field_mode0 = 1.5 * 1 * (1 + 1j )
2153+ assert np .allclose (field_at_mid_mode0 , expected_field_mode0 , rtol = 0.01 ), (
2154+ f"Field interpolation for mode 0: expected { expected_field_mode0 } , got { field_at_mid_mode0 } "
2155+ )
2156+
2157+ # Check that n_complex interpolation is reasonable
2158+ # At f=1.5e14, mode 0 should have n_eff approximately 1.5 + 1.5*0.1 + 0*0.2 = 1.65
2159+ n_complex_at_mid_mode0 = data_interp_linear .n_complex .sel (
2160+ f = f_mid , mode_index = 0 , method = "nearest"
2161+ ).values
2162+ expected_n_complex_mode0 = 1.5 + 1.5 * 0.1 + 0 * 0.2 + 0.01j
2163+ assert np .allclose (n_complex_at_mid_mode0 , expected_n_complex_mode0 , rtol = 0.01 ), (
2164+ f"n_complex interpolation for mode 0: expected { expected_n_complex_mode0 } , got { n_complex_at_mid_mode0 } "
2165+ )
2166+
2167+ # Check that transmission line data interpolation is reasonable
2168+ # At f=1.5e14, mode 0 should have Z0 approximately 50 + 1.5*10 + 0*20 = 65
2169+ Z0_at_mid_mode0 = data_interp_linear .transmission_line_data .Z0 .sel (
2170+ f = f_mid , mode_index = 0 , method = "nearest"
2171+ ).values
2172+ expected_Z0_mode0 = 50 + 1.5 * 10 + 0 * 20
2173+ assert np .allclose (Z0_at_mid_mode0 , expected_Z0_mode0 , rtol = 0.01 ), (
2174+ f"Z0 interpolation for mode 0: expected { expected_Z0_mode0 } , got { Z0_at_mid_mode0 } "
2175+ )
2176+
2177+ # At f=1.5e14, mode 0 should have voltage approximately (1.5 + 0) * (10 + 5j) = 1.5*(10+5j)
2178+ voltage_at_mid_mode0 = data_interp_linear .transmission_line_data .voltage_coeffs .sel (
2179+ f = f_mid , mode_index = 0 , method = "nearest"
2180+ ).values
2181+ expected_voltage_mode0 = 1.5 * (10 + 5j )
2182+ assert np .allclose (voltage_at_mid_mode0 , expected_voltage_mode0 , rtol = 0.01 ), (
2183+ f"voltage_coeffs interpolation for mode 0: expected { expected_voltage_mode0 } , got { voltage_at_mid_mode0 } "
2184+ )
2185+
2186+ # At f=1.5e14, mode 0 should have current approximately (1.5 + 0) * (0.2 + 0.1j) = 1.5*(0.2+0.1j)
2187+ current_at_mid_mode0 = data_interp_linear .transmission_line_data .current_coeffs .sel (
2188+ f = f_mid , mode_index = 0 , method = "nearest"
2189+ ).values
2190+ expected_current_mode0 = 1.5 * (0.2 + 0.1j )
2191+ assert np .allclose (current_at_mid_mode0 , expected_current_mode0 , rtol = 0.01 ), (
2192+ f"current_coeffs interpolation for mode 0: expected { expected_current_mode0 } , got { current_at_mid_mode0 } "
2193+ )
2194+
2195+ # Test at endpoints to ensure they match original values
2196+ # At f=1e14, mode 1 should have Z0 = 50 + 1*10 + 1*20 = 80
2197+ Z0_at_start_mode1 = data_interp_linear .transmission_line_data .Z0 .sel (
2198+ f = 1e14 , mode_index = 1 , method = "nearest"
2199+ ).values
2200+ expected_Z0_start_mode1 = 50 + 1 * 10 + 1 * 20
2201+ assert np .allclose (Z0_at_start_mode1 , expected_Z0_start_mode1 , rtol = 1e-6 ), (
2202+ f"Z0 at endpoint should match original: expected { expected_Z0_start_mode1 } , got { Z0_at_start_mode1 } "
2203+ )
2204+
2205+ # At f=2e14, mode 1 should have Z0 = 50 + 2*10 + 1*20 = 90
2206+ Z0_at_end_mode1 = data_interp_linear .transmission_line_data .Z0 .sel (
2207+ f = 2e14 , mode_index = 1 , method = "nearest"
2208+ ).values
2209+ expected_Z0_end_mode1 = 50 + 2 * 10 + 1 * 20
2210+ assert np .allclose (Z0_at_end_mode1 , expected_Z0_end_mode1 , rtol = 1e-6 ), (
2211+ f"Z0 at endpoint should match original: expected { expected_Z0_end_mode1 } , got { Z0_at_end_mode1 } "
2212+ )
0 commit comments