diff --git a/README.md b/README.md
index bb04aa78fa18890846e377ab7bf34db91e3ff6a9..af9fdbee01bf17541ae87fe9be4690cb75a02570 100644
--- a/README.md
+++ b/README.md
@@ -17,8 +17,7 @@ description is loaded, the noise budget can be calculated and plotted:
 >>> import numpy as np
 >>> freq = np.logspace(1, 3, 1000)
 >>> Budget = gwinc.load_budget('aLIGO')
->>> ifo = gwinc.precompIFO(freq, Budget.ifo)
->>> traces = Budget(freq, ifo=ifo).run()
+>>> traces = Budget(freq).run()
 >>> fig = gwinc.plot_noise(freq, traces)
 >>> fig.show()
 ```
diff --git a/gwinc/__init__.py b/gwinc/__init__.py
index 3347d489b628bd714fda268127e032d11db8031b..efa72a1cd02b15dd4279a56261d7668d79a3e054 100644
--- a/gwinc/__init__.py
+++ b/gwinc/__init__.py
@@ -6,7 +6,6 @@ import numpy as np
 
 from .ifo import available_ifos
 from .struct import Struct
-from .precomp import precompIFO
 from .plot import plot_noise
 from .io import load_hdf5, save_hdf5
 
@@ -112,7 +111,6 @@ def gwinc(freq, ifo, source=None, plot=False, PRfixed=True):
     # FIXME: how do we allow adding arbitrary addtional noise sources
     # from just ifo description, without having to specify full budget
     Budget = load_budget('aLIGO')
-    ifo = precompIFO(freq, ifo, PRfixed)
     traces = Budget(freq, ifo=ifo).run()
     plot_style = getattr(Budget, 'plot_style', {})
 
diff --git a/gwinc/__main__.py b/gwinc/__main__.py
index d6b220f38d919c0f1f76e288dbf1c11ca3b746a2..beb810e52a1ddbd781f3e9e50e77dfefb95ff5f7 100644
--- a/gwinc/__main__.py
+++ b/gwinc/__main__.py
@@ -10,7 +10,6 @@ logging.basicConfig(format='%(message)s',
                     level=os.getenv('LOG_LEVEL', logging.INFO))
 
 from . import available_ifos, load_budget, plot_noise
-from .precomp import precompIFO
 from . import io
 
 ##################################################
@@ -175,9 +174,6 @@ def main():
     # main calculations
 
     if not traces:
-        if ifo:
-            logging.info("precomputing ifo...")
-            ifo = precompIFO(freq, ifo)
         logging.info("calculating budget...")
         traces = Budget(freq=freq, ifo=ifo).run()
 
diff --git a/gwinc/ifo/noises.py b/gwinc/ifo/noises.py
index 5c0b901596b6049e057773a8cf6918ab7e946abb..56e06b1adc577508966f2d3db03f3a08675432f4 100644
--- a/gwinc/ifo/noises.py
+++ b/gwinc/ifo/noises.py
@@ -2,11 +2,95 @@ import numpy as np
 from numpy import pi, sin, exp, sqrt
 
 from .. import const
+from ..struct import Struct
 from .. import nb
 from .. import noise
+from .. import suspension
 
 ##################################################
 
+def coating_thickness(ifo, optic):
+    optic = ifo.Optics.get(optic)
+    if 'CoatLayerOpticalThickness' in optic:
+        return optic['CoatLayerOpticalThickness']
+    T = optic.Transmittance
+    dL = optic.CoatingThicknessLown
+    dCap = optic.CoatingThicknessCap
+    return noise.coatingthermal.getCoatDopt(ifo.Materials, T, dL, dCap=dCap)
+
+
+def arm_cavity(ifo):
+    L = ifo.Infrastructure.Length
+
+    g1 = 1 - L / ifo.Optics.Curvature.ITM
+    g2 = 1 - L / ifo.Optics.Curvature.ETM
+    gcav = sqrt(g1 * g2 * (1 - g1 * g2))
+    gden = g1 - 2 * g1 * g2 + g2
+
+    if (g1 * g2 * (1 - g1 * g2)) <= 0:
+        raise Exception('Unstable arm cavity g-factors.  Change ifo.Optics.Curvature')
+    elif gcav < 1e-3:
+        logging.warning('Nearly unstable arm cavity g-factors.  Reconsider ifo.Optics.Curvature')
+
+    ws = sqrt(L * ifo.Laser.Wavelength / pi)
+    w1 = ws * sqrt(abs(g2) / gcav)
+    w2 = ws * sqrt(abs(g1) / gcav)
+
+    # waist size
+    w0 = ws * sqrt(gcav / abs(gden))
+    zr = pi * w0**2 / ifo.Laser.Wavelength
+    z1 = L * g2 * (1 - g1) / gden
+    z2 = L * g1 * (1 - g2) / gden
+
+    # waist, input, output
+    return w0, w1, w2
+
+
+def ifo_power(ifo, PRfixed=True):
+    """Compute power on beamsplitter, finesse, and power recycling factor.
+
+    """
+    c = const.c
+    pin = ifo.Laser.Power
+    t1 = sqrt(ifo.Optics.ITM.Transmittance)
+    r1 = sqrt(1 - ifo.Optics.ITM.Transmittance)
+    r2 = sqrt(1 - ifo.Optics.ETM.Transmittance)
+    t5 = sqrt(ifo.Optics.PRM.Transmittance)
+    r5 = sqrt(1 - ifo.Optics.PRM.Transmittance)
+    loss = ifo.Optics.Loss  # single TM loss
+    bsloss = ifo.Optics.BSLoss
+    acoat = ifo.Optics.ITM.CoatingAbsorption
+    pcrit = ifo.Optics.pcrit
+
+    # Finesse, effective number of bounces in cavity, power recycling factor
+    finesse = 2*pi / (t1**2 + 2*loss)  # arm cavity finesse
+    neff    = 2 * finesse / pi
+
+    # Arm cavity reflectivity with finite loss
+    garm = t1 / (1 - r1*r2*sqrt(1-2*loss))  # amplitude gain wrt input field
+    rarm = r1 - t1 * r2 * sqrt(1-2*loss) * garm
+
+    if PRfixed:
+        Tpr = ifo.Optics.PRM.Transmittance  # use given value
+    else:
+        Tpr = 1-(rarm*sqrt(1-bsloss))**2  # optimal recycling mirror transmission
+        t5 = sqrt(Tpr)
+        r5 = sqrt(1 - Tpr)
+    prfactor = t5**2 / (1 + r5 * rarm * sqrt(1-bsloss))**2
+
+    pbs  = pin * prfactor  # BS power from input power
+    parm = pbs * garm**2 / 2  # arm power from BS power
+
+    thickness = ifo.Optics.ITM.get('Thickness', ifo.Materials.MassThickness)
+    asub = 1.3 * 2 * thickness * ifo.Optics.SubstrateAbsorption
+    pbsl = 2 *pcrit / (asub+acoat*neff)  # bs power limited by thermal lensing
+
+    if pbs > pbsl:
+        logging.warning('P_BS exceeds BS Thermal limit!')
+
+    return pbs, parm, finesse, prfactor, Tpr
+
+
 def dhdl(f, armlen):
     """Strain to length conversion for noise power spetra
 
@@ -35,6 +119,49 @@ def dhdl(f, armlen):
 
 ##################################################
 
+def precomp_mirror(f, ifo):
+    ifo.Materials.MirrorVolume = \
+        pi * ifo.Materials.MassRadius**2 \
+        * ifo.Materials.MassThickness
+    ifo.Materials.MirrorMass = \
+        ifo.Materials.MirrorVolume \
+        * ifo.Materials.Substrate.MassDensity
+
+
+def precomp_gwinc(f, ifo):
+    ifo.gwinc = Struct()
+    pbs, parm, finesse, prfactor, Tpr = ifo_power(ifo)
+    ifo.gwinc.pbs = pbs
+    ifo.gwinc.parm = parm
+    ifo.gwinc.finesse = finesse
+    ifo.gwinc.prfactor = prfactor
+
+
+def precomp_suspension(f, ifo):
+    if 'VHCoupling' not in ifo.Suspension:
+        ifo.Suspension.VHCoupling = Struct()
+        ifo.Suspension.VHCoupling.theta = ifo.Infrastructure.Length / const.R_earth
+
+    if ifo.Suspension.Type == 'BQuad':
+        material = 'Silicon'
+    else:
+        material = 'Silica'
+    hForce, vForce, hTable, vTable = suspension.suspQuad(f, ifo.Suspension, material)
+    try:
+        # full TF (conventional)
+        ifo.Suspension.hForce = hForce.fullylossy
+        ifo.Suspension.vForce = vForce.fullylossy
+        # TFs with each stage lossy
+        ifo.Suspension.hForce_singlylossy = hForce.singlylossy
+        ifo.Suspension.vForce_singlylossy = vForce.singlylossy
+    except:
+        ifo.Suspension.hForce = hForce
+        ifo.Suspension.vForce = vForce
+    ifo.Suspension.hTable = hTable
+    ifo.Suspension.vTable = vTable
+
+##################################################
+
 class Strain(nb.Calibration):
     def calc(self):
         dhdl_sqr, sinc_sqr = dhdl(self.freq, self.ifo.Infrastructure.Length)
@@ -52,6 +179,8 @@ class QuantumVacuum(nb.Noise):
     )
 
     def calc(self):
+        precomp_mirror(self.freq, self.ifo)
+        precomp_gwinc(self.freq, self.ifo)
         return noise.quantum.shotrad(self.freq, self.ifo)
 
 
@@ -65,6 +194,7 @@ class Seismic(nb.Noise):
     )
 
     def calc(self):
+        precomp_suspension(self.freq, self.ifo)
         if 'PlatformMotion' in self.ifo.Seismic:
             if self.ifo.Seismic.PlatformMotion == 'BSC':
                 nt, nr = noise.seismic.seismic_BSC_ISI(self.freq)
@@ -146,6 +276,7 @@ class SuspensionThermal(nb.Noise):
     )
 
     def calc(self):
+        precomp_suspension(self.freq, self.ifo)
         n = noise.suspensionthermal.suspension_thermal(self.freq, self.ifo.Suspension)
         return n * 4
 
@@ -162,10 +293,9 @@ class CoatingBrownian(nb.Noise):
     def calc(self):
         wavelength = self.ifo.Laser.Wavelength
         materials = self.ifo.Materials
-        wBeam_ITM = self.ifo.Optics.ITM.BeamRadius
-        wBeam_ETM = self.ifo.Optics.ETM.BeamRadius
-        dOpt_ITM = self.ifo.Optics.ITM.CoatLayerOpticalThickness
-        dOpt_ETM = self.ifo.Optics.ETM.CoatLayerOpticalThickness
+        w0, wBeam_ITM, wBeam_ETM = arm_cavity(self.ifo)
+        dOpt_ITM = coating_thickness(self.ifo, 'ITM')
+        dOpt_ETM = coating_thickness(self.ifo, 'ETM')
         nITM = noise.coatingthermal.coating_brownian(
             self.freq, materials, wavelength, wBeam_ITM, dOpt_ITM)
         nETM = noise.coatingthermal.coating_brownian(
@@ -186,10 +316,9 @@ class CoatingThermoOptic(nb.Noise):
     def calc(self):
         wavelength = self.ifo.Laser.Wavelength
         materials = self.ifo.Materials
-        wBeam_ITM = self.ifo.Optics.ITM.BeamRadius
-        wBeam_ETM = self.ifo.Optics.ETM.BeamRadius
-        dOpt_ITM = self.ifo.Optics.ITM.CoatLayerOpticalThickness
-        dOpt_ETM = self.ifo.Optics.ETM.CoatLayerOpticalThickness
+        w0, wBeam_ITM, wBeam_ETM = arm_cavity(self.ifo)
+        dOpt_ITM = coating_thickness(self.ifo, 'ITM')
+        dOpt_ETM = coating_thickness(self.ifo, 'ETM')
         nITM, junk1, junk2, junk3 = noise.coatingthermal.coating_thermooptic(
             self.freq, materials, wavelength, wBeam_ITM, dOpt_ITM[:])
         nETM, junk1, junk2, junk3 = noise.coatingthermal.coating_thermooptic(
@@ -208,9 +337,11 @@ class ITMThermoRefractive(nb.Noise):
     )
 
     def calc(self):
-        gPhase = self.ifo.gwinc.finesse * 2/np.pi
+        finesse = ifo_power(self.ifo)[2]
+        gPhase =  finesse * 2/np.pi
+        w0, wBeam_ITM, wBeam_ETM = arm_cavity(self.ifo)
         n = noise.substratethermal.substrate_thermorefractive(
-            self.freq, self.ifo.Materials, self.ifo.Optics.ITM.BeamRadius)
+            self.freq, self.ifo.Materials, wBeam_ITM)
         return n * 2 / (gPhase)**2
 
 
@@ -225,9 +356,11 @@ class ITMCarrierDensity(nb.Noise):
     )
 
     def calc(self):
-        gPhase = self.ifo.gwinc.finesse * 2/np.pi
+        finesse = ifo_power(self.ifo)[2]
+        gPhase = finesse * 2/np.pi
+        w0, wBeam_ITM, wBeam_ETM = arm_cavity(self.ifo)
         n = noise.substratethermal.substrate_carrierdensity(
-            self.freq, self.ifo.Materials, self.ifo.Optics.ITM.BeamRadius)
+            self.freq, self.ifo.Materials, wBeam_ITM)
         return n * 2 / (gPhase)**2
 
 
@@ -242,10 +375,11 @@ class SubstrateBrownian(nb.Noise):
     )
 
     def calc(self):
+        w0, wBeam_ITM, wBeam_ETM = arm_cavity(self.ifo)
         nITM = noise.substratethermal.substrate_brownian(
-            self.freq, self.ifo.Materials, self.ifo.Optics.ITM.BeamRadius)
+            self.freq, self.ifo.Materials, wBeam_ITM)
         nETM = noise.substratethermal.substrate_brownian(
-            self.freq, self.ifo.Materials, self.ifo.Optics.ETM.BeamRadius)
+            self.freq, self.ifo.Materials, wBeam_ETM)
         return (nITM + nETM) * 2
 
 
@@ -260,10 +394,11 @@ class SubstrateThermoElastic(nb.Noise):
     )
 
     def calc(self):
+        w0, wBeam_ITM, wBeam_ETM = arm_cavity(self.ifo)
         nITM = noise.substratethermal.substrate_thermoelastic(
-            self.freq, self.ifo.Materials, self.ifo.Optics.ITM.BeamRadius)
+            self.freq, self.ifo.Materials, wBeam_ITM)
         nETM = noise.substratethermal.substrate_thermoelastic(
-            self.freq, self.ifo.Materials, self.ifo.Optics.ETM.BeamRadius)
+            self.freq, self.ifo.Materials, wBeam_ETM)
         return (nITM + nETM) * 2
 
 
diff --git a/gwinc/precomp.py b/gwinc/precomp.py
deleted file mode 100644
index 554834c23e038eeb05e8b397a8daeb40b650687b..0000000000000000000000000000000000000000
--- a/gwinc/precomp.py
+++ /dev/null
@@ -1,183 +0,0 @@
-from __future__ import division
-from numpy import pi, sqrt, sin, exp
-from scipy.io import loadmat
-import logging
-import copy
-
-from . import const
-from . import suspension
-from .struct import Struct
-from .noise.coatingthermal import getCoatDopt
-
-
-def precompIFO(f, ifoin, PRfixed=True):
-    """Add precomputed data to the IFO model.
-
-    To prevent recomputation of these precomputed data, if the
-    ifo argument contains ifo.gwinc.PRfixed, and this matches
-    the argument PRfixed, no changes are made.
-
-    This function DOES NOT modify IFO. Its return value is a copy of
-    IFO with precomputations filled out.
-
-    """
-    ifo = copy.deepcopy(ifoin)
-
-    if 'gwinc' not in ifo:
-        ifo.gwinc = Struct()
-
-    ifo.gwinc.PRfixed = PRfixed
-
-    ##############################
-    # derived temp
-
-    if 'Temp' not in ifo.Materials.Substrate:
-        ifo.Materials.Substrate.Temp = ifo.Infrastructure.Temp
-
-    ##############################
-    # suspension vertical-horizontal coupling
-
-    if 'VHCoupling' not in ifo.Suspension:
-        ifo.Suspension.VHCoupling = Struct()
-        ifo.Suspension.VHCoupling.theta = ifo.Infrastructure.Length / const.R_earth
-
-    ##############################
-    # optics values
-
-    # calculate optics' parameters
-    ifo.Materials.MirrorVolume = pi*ifo.Materials.MassRadius**2 * \
-                                 ifo.Materials.MassThickness
-    ifo.Materials.MirrorMass = ifo.Materials.MirrorVolume * \
-                               ifo.Materials.Substrate.MassDensity
-    ifo.Optics.ITM.Thickness = ifo.Materials.MassThickness
-
-    # coating layer optical thicknesses - mevans 2 May 2008
-    if 'CoatLayerOpticalThickness' not in ifo.Optics.ITM:
-        T = ifo.Optics.ITM.Transmittance
-        dL = ifo.Optics.ITM.CoatingThicknessLown
-        dCap = ifo.Optics.ITM.CoatingThicknessCap
-        ifo.Optics.ITM.CoatLayerOpticalThickness = getCoatDopt(ifo.Materials, T, dL, dCap=dCap)
-        T = ifo.Optics.ETM.Transmittance
-        dL = ifo.Optics.ETM.CoatingThicknessLown
-        dCap = ifo.Optics.ETM.CoatingThicknessCap
-        ifo.Optics.ETM.CoatLayerOpticalThickness = getCoatDopt(ifo.Materials, T, dL, dCap=dCap)
-
-    ##############################
-    # beam parameters
-
-    armlen = ifo.Infrastructure.Length
-
-    # g-factors
-    g1 = 1 - armlen / ifo.Optics.Curvature.ITM
-    g2 = 1 - armlen / ifo.Optics.Curvature.ETM
-    gcav = sqrt(g1 * g2 * (1 - g1 * g2))
-    gden = g1 - 2 * g1 * g2 + g2
-
-    if (g1 * g2 * (1 - g1 * g2)) <= 0:
-        raise Exception('Unstable arm cavity g-factors.  Change ifo.Optics.Curvature')
-    elif gcav < 1e-3:
-        logging.warning('Nearly unstable arm cavity g-factors.  Reconsider ifo.Optics.Curvature')
-
-    ws = sqrt(armlen * ifo.Laser.Wavelength / pi)
-    w1 = ws * sqrt(abs(g2) / gcav)
-    w2 = ws * sqrt(abs(g1) / gcav)
-
-    # waist size
-    w0 = ws * sqrt(gcav / abs(gden))
-    zr = pi * w0**2 / ifo.Laser.Wavelength
-    z1 = armlen * g2 * (1 - g1) / gden
-    z2 = armlen * g1 * (1 - g2) / gden
-
-    ifo.Optics.ITM.BeamRadius = w1
-    ifo.Optics.ETM.BeamRadius = w2
-
-    ##############################
-    # calc power and IFO parameters
-
-    pbs, parm, finesse, prfactor, Tpr = precompPower(ifo, PRfixed)
-
-    ifo.gwinc.pbs = pbs
-    ifo.gwinc.parm = parm
-    ifo.gwinc.finesse = finesse
-    ifo.gwinc.prfactor = prfactor
-    ifo.gwinc.gITM = g1
-    ifo.gwinc.gETM = g2
-    ifo.gwinc.BeamWaist = w0
-    ifo.gwinc.BeamRayleighRange = zr
-    ifo.gwinc.BeamWaistToITM = z1
-    ifo.gwinc.BeamWaistToETM = z2
-    ifo.Optics.PRM.Transmittance = Tpr
-
-    ##############################
-    # saved seismic spectrum
-
-    if 'darmSeiSusFile' in ifo.Seismic and ifo.Seismic.darmSeiSusFile:
-        darmsei = loadmat(ifo.Seismic.darmSeiSusFile)
-        ifo.Seismic.darmseis_f = darmsei['darmseis_f'][0]
-        ifo.Seismic.darmseis_x = darmsei['darmseis_x'][0]
-
-    # --------------------------------------------------------
-    # Suspension
-    # if the suspension code supports different temps for the stages
-    fname = eval('suspension.susp{}'.format(ifo.Suspension.Type))
-    hForce, vForce, hTable, vTable = fname(f, ifo.Suspension)
-
-    try:
-        # full TF (conventional)
-        ifo.Suspension.hForce = hForce.fullylossy
-        ifo.Suspension.vForce = vForce.fullylossy
-        # TFs with each stage lossy
-        ifo.Suspension.hForce_singlylossy = hForce.singlylossy
-        ifo.Suspension.vForce_singlylossy = vForce.singlylossy
-    except:
-        ifo.Suspension.hForce = hForce
-        ifo.Suspension.vForce = vForce
-
-    ifo.Suspension.hTable = hTable
-    ifo.Suspension.vTable = vTable
-
-    return ifo
-
-
-def precompPower(ifo, PRfixed=True):
-    """Compute power on beamsplitter, finesse, and power recycling factor.
-
-    """
-    c       = const.c
-    pin     = ifo.Laser.Power
-    t1      = sqrt(ifo.Optics.ITM.Transmittance)
-    r1      = sqrt(1 - ifo.Optics.ITM.Transmittance)
-    r2      = sqrt(1 - ifo.Optics.ETM.Transmittance)
-    t5      = sqrt(ifo.Optics.PRM.Transmittance)
-    r5      = sqrt(1 - ifo.Optics.PRM.Transmittance)
-    loss    = ifo.Optics.Loss                          # single TM loss
-    bsloss  = ifo.Optics.BSLoss
-    acoat   = ifo.Optics.ITM.CoatingAbsorption
-    pcrit   = ifo.Optics.pcrit
-
-    # Finesse, effective number of bounces in cavity, power recycling factor
-    finesse = 2*pi / (t1**2 + 2*loss)        # arm cavity finesse
-    neff    = 2 * finesse / pi
-
-    # Arm cavity reflectivity with finite loss
-    garm = t1 / (1 - r1*r2*sqrt(1-2*loss))  # amplitude gain wrt input field
-    rarm = r1 - t1 * r2 * sqrt(1-2*loss) * garm
-
-    if PRfixed:
-        Tpr = ifo.Optics.PRM.Transmittance  # use given value
-    else:
-        Tpr = 1-(rarm*sqrt(1-bsloss))**2 # optimal recycling mirror transmission
-        t5 = sqrt(Tpr)
-        r5 = sqrt(1 - Tpr)
-    prfactor = t5**2 / (1 + r5 * rarm * sqrt(1-bsloss))**2
-
-    pbs  = pin * prfactor          # BS power from input power
-    parm = pbs * garm**2 / 2       # arm power from BS power
-
-    asub = 1.3*2*ifo.Optics.ITM.Thickness*ifo.Optics.SubstrateAbsorption
-    pbsl = 2*pcrit/(asub+acoat*neff) # bs power limited by thermal lensing
-
-    if pbs > pbsl:
-        logging.warning('P_BS exceeds BS Thermal limit!')
-
-    return pbs, parm, finesse, prfactor, Tpr
diff --git a/gwinc/test/__main__.py b/gwinc/test/__main__.py
index cfac045c4f110d111aa6af225b197f8b076f0446..41c684f492244b56ab83b9143f2c36fe9cca3b3b 100644
--- a/gwinc/test/__main__.py
+++ b/gwinc/test/__main__.py
@@ -15,7 +15,6 @@ logging.basicConfig(format='%(message)s',
                     level=os.getenv('LOG_LEVEL', logging.INFO))
 
 from .. import available_ifos, load_budget
-from .. import precompIFO
 from .. import load_hdf5, save_hdf5
 
 try:
@@ -180,8 +179,7 @@ def main():
         freq = np.logspace(np.log10(5), np.log10(6000), 3000)
         for name in available_ifos():
             Budget = load_budget(name)
-            ifo = precompIFO(freq, Budget.ifo)
-            traces = Budget(freq, ifo=ifo).run()
+            traces = Budget(freq).run()
             path = os.path.join(args.cache, name+'.h5')
             save_hdf5(path, freq, traces)
         return
@@ -219,8 +217,7 @@ def main():
         freq, tracesA, attrs = load_hdf5(path)
 
         Budget = load_budget(name)
-        ifo = precompIFO(freq, Budget.ifo)
-        tracesB = Budget(freq, ifo=ifo).run()
+        tracesB = Budget(freq).run()
 
         if inspiral_range:
             totalA = tracesA['Total'][0]