docs: def NTU_from_P_plate(P1, R1, Np1, Np2, counterflow=True, 
                       passes_counterflow=True, reverse=False):
      r'''Returns the number of transfer units of a plate heat exchanger
      with a specified side 1 heat capacity ratio `R1`, side 1 number
      of transfer units `NTU1`, number of passes on sides 1 and 2 (respectively
      `Np1` and `Np2`). 
              
      For all cases, the function also takes as arguments whether the exchanger 
      is setup in an overall counter or parallel orientation `counterflow`, and 
      whether or not individual stream passes are themselves counterflow or
      parallel. 
      
      The 20 supported cases are as follows. (the first number of sides listed
      refers to side 1, and the second number refers to side 2):
          
      * 1 pass/1 pass parallelflow
      * 1 pass/1 pass counterflow
      * 1 pass/2 pass
      * 1 pass/3 pass or 3 pass/1 pass (with the two end passes in parallel)
      * 1 pass/3 pass or 3 pass/1 pass (with the two end passes in counterflow)
      * 1 pass/4 pass 
      * 2 pass/2 pass, overall parallelflow, individual passes in parallel 
      * 2 pass/2 pass, overall parallelflow, individual passes counterflow
      * 2 pass/2 pass, overall counterflow, individual passes parallelflow 
      * 2 pass/2 pass, overall counterflow, individual passes counterflow 
      * 2 pass/3 pass or 3 pass/2 pass, overall parallelflow 
      * 2 pass/3 pass or 3 pass/2 pass, overall counterflow
      * 2 pass/4 pass or 4 pass/2 pass, overall parallel flow
      * 2 pass/4 pass or 4 pass/2 pass, overall counterflow flow
      
      For all except the simplest cases numerical solutions are used.
      
      1 pass/1 pass counterflow (also 2/2 fully counterflow):
      
      .. math::
          NTU_1 = - \frac{1}{R_{1} - 1} \log{\left (\frac{P_{1} R_{1} - 1}{P_{1} 
          - 1} \right )}
      
      1 pass/1 pass parallel flow (also 2/2 fully parallelflow):
      
      .. math::
          NTU_1 = \frac{1}{R_{1} + 1} \log{\left (- \frac{1}{P_{1} \left(R_{1} 
          + 1\right) - 1} \right )}
                  
      Parameters
      ----------
      P1 : float
          Thermal effectiveness of the heat exchanger in the P-NTU method,
          calculated with respect to stream 1 [-]
      R1 : float
          Heat capacity ratio of the heat exchanger in the P-NTU method,
          calculated with respect to stream 1 [-]
      Np1 : int
          Number of passes on side 1 [-]
      Np2 : int
          Number of passes on side 2 [-]
      counterflow : bool
          Whether or not the overall flow through the heat exchanger is in
          counterflow or parallel flow, [-]
      passes_counterflow : bool
          In addition to the overall flow direction, in some cases individual 
          passes may be in counter or parallel flow; this controlls that [-]
      reverse : bool
          Used **internally only** to allow cases like the 1-4 formula to work  
          for the 4-1 flow case, without having to duplicate the code [-]
  
      Returns
      -------
      NTU1 : float
          Thermal number of transfer units of the heat exchanger in the P-NTU 
          method, calculated with respect to stream 1 [-]
  
      Notes
      -----
      The defaults of counterflow=True and passes_counterflow=True will always
      result in the most efficient heat exchanger option, normally what is
      desired.
      
      If a number of passes which is not supported is provided, an exception is
      raised.
      
      For more details, see :obj:`temperature_effectiveness_plate`.
  
      Examples
      --------
      Three passes on side 1; one pass on side 2; two end passes in counterflow
      orientation.
      
      >>> NTU_from_P_plate(P1=0.5743, R1=1/3., Np1=3, Np2=1)
      0.9998336056060733
      '''
      NTU_min = 1E-11
      function = temperature_effectiveness_plate
      if Np1 == 1 and Np2 == 1 and counterflow:
          try:
              return -log((P1*R1 - 1.)/(P1 - 1.))/(R1 - 1.)
          except ValueError:
              raise ValueError('The maximum P1 obtainable at the specified R1 is %f at the limit of NTU1=inf.' %(1./R1))
          
      elif Np1 == 1 and Np2 == 1 and not counterflow:
          try:
              return log(-1./(P1*(R1 + 1.) - 1.))/(R1 + 1.)
          except ValueError:
              raise ValueError('The maximum P1 obtainable at the specified R1 is %f at the limit of NTU1=inf.' %Pp(1E10, R1))
      elif Np1 == 1 and Np2 == 2:
          NTU_max = 100.
      elif Np1 == 1 and Np2 == 3 and counterflow:
          NTU_max = 100.
      elif Np1 == 1 and Np2 == 3 and not counterflow:
          NTU_max = 100.
      elif Np1 == 1 and Np2 == 4:
          NTU_max = 100.
      elif Np1 == 2 and Np2 == 2:
          if counterflow and passes_counterflow:
              return NTU_from_P_plate(P1, R1, Np1=1, Np2=1, counterflow=True, 
                                      passes_counterflow=True)
          elif counterflow and not passes_counterflow:
              NTU_max = 100
          elif not counterflow and passes_counterflow:
              NTU_max = _NTU_max_for_P_solver(NTU_from_plate_2_2_parallel_counterflow, R1)
          elif not counterflow and not passes_counterflow:
              return NTU_from_P_plate(P1, R1, Np1=1, Np2=1, counterflow=False, 
                                      passes_counterflow=False)
      elif Np1 == 2 and Np2 == 3:
          if counterflow:
              NTU_max = 100
          elif not counterflow:
              NTU_max = _NTU_max_for_P_solver(NTU_from_plate_2_3_parallel, R1)
      elif Np1 == 2 and Np2 == 4:
          if counterflow:
              NTU_max = 100
          elif not counterflow:
              NTU_max = _NTU_max_for_P_solver(NTU_from_plate_2_4_parallel, R1)
      elif not reverse:
          # Proved to work by example
          P2 = P1*R1
          R2 = 1./R1
          NTU2 = NTU_from_P_plate(R1=R2, P1=P2, Np1=Np2, Np2=Np1,
                                  counterflow=counterflow, 
                                  passes_counterflow=passes_counterflow, 
                                  reverse=True)
          NTU1 = NTU2/R1
          return NTU1
      else:
          raise Exception('Supported number of passes does not have a formula available')
      return _NTU_from_P_solver(P1, R1, NTU_min, NTU_max, function, Np1=Np1, 
                                Np2=Np2, counterflow=counterflow, 
                                passes_counterflow=passes_counterflow)