diff --git a/lisainstrument/instrument.py b/lisainstrument/instrument.py index 30ee64f3a680082691f6539362ecfa9394a57f5a..50751ab7c8d9a0583d5f94b69f651e0b0d258d9c 100755 --- a/lisainstrument/instrument.py +++ b/lisainstrument/instrument.py @@ -75,8 +75,8 @@ class Instrument: glitches=None, # Laser locking and frequency plan lock='N1-12', fplan='static', - laser_asds=28.2, central_freq=2.816E14, - offset_freqs='default', + laser_asds=28.2, laser_shape='white+infrared', + central_freq=2.816E14, offset_freqs='default', # Laser phase modulation modulation_asds='default', modulation_freqs='default', tdir_tone=None, # Clocks @@ -127,6 +127,7 @@ class Instrument: fplan: path to frequency-plan file, dictionary of locking beatnote frequencies [Hz], or 'static' for a default set of constant locking beatnote frequencies laser_asds: dictionary of amplitude spectral densities for laser noise [Hz/sqrt(Hz)] + laser_shape: laser noise spectral shape, either 'white' or 'white+infrared' central_freq: laser central frequency from which all offsets are computed [Hz] offset_freqs: dictionary of laser frequency offsets for unlocked lasers [Hz], defined with respect to :attr:`lisainstrument.Instrument.central_freq`, @@ -248,6 +249,10 @@ class Instrument: # Laser and modulation noise self.laser_asds = ForEachMOSA(laser_asds) + if laser_shape not in ['white', 'white+infrared']: + raise ValueError(f"invalid laser noise spectral shape '{laser_shape}'") + self.laser_shape = laser_shape + if modulation_asds == 'default': # Default based on the performance model, with 10x amplification for right-sided # MOSAs, to account for errors in the frequency distribution system @@ -1508,7 +1513,11 @@ class Instrument: mosa: laser index """ logger.info("Generating laser noise for laser %s", mosa) - self.laser_noises[mosa] = noises.laser(self.physics_fs, self.physics_size, self.laser_asds[mosa]) + self.laser_noises[mosa] = noises.laser( + fs=self.physics_fs, + size=self.physics_size, + asd=self.laser_asds[mosa], + shape=self.laser_shape) logger.debug("Computing carrier offsets for primary local beam %s", mosa) self.local_carrier_offsets[mosa] = self.offset_freqs[mosa] diff --git a/lisainstrument/noises.py b/lisainstrument/noises.py index ca3973abac073cb4d5703de27bf177c7d36e834d..5507df9e27dd974def3386a075dc94b57410d26d 100644 --- a/lisainstrument/noises.py +++ b/lisainstrument/noises.py @@ -134,7 +134,7 @@ def infrared(fs, size, asd): return np.cumsum(red_noise) * (2 * pi / fs) -def laser(fs, size, asd=30, fknee=2E-3): +def laser(fs, size, asd=30, fknee=2E-3, shape='white+infrared'): """Generate laser noise [Hz]. This is a white noise with an infrared relaxation towards low frequencies, @@ -143,13 +143,26 @@ def laser(fs, size, asd=30, fknee=2E-3): S_p(f) = asd^2 [ 1 + (fknee / f)^4 ] = asd^2 + asd^2 fknee^4 / f^4. + The low-frequency part (infrared relaxation) can be disabled, in which + case the noise shape becomes + + S_p(f) = asd^2. + Args: asd: amplitude spectral density [Hz/sqrt(Hz)] fknee: cutoff frequency [Hz] + shape: spectral shape, either 'white' or 'white+infrared' """ - logger.debug("Generating laser noise (fs=%s Hz, size=%s, asd=%s Hz/sqrt(Hz), fknee=%s Hz)", - fs, size, asd, fknee) - return white(fs, size, asd) + infrared(fs, size, asd * fknee**2) + logger.debug( + "Generating laser noise (fs=%s Hz, size=%s, asd=%s " + "Hz/sqrt(Hz), fknee=%s Hz, shape=%s)", + fs, size, asd, fknee, shape) + + if shape == 'white': + return white(fs, size, asd) + if shape == 'white+infrared': + return white(fs, size, asd) + infrared(fs, size, asd * fknee**2) + raise ValueError(f"invalid laser noise spectral shape '{shape}'") def clock(fs, size, asd=6.32E-14):