docs: def P_NTU_method(m1, m2, Cp1, Cp2, UA=None, T1i=None, T1o=None, 
                   T2i=None, T2o=None, subtype='crossflow', Ntp=1, optimal=True):
      r'''Wrapper for the various P-NTU method function calls,
      which can solve a heat exchanger. The heat capacities and mass flows
      of each stream and the type of the heat exchanger are always required.
      As additional inputs, one combination of the following inputs is required:
      
      * Three of the four inlet and outlet stream temperatures.
      * Temperatures for the side 1 outlet and side 2 inlet and UA
      * Temperatures for the side 1 outlet side 2 outlet and UA
      * Temperatures for the side 1 inlet and side 2 inlet and UA
      * Temperatures for the side 1 inlet and side 2 outlet and UA
  
      Computes the total heat exchanged as well as both temperatures of both
      streams.
        
      Parameters
      ----------
      m1 : float
          Mass flow rate of stream 1 (shell side = 1, tube side = 2), [kg/s]
      m2 : float
          Mass flow rate of stream 2 (shell side = 1, tube side = 2), [kg/s]
      Cp1 : float
          Averaged heat capacity of stream 1 (shell side), [J/kg/K]
      Cp2 : float
          Averaged heat capacity of stream 2 (tube side), [J/kg/K]
      UA : float, optional
          Combined Area-heat transfer coefficient term, [W/K]
      T1i : float, optional
          Inlet temperature of stream 1 (shell side), [K]
      T1o : float, optional
          Outlet temperature of stream 1 (shell side), [K]
      T2i : float, optional
          Inlet temperature of stream 2 (tube side), [K]
      T2o : float, optional
          Outlet temperature of stream 2 (tube-side), [K]
      subtype : str, optional
          The subtype of exchanger; one of 'E', 'G', 'H', 'J', 'counterflow',
          'parallel', 'crossflow', 'crossflow, mixed 1', 'crossflow, mixed 2',
          or 'crossflow, mixed 1&2'. For plate exchangers 'Np1/Np2' where `Np1`
          is the number of side 1 passes and `Np2` is the number of side 2 passes
      Ntp : int, optional
          For real heat exchangers (types 'E', 'G', 'H', and 'J'), the number of 
          tube passes needss to be specified as well. Not all types support
          any number of tube passes.
      optimal : bool, optional
          For real heat exchangers (types 'E', 'G', 'H', and 'J'), there is often
          a more countercurrent (optimal) way to arrange the tube passes and a
          more parallel (optimal=False) way to arrange them. This controls that.
  
      Returns
      -------
      results : dict
          * Q : Heat exchanged in the heat exchanger, [W]
          * UA : Combined area-heat transfer coefficient term, [W/K]
          * T1i : Inlet temperature of stream 1, [K]
          * T1o : Outlet temperature of stream 1, [K]
          * T2i : Inlet temperature of stream 2, [K]
          * T2o : Outlet temperature of stream 2, [K]
          * P1 : Thermal effectiveness with respect to stream 1, [-]
          * P2 : Thermal effectiveness with respect to stream 2, [-]
          * R1 : Heat capacity ratio with respect to stream 1, [-]
          * R2 : Heat capacity ratio with respect to stream 2, [-]
          * C1 : The heat capacity rate of fluid 1, [W/K]
          * C2 : The heat capacity rate of fluid 2, [W/K]
          * NTU1 : Thermal Number of Transfer Units with respect to stream 1 [-]
          * NTU2 : Thermal Number of Transfer Units with respect to stream 2 [-]
      
      Notes
      -----
      The main equations used in this method are as follows. For the individual
      expressions used to calculate `P1`, see the `See Also` section.
      
      .. math::
          Q = P_1 C_1 \Delta T_{max} = P_2 C_2 \Delta T_{max}
          
          \Delta T_{max} = T_{h,i} - T_{c,i} = |T_{2,i} - T_{1,i}|
          
          R_1 = \frac{C_1}{C_2} = \frac{T_{2,i} - T_{2,o}}{T_{1,o} - T_{1, i}}
          
          R_2 = \frac{C_2}{C_1} = \frac{T_{1,o} - T_{1, i}}{T_{2,i} - T_{2,o}}
  
          R_1 = \frac{1}{R_2}
          
          NTU_1 = \frac{UA}{C_1}
          
          NTU_2 = \frac{UA}{C_2}
          
          NTU_1 = NTU_2 R_2
          
          NTU_2 = NTU_1 R_1
          
          P_1 = \frac{T_{1,o} - T_{1,i}}{T_{2,i} - T_{1,i}}
          
          P_2 = \frac{T_{2,i} - T_{2,o}}{T_{2,i} - T_{1,i}}
          
          P_1 = P_2 R_2
          
          P_2 = P_1 R_1
          
          C_1 = m_1 Cp_1
          
          C_2 = m_2 Cp_2
          
      Once `P1` has been calculated, there are six different cases for calculating
      the other stream temperatures depending on the two temperatures provided. 
      They were derived with SymPy.
          
      Two known inlet temperatures:
          
      .. math::
          T_{1,o} = - P_{1} T_{1,i} + P_{1} T_{2,i} + T_{1,i}
          
          T_{2,o} = P_{1} R_{1} T_{1,i} - P_{1} R_{1} T_{2,i} + T_{2,i}
          
      Two known outlet temperatures:
          
      .. math::
          T_{1,i} = \frac{P_{1} R_{1} T_{1,o} + P_{1} T_{2,o} 
          - T_{1,o}}{P_{1} R_{1} + P_{1} - 1}
          
          T_{2,i} = \frac{P_{1} R_{1} T_{1,o} + P_{1} T_{2,o}
          - T_{2,o}}{P_{1} R_{1} + P_{1} - 1}
          
      Inlet 1 known, outlet 2 known:
          
      .. math::
          T_{1,o} = \frac{1}{P_{1} R_{1} - 1} \left(P_{1} R_{1} T_{1,i}
          + P_{1} T_{1,i} - P_{1} T_{2,o} - T_{1,i}\right)
          
          T_{2,i} = \frac{P_{1} R_{1} T_{1,i} - T_{2,o}}{P_{1} R_{1} - 1}
          
      Outlet 1 known, inlet 2 known:
          
      .. math::
          T_{1,i} = \frac{P_{1} T_{2,i} - T_{1,o}}{P_{1} - 1}
          
          T_{2,o}  = \frac{1}{P_{1} - 1} \left(R_{1} \left(P_{1} T_{2,i}
          - T_{1,o}\right) - \left(P_{1} - 1\right) \left(R_{1} T_{1,o}
          - T_{2,i}\right)\right)
      
      Input and output of 2 known:
          
      .. math::
          T_{1,i} = \frac{1}{P_{1} R_{1}} \left(P_{1} R_{1} T_{2,i} 
          - T_{2,i} + T_{2,o}\right)
          
          T_{1,o} = \frac{1}{P_{1} R_{1}} \left(P_{1} R_{1} T_{2,i} 
          + \left(P_{1} - 1\right) \left(T_{2,i} - T_{2,o}\right)\right)
          
      Input and output of 1 known:
          
      .. math::
          T_{2,i} = \frac{1}{P_{1}} \left(P_{1} T_{1,i} - T_{1,i} 
          + T_{1,o}\right)
          
          T_{2,o} = \frac{1}{P_{1}} \left(P_{1} R_{1} \left(T_{1,i} 
          - T_{1,o}\right) + P_{1} T_{1,i} - T_{1,i} + T_{1,o}\right)
          
      See also
      --------
      temperature_effectiveness_basic
      temperature_effectiveness_plate
      temperature_effectiveness_TEMA_E
      temperature_effectiveness_TEMA_G
      temperature_effectiveness_TEMA_H
      temperature_effectiveness_TEMA_J
      NTU_from_P_basic
      NTU_from_P_plate
      NTU_from_P_E
      NTU_from_P_G
      NTU_from_P_H
      NTU_from_P_J
  
      Examples
      --------
      Solve a heat exchanger with the UA specified, and known inlet temperatures:
          
      >>> pprint(P_NTU_method(m1=5.2, m2=1.45, Cp1=1860., Cp2=1900, 
      ... subtype='E', Ntp=4, T2i=15, T1i=130, UA=3041.75))
      {'C1': 9672.0,
       'C2': 2755.0,
       'NTU1': 0.3144902812241522,
       'NTU2': 1.1040834845735028,
       'P1': 0.1730811614360235,
       'P2': 0.6076373841775751,
       'Q': 192514.71424206023,
       'R1': 3.5107078039927404,
       'R2': 0.2848428453267163,
       'T1i': 130,
       'T1o': 110.09566643485729,
       'T2i': 15,
       'T2o': 84.87829918042112,
       'UA': 3041.75}
      
      Solve the same heat exchanger as if T1i, T2i, and T2o were known but UA was
      not:
          
      >>> pprint(P_NTU_method(m1=5.2, m2=1.45, Cp1=1860., Cp2=1900, subtype='E', 
      ... Ntp=4, T1i=130, T2i=15, T2o=84.87829918042112))
      {'C1': 9672.0,
       'C2': 2755.0,
       'NTU1': 0.31449028122515194,
       'NTU2': 1.1040834845770124,
       'P1': 0.17308116143602348,
       'P2': 0.607637384177575,
       'Q': 192514.7142420602,
       'R1': 3.5107078039927404,
       'R2': 0.2848428453267163,
       'T1i': 130,
       'T1o': 110.09566643485729,
       'T2i': 15,
       'T2o': 84.87829918042112,
       'UA': 3041.7500000096693}
  
      Solve a 2 pass/2 pass plate heat exchanger with overall parallel flow and
      its individual passes operating in parallel and known outlet temperatures.
      Note the overall parallel part is trigered with `optimal=False`, and the
      individual pass parallel is triggered by appending 'p' to the subtype. The 
      subpass counterflow can be specified by appending 'c' instead to the 
      subtype, but this is never necessary as it is the default.
          
      >>> pprint(P_NTU_method(m1=5.2, m2=1.45, Cp1=1860., Cp2=1900., UA=300, 
      ... T1o=126.7, T2o=26.7, subtype='2/2p', optimal=False))
      {'C1': 9672.0,
       'C2': 2755.0,
       'NTU1': 0.031017369727047148,
       'NTU2': 0.1088929219600726,
       'P1': 0.028945295974795074,
       'P2': 0.10161847646759273,
       'Q': 32200.050307849266,
       'R1': 3.5107078039927404,
       'R2': 0.2848428453267163,
       'T1i': 130.02920288542694,
       'T1o': 126.7,
       'T2i': 15.012141449056527,
       'T2o': 26.7,
       'UA': 300}
  
      References
      ----------
      .. [1] Shah, Ramesh K., and Dusan P. Sekulic. Fundamentals of Heat 
         Exchanger Design. 1st edition. Hoboken, NJ: Wiley, 2002.
      .. [2] Thulukkanam, Kuppan. Heat Exchanger Design Handbook, Second Edition. 
         CRC Press, 2013.
      .. [3] Rohsenow, Warren and James Hartnett and Young Cho. Handbook of Heat
         Transfer, 3E. New York: McGraw-Hill, 1998.
      '''
      # Shellside: 1
      # Tubeside: 2
      C1 = m1*Cp1
      C2 = m2*Cp2
      R1 = C1/C2
      R2 = C2/C1
      
      if UA is not None:
          NTU1 = UA/C1
          NTU2 = UA/C2
          
          if subtype in ['counterflow', 'parallel', 'crossflow', 'crossflow, mixed 1', 'crossflow, mixed 2', 'crossflow, mixed 1&2']:
              P1 = temperature_effectiveness_basic(R1, NTU1, subtype=subtype)
          elif subtype == 'E':
              P1 = temperature_effectiveness_TEMA_E(R1=R1, NTU1=NTU1, Ntp=Ntp, optimal=optimal)
          elif subtype == 'G':
              P1 = temperature_effectiveness_TEMA_G(R1=R1, NTU1=NTU1, Ntp=Ntp, optimal=optimal)
          elif subtype == 'H':
              P1 = temperature_effectiveness_TEMA_H(R1=R1, NTU1=NTU1, Ntp=Ntp, optimal=optimal)
          elif subtype == 'J':
              P1 = temperature_effectiveness_TEMA_J(R1=R1, NTU1=NTU1, Ntp=Ntp)
          elif '/' in subtype:
              passes_counterflow = True
              Np1, end = subtype.split('/')
              if end[-1] in ['c','p']:
                  passes_counterflow = True if end[-1] == 'c' else False
                  end = end[0:-1]
              Np1, Np2 = int(Np1), int(end)
              P1 = temperature_effectiveness_plate(R1=R1, NTU1=NTU1, Np1=Np1, Np2=Np2, counterflow=optimal, passes_counterflow=passes_counterflow)
          else:
              raise Exception("Supported types are 'E', 'G', 'H', 'J', 'counterflow',\
      'parallel', 'crossflow', 'crossflow, mixed 1', 'crossflow, mixed 2', \
      'crossflow, mixed 1&2', or 'Np1/Np2' for plate exchangers")
          
          possible_inputs = [(T1i, T2i), (T1o, T2o), (T1i, T2o), (T1o, T2i), (T1i, T1o), (T2i, T2o)]
          if not any([i for i in possible_inputs if None not in i]):
              raise Exception('One set of (T1i, T2i), (T1o, T2o), (T1i, T2o), (T1o, T2i), (T1i, T1o), or (T2i, T2o) is required along with UA.')
          
          # Deal with different temperature inputs, generated with SymPy
          if T1i and T2i:
              T2o = P1*R1*T1i - P1*R1*T2i + T2i
              T1o = -P1*T1i + P1*T2i + T1i
          elif T1o and T2o:
              T2i = (P1*R1*T1o + P1*T2o - T2o)/(P1*R1 + P1 - 1.)
              T1i = (P1*R1*T1o + P1*T2o - T1o)/(P1*R1 + P1 - 1.)
          elif T1o and T2i:
              T2o = (R1*(P1*T2i - T1o) - (P1 - 1.)*(R1*T1o - T2i))/(P1 - 1.)
              T1i = (P1*T2i - T1o)/(P1 - 1.)
          elif T1i and T2o:
              T1o = (P1*R1*T1i + P1*T1i - P1*T2o - T1i)/(P1*R1 - 1.)
              T2i = (P1*R1*T1i - T2o)/(P1*R1 - 1.)
          elif T2i and T2o:
              T1o = (P1*R1*T2i + (P1 - 1.)*(T2i - T2o))/(P1*R1)
              T1i = (P1*R1*T2i - T2i + T2o)/(P1*R1)
          elif T1i and T1o:
              T2o = (P1*R1*(T1i - T1o) + P1*T1i - T1i + T1o)/P1
              T2i = (P1*T1i - T1i + T1o)/P1 
      else:
          # Case where we're solving for UA
          # Three temperatures are required
          # Ensures all four temperatures are set and Q is calculated
          if T1i is not None and T1o is not None:
              Q = m1*Cp1*(T1i-T1o)
              if T2i is not None and T2o is None:
                  T2o = T2i + Q/(m2*Cp2)
              elif T2o is not None and T2i is None:
                  T2i = T2o - Q/(m2*Cp2)
              elif T2o is not None and T2i is not None:
                  Q2 = m2*Cp2*(T2o-T2i)
                  if abs((Q-Q2)/Q) > 0.01:
                      raise Exception('The specified heat capacities, mass flows,'
                                      ' and temperatures are inconsistent')
              else:
                  raise Exception('At least one temperature is required to be '
                                  'specified on side 2.')
                  
          elif T2i is not None and T2o is not None:
              Q = m2*Cp2*(T2o-T2i)
              if T1i is not None and T1o is None:
                  T1o = T1i - Q/(m1*Cp1)
              elif T1o is not None and T1i is None:
                  T1i = T1o + Q/(m1*Cp1)
              else:
                  raise Exception('At least one temperature is required to be '
                                  'specified on side 2.')
          else:
              raise Exception('Three temperatures are required to be specified '
                              'when solving for UA')
                  
          P1 = Q/(C1*abs(T2i-T1i))
          if subtype in ['counterflow', 'parallel', 'crossflow', 'crossflow, mixed 1', 'crossflow, mixed 2', 'crossflow, mixed 1&2']:
              NTU1 = NTU_from_P_basic(P1=P1, R1=R1, subtype=subtype)
          elif subtype == 'E':
              NTU1 = NTU_from_P_E(P1=P1, R1=R1, Ntp=Ntp, optimal=optimal)
          elif subtype == 'G':
              NTU1 = NTU_from_P_G(P1=P1, R1=R1, Ntp=Ntp, optimal=optimal)
          elif subtype == 'H':
              NTU1 = NTU_from_P_H(P1=P1, R1=R1, Ntp=Ntp, optimal=optimal)
          elif subtype == 'J':
              NTU1 = NTU_from_P_J(P1=P1, R1=R1, Ntp=Ntp)
          elif '/' in subtype:
              passes_counterflow = True
              Np1, end = subtype.split('/')
              if end[-1] in ['c','p']:
                  passes_counterflow = True if end[-1] == 'c' else False
                  end = end[0:-1]
              Np1, Np2 = int(Np1), int(end)
              NTU1 = NTU_from_P_plate(P1=P1, R1=R1, Np1=Np1, Np2=Np2, counterflow=optimal, passes_counterflow=passes_counterflow)
          else:
              raise Exception("Supported types are 'E', 'G', 'H', 'J', 'counterflow',\
      'parallel', 'crossflow', 'crossflow, mixed 1', 'crossflow, mixed 2', \
      'crossflow, mixed 1&2', or 'Np1/Np2' for plate exchangers")
          UA = NTU1*C1
          NTU2 = UA/C2
          
      Q = abs(T1i-T2i)*P1*C1
      # extra:
      P2 = P1*R1
  #    effectiveness = max(C1, C2)/min(C1, C2)
      results = {'Q': Q, 'T1i': T1i, 'T1o': T1o, 'T2i': T2i, 'T2o': T2o, 
            'C1': C1, 'C2': C2, 'R1': R1, 'R2': R2, 'P1': P1, 'P2': P2, 'NTU1': NTU1, 'NTU2': NTU2, 'UA': UA}
      return results