docs: def effectiveness_from_NTU(NTU, Cr, subtype='counterflow'):
      r'''Returns the effectiveness of a heat exchanger at a specified heat 
      capacity rate, number of transfer units, and configuration. The following
      configurations are supported:
          
          * Counterflow (ex. double-pipe)
          * Parallel (ex. double pipe inefficient configuration)
          * Shell and tube exchangers with even numbers of tube passes,
            one or more shells in series
          * Crossflow, single pass, fluids unmixed
          * Crossflow, single pass, Cmax mixed, Cmin unmixed
          * Crossflow, single pass, Cmin mixed, Cmax unmixed
          * Boiler or condenser
      
      These situations are normally not those which occur in real heat exchangers,
      but are useful for academic purposes. More complicated expressions are 
      available for other methods. These equations are confirmed in [1]_,
      [2]_, and [3]_.
      
      For parallel flow heat exchangers:
  
      .. math::
          \epsilon = \frac{1 - \exp[-NTU(1+C_r)]}{1+C_r}
  
      For counterflow heat exchangers:
  
      .. math::
          \epsilon = \frac{1 - \exp[-NTU(1-C_r)]}{1-C_r\exp[-NTU(1-C_r)]},\; C_r < 1
  
          \epsilon = \frac{NTU}{1+NTU},\; C_r = 1
  
      For TEMA E shell-and-tube heat exchangers with one shell pass, 2n tube 
      passes:
  
      .. math::
          \epsilon_1 = 2\left\{1 + C_r + \sqrt{1+C_r^2}\times\frac{1+\exp
          [-(NTU)_1\sqrt{1+C_r^2}]}{1-\exp[-(NTU)_1\sqrt{1+C_r^2}]}\right\}^{-1}
  
      For TEMA E shell-and-tube heat exchangers with more than one shell pass, 2n  
      tube passes (this model assumes each exchanger has an equal share of the 
      overall NTU or said more plainly, the same UA):
  
      .. math::
          \epsilon = \left[\left(\frac{1-\epsilon_1 C_r}{1-\epsilon_1}\right)^2
          -1\right]\left[\left(\frac{1-\epsilon_1 C_r}{1-\epsilon_1}\right)^n
          - C_r\right]^{-1}
  
      For cross-flow (single-pass) heat exchangers with both fluids unmixed, there
      is an approximate and an exact formula. The approximate one is:
  
      .. math::
          \epsilon = 1 - \exp\left[\left(\frac{1}{C_r}\right)
          (NTU)^{0.22}\left\{\exp\left[C_r(NTU)^{0.78}\right]-1\right\}\right]
          
      The exact solution for crossflow (fluids unmixed) uses SciPy's quad
      to perform an integral (there is no analytical integral solution available).
      :math:`I_0(v)` is the modified Bessel function of the first kind. This formula
      was developed in [4]_.
      
      .. math::
          \epsilon = \frac{1}{C_r} - \frac{\exp(-C_r \cdot NTU)}{2(C_r NTU)^2}
          \int_0^{2 NTU\sqrt{C_r}} \left(1 + NTU - \frac{v^2}{4C_r NTU}\right)
          \exp\left(-\frac{v^2}{4C_r NTU}\right)v I_0(v) dv
  
      For cross-flow (single-pass) heat exchangers with Cmax mixed, Cmin unmixed:
  
      .. math::
          \epsilon = \left(\frac{1}{C_r}\right)(1 - \exp\left\{-C_r[1-\exp(-NTU)]\right\})
  
      For cross-flow (single-pass) heat exchangers with Cmin mixed, Cmax unmixed:
  
      .. math::
          \epsilon = 1 - \exp(-C_r^{-1}\{1 - \exp[-C_r(NTU)]\})
  
      For cases where `Cr` = 0, as in an exchanger with latent heat exchange,
      flow arrangement does not matter: 
  
      .. math::
          \epsilon = 1 - \exp(-NTU)
  
      Parameters
      ----------
      NTU : float
          Thermal Number of Transfer Units [-]
      Cr : float
          The heat capacity rate ratio, of the smaller fluid to the larger
          fluid, [-]
      subtype : str, optional
          The subtype of exchanger; one of 'counterflow', 'parallel', 'crossflow'
          'crossflow approximate', 'crossflow, mixed Cmin', 
          'crossflow, mixed Cmax', 'boiler', 'condenser', 'S&T', or 'nS&T' where 
          n is the number of shell and tube exchangers in a row.
  
      Returns
      -------
      effectiveness : float
          The thermal effectiveness of the heat exchanger, [-]
  
      Notes
      -----
      Once the effectiveness of the exchanger has been calculated, the total
      heat transferred can be calculated according to the following formulas,
      depending on which stream temperatures are known:
          
      If the inlet temperatures for both sides are known:
          
      .. math::
          Q=\epsilon C_{min}(T_{h,i}-T_{c,i})
          
      If the outlet temperatures for both sides are known:
          
      .. math::
          Q = \frac{\epsilon C_{min}C_{hot}C_{cold}(T_{c,o}-T_{h,o})}
          {\epsilon  C_{min}(C_{hot} +C_{cold}) - (C_{hot}C_{cold}) }
      
      If the hot inlet and cold outlet are known:
          
      .. math::
          Q = \frac{\epsilon C_{min}C_c(T_{co}-T_{hi})}{\epsilon C_{min}-C_c}
          
      If the hot outlet stream and cold inlet stream are known:
          
      .. math::
          Q = \frac{\epsilon C_{min}C_h(T_{ci}-T_{ho})}{\epsilon C_{min}-C_h}
      
      
      If the inlet and outlet conditions for a single side are known, the
      effectiveness wasn't needed for it to be calculated. For completeness,
      the formulas are as follows:
          
      .. math::
          Q = C_c(T_{c,o} - T_{c,i}) = C_h(T_{h,i} - T_{h,o})
          
      There is also a term called :math:`Q_{max}`, which is the heat which would
      have been transferred if the effectiveness was 1. It is calculated as
      follows:
          
      .. math::
          Q_{max} = \frac{Q}{\text{effectiveness}}
          
      Examples
      --------
      Worst case, parallel flow:
      
      >>> effectiveness_from_NTU(NTU=5, Cr=0.7, subtype='parallel')
      0.5881156068417585
      
      Crossflow, somewhat higher effectiveness:
          
      >>> effectiveness_from_NTU(NTU=5, Cr=0.7, subtype='crossflow')
      0.8444821799748551
  
      Counterflow, better than either crossflow or parallel flow:
  
      >>> effectiveness_from_NTU(NTU=5, Cr=0.7, subtype='counterflow')
      0.9206703686051108
      
      One shell and tube heat exchanger gives worse performance than counterflow,
      but they are designed to be economical and compact which a counterflow
      exchanger would not be. As the number of shells approaches infinity,
      the counterflow result is obtained exactly.
      
      >>> effectiveness_from_NTU(NTU=5, Cr=0.7, subtype='S&T')
      0.6834977044311439
      >>> effectiveness_from_NTU(NTU=5, Cr=0.7, subtype='50S&T')
      0.9205058702789254
  
      
      Overall case of rating an existing heat exchanger where a known flowrate
      of steam and oil are contacted in crossflow, with the steam side mixed
      (example 10-9 in [3]_):
          
      >>> U = 275 # W/m^2/K
      >>> A = 10.82 # m^2
      >>> Cp_oil = 1900 # J/kg/K
      >>> Cp_steam = 1860 # J/kg/K
      >>> m_steam = 5.2 # kg/s
      >>> m_oil = 0.725 # kg/s
      >>> Thi = 130 # °C
      >>> Tci = 15 # °C
      >>> Cmin = calc_Cmin(mh=m_steam, mc=m_oil, Cph=Cp_steam, Cpc=Cp_oil)
      >>> Cmax = calc_Cmax(mh=m_steam, mc=m_oil, Cph=Cp_steam, Cpc=Cp_oil)
      >>> Cr = calc_Cr(mh=m_steam, mc=m_oil, Cph=Cp_steam, Cpc=Cp_oil)
      >>> NTU = NTU_from_UA(UA=U*A, Cmin=Cmin)
      >>> eff = effectiveness_from_NTU(NTU=NTU, Cr=Cr, subtype='crossflow, mixed Cmax')
      >>> Q = eff*Cmin*(Thi - Tci)
      >>> Tco = Tci + Q/(m_oil*Cp_oil)
      >>> Tho = Thi - Q/(m_steam*Cp_steam)
      >>> Cmin, Cmax, Cr
      (1377.5, 9672.0, 0.14242142266335814)
      >>> NTU, eff, Q
      (2.160072595281307, 0.8312180361425988, 131675.32715043944)
      >>> Tco, Tho
      (110.59007415639887, 116.38592564614977)
      
      Alternatively, if only the outlet temperatures had been known:
          
      >>> Tco = 110.59007415639887
      >>> Tho = 116.38592564614977
      >>> Cc, Ch = Cmin, Cmax # In this case but not always
      >>> Q = eff*Cmin*Cc*Ch*(Tco - Tho)/(eff*Cmin*(Cc+Ch) - Ch*Cc)
      >>> Thi = Tho + Q/Ch
      >>> Tci = Tco - Q/Cc
      >>> Q, Tci, Thi
      (131675.32715043964, 14.999999999999858, 130.00000000000003)
  
      References
      ----------
      .. [1] Bergman, Theodore L., Adrienne S. Lavine, Frank P. Incropera, and
         David P. DeWitt. Introduction to Heat Transfer. 6E. Hoboken, NJ:
         Wiley, 2011.
      .. [2] Shah, Ramesh K., and Dusan P. Sekulic. Fundamentals of Heat 
         Exchanger Design. 1st edition. Hoboken, NJ: Wiley, 2002.
      .. [3] Holman, Jack. Heat Transfer. 10th edition. Boston: McGraw-Hill 
         Education, 2009.
      .. [4] Triboix, Alain. "Exact and Approximate Formulas for Cross Flow Heat 
         Exchangers with Unmixed Fluids." International Communications in Heat 
         and Mass Transfer 36, no. 2 (February 1, 2009): 121-24. 
         doi:10.1016/j.icheatmasstransfer.2008.10.012.
      '''
      if Cr > 1:
          raise Exception('Heat capacity rate must be less than 1 by definition.')
          
      if subtype == 'counterflow':
          if Cr < 1:
              return (1. - exp(-NTU*(1. - Cr)))/(1. - Cr*exp(-NTU*(1. - Cr)))
          elif Cr == 1:
              return NTU/(1. + NTU)
      elif subtype == 'parallel':
              return (1. - exp(-NTU*(1. + Cr)))/(1. + Cr)
      elif 'S&T' in subtype:
          str_shells = subtype.split('S&T')[0]
          shells = int(str_shells) if str_shells else 1
          NTU = NTU/shells
          
          top = 1. + exp(-NTU*(1. + Cr**2)**.5)
          bottom = 1. - exp(-NTU*(1. + Cr**2)**.5)
          effectiveness = 2./(1. + Cr + (1. + Cr**2)**.5*top/bottom)
          if shells > 1:
              term = ((1. - effectiveness*Cr)/(1. - effectiveness))**shells
              effectiveness = (term - 1.)/(term - Cr)
          return effectiveness
      elif subtype == 'crossflow':
          def to_int(v, NTU, Cr):
              return (1. + NTU - v*v/(4.*Cr*NTU))*exp(-v*v/(4.*Cr*NTU))*v*iv(0, v)
          int_term = quad(to_int, 0, 2.*NTU*Cr**0.5, args=(NTU, Cr))[0]
          return 1./Cr - exp(-Cr*NTU)/(2.*(Cr*NTU)**2)*int_term
      elif subtype == 'crossflow approximate':
          return 1. - exp(1./Cr*NTU**0.22*(exp(-Cr*NTU**0.78) - 1.))
      elif subtype == 'crossflow, mixed Cmin':
          return 1. -exp(-Cr**-1*(1. - exp(-Cr*NTU)))
      elif subtype ==  'crossflow, mixed Cmax':
          return (1./Cr)*(1. - exp(-Cr*(1. - exp(-NTU))))
      elif subtype in ['boiler', 'condenser']:
          return  1. - exp(-NTU)
      else:
          raise Exception('Input heat exchanger type not recognized')