1818from .media import MediaPhantom
1919from .passport import CascadeStage , StructuralPassport
2020from .sources import MultipolarOscillator
21+ from .sigma_guard import SigmaGuard
2122from ..cognition .base import AbstractMind
22- from ..physics .multipolar_wave import MultiConjugateFunction
23+ from ..physics .multipolar_wave import MultiConjugateFunction , WaveMetadata
2324
2425
2526class MultipolarMicrophone :
@@ -121,6 +122,7 @@ def __init__(
121122 oscillator : MultipolarOscillator ,
122123 * ,
123124 key : DynamicKey | None = None ,
125+ sigma_guard : SigmaGuard | None = None ,
124126 name : str | None = None ,
125127 mind : AbstractMind | None = None ,
126128 loka : Loka | str | None = None ,
@@ -130,6 +132,7 @@ def __init__(
130132 super ().__init__ (mind = mind , loka = loka , default_rank = desired_rank )
131133 self .oscillator = oscillator
132134 self .key = key
135+ self .sigma_guard = sigma_guard
133136 self .name = name or "MultipolarReceiver"
134137 if self .oscillator .get_polarity () != self .rank :
135138 self .oscillator .set_polarity (self .rank )
@@ -157,6 +160,8 @@ def _build_passport(self) -> StructuralPassport:
157160 notes = ["Receiver mirrors the transmitter's Sigma guard to close the cascade." ]
158161 if self .key is not None :
159162 notes .append ("Dynamic key retunes polarity and frequency before each capture." )
163+ if self .sigma_guard is not None :
164+ notes .append ("SigmaGuard purification is active before decoding." )
160165 return StructuralPassport (
161166 device_name = self .name ,
162167 cascade = cascade ,
@@ -182,44 +187,104 @@ def set_polarity(self, n_poles: int) -> None:
182187 self ._coder = PolarCoder (self ._n_polarities , loka = self .loka )
183188 self ._sync_passport ()
184189
185- def is_compatible (self , wave : MultiConjugateFunction , * , tol_poles : int = 0 , tol_freq : float | None = None ) -> bool :
190+ def _expected_loka_names (self ) -> set [str ]:
191+ return {self .loka .name , self .oscillator .loka .name }
192+
193+ def _expected_polarity_names (self ) -> list [str ]:
194+ return [p .name for p in self .loka .polarities ]
195+
196+ def _metadata_is_compatible (self , wave : MultiConjugateFunction ) -> bool :
197+ meta = wave .metadata
198+ if meta is None :
199+ return True
200+ if meta .loka_name not in self ._expected_loka_names ():
201+ return False
202+ if len (meta .polarity_names ) != self .rank :
203+ return False
204+ return list (meta .polarity_names ) == self ._expected_polarity_names ()
205+
206+ def _purify_wave (self , wave : MultiConjugateFunction ) -> MultiConjugateFunction :
207+ if self .sigma_guard is None :
208+ return wave .copy ()
209+ mv = wave .to_multipolar_value (self .loka )
210+ purified = self .sigma_guard .apply_nx (mv , sections = self .sigma_guard .sections )[- 1 ]
211+ amplitudes = np .asarray (
212+ [purified .coefficients .get (p , 0.0 ) for p in purified .loka .polarities ],
213+ dtype = np .complex128 ,
214+ )
215+ frequency_hz = self .oscillator .working_frequency
216+ if wave .metadata is not None and wave .metadata .frequency_hz is not None :
217+ frequency_hz = wave .metadata .frequency_hz
218+ metadata = WaveMetadata .from_amplitudes (
219+ amplitudes ,
220+ loka_name = purified .loka .name ,
221+ polarity_names = [p .name for p in purified .loka .polarities ],
222+ frequency_hz = frequency_hz ,
223+ )
224+ return MultiConjugateFunction (amplitudes , n_conjugates = self .rank , metadata = metadata )
225+
226+ def is_compatible (
227+ self ,
228+ wave : MultiConjugateFunction ,
229+ * ,
230+ tol_poles : int = 0 ,
231+ tol_freq : float | None = None ,
232+ require_frequency : bool = False ,
233+ ) -> bool :
186234 poles_ok = abs (wave .n_conjugates - self .rank ) <= tol_poles
187235 if not poles_ok :
188236 return False
237+ if not self ._metadata_is_compatible (wave ):
238+ return False
189239 # Frequency check: only compare against explicit frequency metadata if requested
190- if tol_freq is None or wave . metadata is None :
240+ if tol_freq is None :
191241 return poles_ok
242+ if wave .metadata is None :
243+ return False if require_frequency else poles_ok
192244 freq_meta = wave .metadata .frequency_hz
193245 if freq_meta is None :
194- return poles_ok
246+ return False if require_frequency else poles_ok
195247 return abs (freq_meta - self .oscillator .working_frequency ) <= tol_freq
196248
197- def receive (self , wave : MultiConjugateFunction ) -> bool :
249+ def receive (self , wave : MultiConjugateFunction , * , purify : bool | None = None ) -> bool :
198250 if self .key is not None :
199251 n_pol , freq = self .key .next ()
200252 self .set_polarity (n_pol )
201253 self .oscillator .set_frequency (freq )
202254 self .structural_passport .record_metric ("configured_polarity" , float (self .rank ))
203- tol_freq = 1.0 if self .key is None else None
204- if not self .is_compatible (wave , tol_freq = tol_freq ):
255+ self .structural_passport .record_metric ("working_frequency_hz" , self .oscillator .working_frequency )
256+ tol_freq = 1.0
257+ if not self .is_compatible (wave , tol_freq = tol_freq , require_frequency = self .key is not None ):
205258 return False
206- self ._last_wave = wave
259+ should_purify = self .sigma_guard is not None if purify is None else bool (purify )
260+ self ._last_wave = self ._purify_wave (wave ) if should_purify else wave .copy ()
261+ if self ._last_wave .metadata is not None :
262+ self .structural_passport .record_metric ("last_sigma_norm" , self ._last_wave .metadata .sigma_norm )
207263 return True
208264
265+ def receive_and_purify (self , wave : MultiConjugateFunction ) -> bool :
266+ return self .receive (wave , purify = True )
267+
209268 def last_wave (self ) -> Optional [MultiConjugateFunction ]:
210269 return self ._last_wave
211270
212- def demodulate (self , wave : Optional [MultiConjugateFunction ] = None ) -> List [int ]:
271+ def demodulate (self , wave : Optional [MultiConjugateFunction ] = None , * , purify : bool | None = None ) -> List [int ]:
213272 target = wave or self ._last_wave
214273 if target is None :
215274 return []
275+ should_purify = self .sigma_guard is not None if purify is None else bool (purify )
276+ if should_purify :
277+ target = self ._purify_wave (target )
216278 amps = target .amplitudes
217279 if amps .size == 0 :
218280 return []
219281 idx = int (np .argmax (np .abs (amps )))
220282 self .structural_passport .record_metric ("last_demodulated" , float (idx ))
221283 return [idx ]
222284
285+ def demodulate_clean (self , wave : Optional [MultiConjugateFunction ] = None ) -> List [int ]:
286+ return self .demodulate (wave , purify = True )
287+
223288 def describe_structure (self ) -> dict :
224289 self ._sync_passport ()
225290 return self .structural_passport .to_dict ()
0 commit comments