From 877f4c7c28784087ff6e6a1c9d1e57009b9bad58 Mon Sep 17 00:00:00 2001
From: Christopher Wipf <wipf@ligo.mit.edu>
Date: Mon, 20 Aug 2018 23:52:41 -0700
Subject: [PATCH] coatingthermal: update for field penetration and
 freq-dependent loss

---
 gwinc/noise/coatingthermal.py | 247 ++++++++++++++++++++--------------
 1 file changed, 146 insertions(+), 101 deletions(-)

diff --git a/gwinc/noise/coatingthermal.py b/gwinc/noise/coatingthermal.py
index 2c579787..52779635 100644
--- a/gwinc/noise/coatingthermal.py
+++ b/gwinc/noise/coatingthermal.py
@@ -39,79 +39,106 @@ def getCoatBrownian(f, ifo, wBeam, dOpt):
 
     The layers are assumed to be alernating low-n high-n layers, with
     low-n first.
-    
+
     f = frequency vector in Hz
     ifo = parameter struct from IFOmodel.m
-    
+
     opticName = name of the Optic struct to use for wBeam and dOpt
-    wBeam = ifoArg.Optics.(opticName).BeamRadius
-    dOpt = ifoArg.Optics.(opticName).CoatLayerOpticalThickness
-    
+        wBeam = ifoArg.Optics.(opticName).BeamRadius
+         dOpt = ifoArg.Optics.(opticName).CoatLayerOpticalThickness
+
     wBeam = beam radius (at 1 / e^2 power)
-    dOpt = coating layer thickness vector (Nlayer x 1)
-         = the optical thickness, normalized by lambda, of each coating layer.
+     dOpt = coating layer thickness vector (Nlayer x 1)
+          = the optical thickness, normalized by lambda, of each coating layer.
 
     SbrZ = Brownian noise spectra for one mirror in m^2 / Hz
-    
-    adapted from bench62
-    based on Harry et al., Class Quant Grav 24 (2007) 405-415
 
     """
+    # extract substructures
+    sub = ifo.Materials.Substrate
+    coat = ifo.Materials.Coating
 
     # Constants
-    subTemp = ifo.Materials.Substrate.Temp
-    kBT    = scipy.constants.k * subTemp
+    kBT = scipy.constants.k * sub.Temp
     lambda_ = ifo.Laser.Wavelength
 
-    Ysub     = ifo.Materials.Substrate.MirrorY
-    sigmasub = ifo.Materials.Substrate.MirrorSigma
-    
-    Yhighn     = ifo.Materials.Coating.Yhighn
-    sigmahighn = ifo.Materials.Coating.Sigmahighn
-    phihighn   = ifo.Materials.Coating.Phihighn
-    nH         = ifo.Materials.Coating.Indexhighn
-    
-    Ylown     = ifo.Materials.Coating.Ylown
-    sigmalown = ifo.Materials.Coating.Sigmalown
-    philown   = ifo.Materials.Coating.Philown
-    nL        = ifo.Materials.Coating.Indexlown
-
-    # compute thickness of each material in the coating
-    dlown  = sum(dOpt[::2])  * lambda_ / nL
-    dhighn = sum(dOpt[1::2]) * lambda_ / nH
-    dCoat  = dlown + dhighn
-    
-    # for debugging, this is a rough but direct estimate
-    Llown = dlown * philown
-    Lhighn = dhighn * phihighn
-    Lsum = Llown + Lhighn
-    Lall = [Lsum, Llown, Lhighn]
-    
-    ################# this part is directly from bench62 #################
-    Yperp = dCoat/(dhighn/Yhighn+dlown/Ylown)
-    phiperp = Yperp/dCoat*(dlown*philown/Ylown + dhighn*phihighn/Yhighn)
-    Ypara = 1/dCoat*(Yhighn*dhighn + Ylown*dlown)
-    phipara = 1/(dCoat*Ypara)*(Ylown*philown*dlown + Yhighn*phihighn*dhighn)
-    
-    # This is a kludge, the real formula is very complicted but this
-    # average works really well
-    sigma1 = 1/2*(sigmahighn+sigmalown)
+    # substrate properties
+    Ysub = sub.MirrorY         # Young's Modulous
+    pratsub = sub.MirrorSigma  # Poisson Ratio
+
+    # coating properties
+    Yhighn = coat.Yhighn
+    sigmahighn = coat.Sigmahighn
+    phihighn = coat.Phihighn
+    nH = coat.Indexhighn
+
+    Ylown = coat.Ylown
+    sigmalown = coat.Sigmalown
+    philown = coat.Philown
+    nL = coat.Indexlown
+
+    # make vectors of material properties
+    nN = zeros(dOpt.size)
+    yN = zeros(dOpt.size)
+    pratN = zeros(dOpt.size)
+    phiN = zeros(dOpt.size)
+
+    # make simple alternating structure (low-index, high-index doublets)
+    # (adapted from the more general calculation in
+    #  Yam, W., Gras, S., & Evans, M. Multimaterial coatings with reduced thermal noise.
+    #  Physical Review D, 91(4), 042002.  (2015).
+    #  http://doi.org/10.1103/PhysRevD.91.042002 )
+    nN[::2] = nL
+    nN[1::2] = nH
+
+    yN[::2] = Ylown
+    yN[1::2] = Yhighn
+
+    pratN[::2] = sigmalown
+    pratN[1::2] = sigmahighn
+
+    phiN[::2] = philown
+    phiN[1::2] = phihighn
+
+    # geometrical thickness of each layer and total
+    dGeo = lambda_ * dOpt / nN
+    dCoat = sum(dGeo)
+
+    ###################################
+    # Brownian
+    ###################################
+    # coating reflectivity
+    dcdp = getCoatRefl(ifo, dOpt)[1]
 
-    # This is exact
-    sigma2 = (sigmahighn*Yhighn*dhighn+sigmalown*Ylown*dlown)/(Yhighn*dhighn+Ylown*dlown)
+    # Z-dir (1 => away from the substrate, -1 => into the substrate)
+    zdir = -1
+    dcdp_z = zdir * dcdp  # z-dir only matters here
 
-    # Brownian contribution to coating thermal noise, low Poisson ratio limit
-    # cITM = dITM/(pi*wITM^2)*(Ypara/Ysub^2*phipara+phiperp/Yperp);
-    # cETM = dCoat/(pi*wETM^2)*(Ypara/Ysub^2*phipara+phiperp/Yperp);
+    # layer contrubutions (b_j in PhysRevD.91.042002)
+    brLayer = ( (1 - nN * dcdp_z / 2)**2 * (Ysub / yN) +
+                (1 - pratsub - 2 * pratsub**2)**2 * yN /
+                ((1 + pratN)**2 * (1 - 2 * pratN) * Ysub) )/ (1 - pratN)
 
-    # Brownian contribution to coating thermal noise, full formula
-    cbnoise = dCoat * (1-sigmasub**2)/(pi*wBeam**2) * (
-        (1/(Yperp*(1-sigmasub**2)) - 2*sigma2**2*Ypara/(Yperp**2*(1-sigmasub**2)*(1-sigma1)))*phiperp
-        + Ypara*sigma2*(1-2*sigmasub)/(Yperp*Ysub*(1-sigma1)*(1-sigmasub))*(phipara-phiperp)
-        + Ypara*(1+sigmasub)*(1-2*sigmasub)**2/(Ysub**2*(1-sigma1**2)*(1-sigmasub))*phipara)
+    # sum them up for total
+    w = 2 * pi * f
 
-    # noise power spectrum
-    SbrZ = 4 * kBT * cbnoise / (2 * pi * f)
+    is_low_slope = 'Philown_slope' in coat
+    is_high_slope = 'Phihighn_slope' in coat
+
+    if (not is_low_slope) and (not is_high_slope):
+        # this is the old code for frequency independent loss
+        SbrZ = (4 * kBT / (pi * wBeam**2 * w)) * \
+               sum(dGeo * brLayer * phiN) * (1 - pratsub - 2 * pratsub**2) / Ysub
+    else:
+        SbrZ = (4 * kBT / (pi * wBeam**2 * w)) * \
+               (1 - pratsub - 2 * pratsub**2) / Ysub
+
+        SbrZ = SbrZ * (sum(dGeo[::2] * brLayer[::2] * phiN[::2]) * (f / 100)**(coat.Philown_slope) +
+                       sum(dGeo[1::2] * brLayer[1::2] * phiN[1::2]) * (f / 100)**(coat.Phihighn_slope))
+
+    # for the record: evaluate summation in eq 1 of PhysRevD.91.042002
+    # normalized by total coating thickness to make it unitless
+    cCoat = sum(dGeo * brLayer * phiN) / dCoat
 
     return SbrZ
 
@@ -471,41 +498,14 @@ def getCoatTOPhase(nIn, nOut, nLayer, dOpt, aLayer, bLayer, sLayer):
     (see also T080101)
 
     """
-    # vector of all refractive indexes
-    nAll = np.concatenate(([nIn], nLayer, [nOut]))
-  
-    # reflectivity of each interface
-    r = (nAll[:-1] - nAll[1:]) / (nAll[:-1] + nAll[1:])
-  
-    # combine reflectivities
-    rbar = zeros(r.shape, dtype=complex)
-    ephi = zeros(r.shape, dtype=complex)
-
-    ephi[-1] = exp(-4j * pi * dOpt[-1])
-    rbar[-1] = ephi[-1] * r[-1]
-    for n in range(len(dOpt), 0, -1):
-        # one-way phase in this layer
-        if n > 1:
-            ephi[n-1] = exp(-4j * pi * dOpt[n - 2])
-        else:
-            ephi[n-1] = 1
-    
-        # accumulate reflecitivity
-        rbar[n-1] = ephi[n-1] * (r[n-1] + rbar[n]) / (1 + r[n-1] * rbar[n])
-  
-    # reflectivity derivatives
-    dr_dphi = zeros(len(dOpt), dtype=complex)
-    for n in range(len(dOpt), 0, -1):
-        dr_dphi[n-1] = -1j * rbar[n]
-        for m in range(n, 0, -1):
-            dr_dphi[n-1] = dr_dphi[n-1] * ephi[m-1] * (1 - r[m-1]**2) / (1 + r[m-1] * rbar[m])**2
-  
+    # coating reflectivity calc
+    rCoat, dcdp = getCoatRefl2(nIn, nOut, nLayer, dOpt)[:2]
 
     # geometrical distances
     dGeo = dOpt / nLayer
 
     # phase derivatives
-    dphi_dd = 4 * pi * imag(dr_dphi / rbar[0])
+    dphi_dd = 4 * pi * dcdp
 
     # thermo-refractive coupling
     dphi_TR = sum(dphi_dd * (bLayer + sLayer * nLayer) * dGeo)
@@ -515,9 +515,6 @@ def getCoatTOPhase(nIn, nOut, nLayer, dOpt, aLayer, bLayer, sLayer):
 
     # total
     dphi_dT = dphi_TR + dphi_TE
-  
-    # coating reflectivity
-    rCoat = rbar[0]
 
     return dphi_dT, dphi_TE, dphi_TR, rCoat
 
@@ -647,7 +644,7 @@ def getCoatDopt(ifo, T, dL, dCap=0.5):
         T = zeros(N)
         for n in range(N):
             dOpt[-1] = dTweak[n]
-            r = getCoatRefl(ifo, dOpt)
+            r = getCoatRefl(ifo, dOpt)[0]
             T[n] = 1 - abs(r**2)
 
         return T
@@ -747,7 +744,12 @@ def getCoatRefl(ifo, dOpt):
     dOpt   = optical thickness / lambda of each layer
            = geometrical thickness * refractive index / lambda
 
-    This code is taken from ewa/multidiel1.m
+    rCoat = amplitude reflectivity of coating (complex) = rbar(0)
+    dcdp = d reflection phase / d round-trip layer phase
+    rbar = amplitude reflectivity of coating from this layer down
+    r = amplitude reflectivity of this interface (r(1) is nIn to nLayer(1))
+
+    cf ewa/multidiel1.m
 
     """
     pS = ifo.Materials.Substrate
@@ -766,17 +768,60 @@ def getCoatRefl(ifo, dOpt):
     nAll[2::2] = nH
     nAll[-1] = nS # substrate output
 
+    # backend calculation
+    return getCoatRefl2(nAll[0], nAll[-1], nAll[1:-1], dOpt)
+
+
+def getCoatRefl2(nIn, nOut, nLayer, dOpt):
+    """Coating reflection and phase derivatives
+
+    nIn = refractive index of input medium (e.g., vacuum = 1)
+    nOut = refractive index of output medium (e.g., SiO2 = 1.45231 @ 1064nm)
+    nLayer = refractive index of each layer, ordered input to output (N x 1)
+    dOpt   = optical thickness / lambda of each layer
+           = geometrical thickness * refractive index / lambda
+
+    rCoat = amplitude reflectivity of coating (complex) = rbar(0)
+    dcdp = d reflection phase / d round-trip layer phase
+    rbar = amplitude reflectivity of coating from this layer down
+    r = amplitude reflectivity of this interface (r(1) is nIn to nLayer(1))
+
+    see GWINC getCoatRefl and getCoatTOPhase for more information
+    (see also T080101)
+
+    """
+
+    # Z-dir (1 => away from the substrate, -1 => into the substrate)
+    zdir = 1
+
+    # vector of all refractive indexs
+    nAll = np.concatenate(([nIn], nLayer, [nOut]))
+
     # reflectivity of each interface
-    rInterface = (nAll[:-1] - nAll[1:]) / (nAll[:-1] + nAll[1:])
+    r = (nAll[:-1] - nAll[1:]) / (nAll[:-1] + nAll[1:])
+
+    # combine reflectivities
+    rbar = zeros(r.size, dtype=complex)
+    ephi = zeros(r.size, dtype=complex)
 
-    # combine layers as small FP cavities, starting with last reflectivity
-    rCoat = rInterface[-1]
-    for n in reversed(range(dOpt.size)):
-        # one-way phase in this layer
-        phi = 2 * pi * dOpt[n]
+    # round-trip phase in each layer
+    ephi[0] = 1
+    ephi[1:] = exp(4j * zdir * pi * dOpt)
 
+    rbar[-1] = ephi[-1] * r[-1]
+    for n in range(dOpt.size, 0, -1):
         # accumulate reflectivity
-        rCoat = rCoat * exp(-2j * phi)
-        rCoat = (rInterface[n] + rCoat) / (1 + rInterface[n] * rCoat)
+        rbar[n-1] = ephi[n-1] * (r[n-1] + rbar[n]) / (1 + r[n-1] * rbar[n])
+
+    # reflectivity derivatives
+    dr_dphi = ephi[:-1] * (1 - r[:-1]**2) / (1 + r[:-1] * rbar[1:])**2
+    dr_dphi = (1j * zdir * rbar[1:]) * np.multiply.accumulate(dr_dphi)
+
+    # shift rbar index
+    rCoat = rbar[0]
+    rbar = rbar[1:]
+
+    # phase derivatives
+    dcdp = -imag(dr_dphi / rCoat)  ### Where did this minus come from???
 
-    return rCoat
+    return rCoat, dcdp, rbar, r
-- 
GitLab