55import numpy as np
66import pydantic .v1 as pd
77
8- from ....components .base import cached_property
8+ from ....components .base import cached_property , skip_if_fields_missing
99from ....components .data .data_array import FreqDataArray , FreqModeDataArray
10- from ....components .data .monitor_data import ModeSolverData
10+ from ....components .data .monitor_data import ModeData
1111from ....components .data .sim_data import SimulationData
1212from ....components .geometry .base import Box
1313from ....components .grid .grid import Grid
14- from ....components .monitor import FieldMonitor , ModeSolverMonitor
14+ from ....components .monitor import ModeMonitor
1515from ....components .simulation import Simulation
1616from ....components .source .field import ModeSource , ModeSpec
1717from ....components .source .time import GaussianPulse
@@ -62,16 +62,35 @@ class WavePort(AbstractTerminalPort, Box):
6262 description = "Definition of current integral used to compute current and the characteristic impedance." ,
6363 )
6464
65+ def _mode_voltage_coefficients (self , mode_data : ModeData ) -> FreqModeDataArray :
66+ """Calculates scaling coefficients to convert mode amplitudes
67+ to the total port voltage.
68+ """
69+ mode_data = mode_data ._isel (mode_index = [self .mode_index ])
70+ if self .voltage_integral is None :
71+ current_coeffs = self .current_integral .compute_current (mode_data )
72+ voltage_coeffs = 2 * np .abs (mode_data .flux ) / np .conj (current_coeffs )
73+ else :
74+ voltage_coeffs = self .voltage_integral .compute_voltage (mode_data )
75+ return voltage_coeffs .squeeze ()
76+
77+ def _mode_current_coefficients (self , mode_data : ModeData ) -> FreqModeDataArray :
78+ """Calculates scaling coefficients to convert mode amplitudes
79+ to the total port current.
80+ """
81+ mode_data = mode_data ._isel (mode_index = [self .mode_index ])
82+ if self .current_integral is None :
83+ voltage_coeffs = self .voltage_integral .compute_voltage (mode_data )
84+ current_coeffs = (2 * np .abs (mode_data .flux ) / voltage_coeffs ).conj ()
85+ else :
86+ current_coeffs = self .current_integral .compute_current (mode_data )
87+ return current_coeffs .squeeze ()
88+
6589 @cached_property
6690 def injection_axis (self ):
6791 """Injection axis of the port."""
6892 return self .size .index (0.0 )
6993
70- @cached_property
71- def _field_monitor_name (self ) -> str :
72- """Return the name of the :class:`.FieldMonitor` associated with this port."""
73- return f"{ self .name } _field"
74-
7594 @cached_property
7695 def _mode_monitor_name (self ) -> str :
7796 """Return the name of the :class:`.ModeMonitor` associated with this port."""
@@ -92,35 +111,24 @@ def to_source(self, source_time: GaussianPulse, snap_center: float = None) -> Mo
92111 name = self .name ,
93112 )
94113
95- def to_field_monitors (
114+ def to_monitors (
96115 self , freqs : FreqArray , snap_center : float = None , grid : Grid = None
97- ) -> list [FieldMonitor ]:
98- """Field monitor to compute port voltage and current."""
116+ ) -> list [ModeMonitor ]:
117+ """The wave port uses a :class:`.ModeMonitor` to compute the characteristic impedance
118+ and the port voltages and currents."""
99119 center = list (self .center )
100120 if snap_center :
101121 center [self .injection_axis ] = snap_center
102- field_mon = FieldMonitor (
103- center = center ,
104- size = self .size ,
105- freqs = freqs ,
106- name = self ._field_monitor_name ,
107- colocate = False ,
108- )
109- return [field_mon ]
110-
111- def to_mode_solver_monitor (self , freqs : FreqArray ) -> ModeSolverMonitor :
112- """Mode solver monitor to compute modes that will be used to
113- compute characteristic impedances."""
114- mode_mon = ModeSolverMonitor (
122+ mode_mon = ModeMonitor (
115123 center = self .center ,
116124 size = self .size ,
117125 freqs = freqs ,
118126 name = self ._mode_monitor_name ,
119127 colocate = False ,
120128 mode_spec = self .mode_spec ,
121- direction = self .direction ,
129+ store_fields_direction = self .direction ,
122130 )
123- return mode_mon
131+ return [ mode_mon ]
124132
125133 def to_mode_solver (self , simulation : Simulation , freqs : FreqArray ) -> ModeSolver :
126134 """Helper to create a :class:`.ModeSolver` instance."""
@@ -136,30 +144,43 @@ def to_mode_solver(self, simulation: Simulation, freqs: FreqArray) -> ModeSolver
136144
137145 def compute_voltage (self , sim_data : SimulationData ) -> FreqDataArray :
138146 """Helper to compute voltage across the port."""
139- field_monitor = sim_data [self ._field_monitor_name ]
140- return self .voltage_integral .compute_voltage (field_monitor )
147+ mode_data = sim_data [self ._mode_monitor_name ]
148+ voltage_coeffs = self ._mode_voltage_coefficients (mode_data )
149+ amps = mode_data .amps
150+ fwd_amps = amps .sel (direction = "+" ).squeeze ()
151+ bwd_amps = amps .sel (direction = "-" ).squeeze ()
152+ return voltage_coeffs * (fwd_amps + bwd_amps )
141153
142154 def compute_current (self , sim_data : SimulationData ) -> FreqDataArray :
143155 """Helper to compute current flowing through the port."""
144- field_monitor = sim_data [self ._field_monitor_name ]
145- return self .current_integral .compute_current (field_monitor )
156+ mode_data = sim_data [self ._mode_monitor_name ]
157+ current_coeffs = self ._mode_current_coefficients (mode_data )
158+ amps = mode_data .amps
159+ fwd_amps = amps .sel (direction = "+" ).squeeze ()
160+ bwd_amps = amps .sel (direction = "-" ).squeeze ()
161+ # In ModeData, fwd_amps and bwd_amps are not relative to
162+ # the direction fields are stored
163+ sign = 1.0
164+ if self .direction == "-" :
165+ sign = - 1.0
166+ return sign * current_coeffs * (fwd_amps - bwd_amps )
146167
147168 def compute_port_impedance (
148- self , sim_mode_data : Union [SimulationData , ModeSolverData ]
169+ self , sim_mode_data : Union [SimulationData , ModeData ]
149170 ) -> FreqModeDataArray :
150171 """Helper to compute impedance of port. The port impedance is computed from the
151172 transmission line mode, which should be TEM or at least quasi-TEM."""
152173 impedance_calc = ImpedanceCalculator (
153174 voltage_integral = self .voltage_integral , current_integral = self .current_integral
154175 )
155176 if isinstance (sim_mode_data , SimulationData ):
156- mode_solver_data = sim_mode_data [self ._mode_monitor_name ]
177+ mode_data = sim_mode_data [self ._mode_monitor_name ]
157178 else :
158- mode_solver_data = sim_mode_data
179+ mode_data = sim_mode_data
159180
160181 # Filter out unwanted modes to reduce impedance computation effort
161- mode_solver_data = mode_solver_data ._isel (mode_index = [self .mode_index ])
162- impedance_array = impedance_calc .compute_impedance (mode_solver_data )
182+ mode_data = mode_data ._isel (mode_index = [self .mode_index ])
183+ impedance_array = impedance_calc .compute_impedance (mode_data )
163184 return impedance_array
164185
165186 @staticmethod
@@ -185,10 +206,11 @@ def _validate_path_integrals_within_port(cls, val, values):
185206 return val
186207
187208 @pd .validator ("current_integral" , always = True )
209+ @skip_if_fields_missing (["voltage_integral" ])
188210 def _check_voltage_or_current (cls , val , values ):
189211 """Raise validation error if both ``voltage_integral`` and ``current_integral``
190212 were not provided."""
191- if not values .get ("voltage_integral" ) and not val :
213+ if values .get ("voltage_integral" ) is None and val is None :
192214 raise ValidationError (
193215 "At least one of 'voltage_integral' or 'current_integral' must be provided."
194216 )
0 commit comments