Equations and algorithm for Reverse Osmosis systems normalization

Equations and algorithm for Reverse Osmosis systems normalization

By Daniel
Sun, 11/13/2016 - Updated 1 year ago
Posted in:

On this page you will find a normalization algorithm that can be built in the PLC or in a SCADA system, therefore eliminating the need of the spreadsheets. This algorithm will deliver the normalized results in three variables: Pressure Drop, Permeability and Salt Passage rate. It also can consider the Concentration Polarization factor for a higher precision.

Normalization is a conversion of operational data that allows the user to compare operation at a specific set of conditions to a reference set of conditions in Reverse Osmosis plants. This allows the user to determine whether changes in flow or rejection are caused by fouling, damage to the membrane, or are just due to different operating conditions: temperature, flow and recovery changes and feed water salinity variations.

The RO normalization equations are described in the ASTM D 4516 (2010) and most membrane manufactures distribute free spreadsheets that have slightly improved calculations over the described in the ASTM.


Water mass transport coefficient (A-Value) and Salt mass transport coefficient (B-value)

RO and NF membranes can be defined by two parameters known in the industry as A and B-values. The


  • A-Value represents the water permeability or the resulting flux from a specific driving pressure. The A-value is directly proportional to the Normalized Permeate Flow, most manufacturers recommend cleaning the membranes when those parameters drop 10% from the stabilized startup reference. A lower A-Value after a few months of operation most likely is an indication of fouling. A-Value is measured in GFD/psi or LMH/bar, in the International System: m²/(m².s.Pa).


  • B-Value is the salt diffusion rate through the membrane. Every salt has it's own B-value for a specific membrane according to it's chemical and physical properties and the Normalization model considers all salts as if they were NaCl. The B-value is directly proportional to the Normalized Salt Rejection, most manufacturers recommend cleaning the modules when those parameters increase 10%. A higher B-Value after a few months of operation might indicate fouling or membrane degradation (abrasion, oxidation, etc...). B-Value is measured in flux units: GFD or LMH, in the International System: m³/(m².s).


Why use permeability and salt diffusion rate and not Normalized Flow and Normalized Rejection?

By using the permeability (A-value) and salt diffusion rate (B-value) we can directly compare different plants and different membranes in the same scale. That's impossible using the ASTM terms since a plant can have a normalized flow of 100m³/h and another of 25m³/h.

The A and B-values are also consistent with membrane datasheets data as described in Equations and algorithm for Reverse Osmosis membranes comparison and scientific papers.


The algorithm

The algorithm below uses the equations provided by the membrane manufactures, it was calibrated for multiple element plant comparisons, for single elements please check Equations and algorithm for Reverse Osmosis membranes comparison.

This algorithm was implemented in Plutocalc Water so if you just need to compare performance you can try this software that works on any computer and also in mobile phones.

The algorithm can also take into consideration (optional) the concentration polarization factor for higher precision (thanks to LG). Please notice that the Concentration Polarization is not part of the ASTM D 4516 equation set.


Source code and equations

This source code was written in JavaScript but it can easily be ported to Java, C or any other language. If you need to see it in action please check the Plutocalc Water application.


  1. /* =============================================================
  2. / * RO normalization and plant comparison function for JavaScript
  3. * Developed by Daniel Brooke Peig ([email protected])
  4. *
  5. * Version 1.0
  6. *
  7. * http://www.danbp.org
  8. *
  9. * Copyright (C) 2016 Daniel Brooke Peig
  10. *
  11. /* =============================================================*/
  12. function ro_normalize(user_inputs){
  14. //Declare variables
  16. //Inputs - Declare and import
  17. //Convert for different user input units in this section
  18. var Tf = user_inputs.Tf; //Temperature (C)
  19. var Pf = user_inputs.Pf; //Feed Pressure (bar)
  20. var Pc = user_inputs.Pc; //Concentrate Pressure (bar)
  21. var Pp = user_inputs.Pp; //Permeate Pressure (bar)
  22. var Qp_m3h = user_inputs.Qp; //Product Flow (m³/h)
  23. var Qc_m3h = user_inputs.Qc; //Concentrate Flow (m³/h)
  24. var Uf = user_inputs.Uf; //Feed conductivity (micro-siemens/cm)
  25. var Up = user_inputs.Up; //Feed conductivity (micro-siemens/cm)
  26. var Area = user_inputs.Area; //Membrane Area (m²)
  27. var UsePolFact = user_inputs.UsePolFact; //Use (true) or don't use the polarization factor (false)
  29. //Calculation variables
  30. var Cf; //Feed concentration (mg/L as NaCl)
  31. var Cp; //Permeate concentration (mg/L as NaCl)
  32. var Rej; //Salt Rejection (%)
  33. var Qp; //Product flow (m³/s)
  34. var Qf; //Feed flow (m³/s)
  35. var Rec; //Recovery (%)
  36. var Beta; //Concentration polarization factor
  37. var TCF; //Temperature correction factor
  38. var CFR; //Average Feed/Concentrate Concentration Factor
  39. var Cfc; //Average Feed/Concentrate Concentration (mg/L)
  40. var Ofc; //Average Feed/Concentrate Osmotic Pressure (bar)
  41. var Op; //Permeate Osmotic Pressure (bar)
  42. var Pd; //Pressure Drop (bar)
  43. var NDP; //Net Driving Pressure (bar)
  44. var A_SI; //Water mass transport coefficient (m/(s.bar))
  45. var B_SI; //Salt mass transport coefficient (m/s)
  46. var A_lmhbar; //Water mass transport coefficient at 25°C (LMH/bar)
  47. var B_lmh; //Salt mass transport coefficient at 25°C (LMH)
  48. var SProp; //Solution properties table
  50. //System variables
  51. var ErrorLog;
  52. var user_outputs;
  53. var incomplete_inputs = false;
  55. //Data tables
  56. //none
  58. ErrorLog="";
  60. //Check for invalid inputs (negative or zero)
  61. if(
  62. Tf<=0||
  63. Pf<=0||
  64. Pc<=0||
  65. Pp<0||
  66. Qp_m3h<=0||
  67. Qc_m3h<=0||
  68. Uf<=0||
  69. Up<=0||
  70. Area<=0
  71. ) incomplete_inputs = true;
  73. //Calculations
  75. if(Tf>80) ErrorLog += "Temperature is too high for the current model. Max 80C.<BR>";
  76. if(Uf>7630) Cf = (0.0000000000801*Math.exp(Math.pow((-50.6458-Math.log(Uf)),2)/112.484)); //Conductivity to mg/L approximation by DOW FILMTEC FTNorm
  77. if(Uf<=7630) Cf = (7.7E-20*Math.exp(Math.pow((-90.4756-Math.log(Uf)),2)/188.884)); //Conductivity to mg/L approximation by DOW FILMTEC FTNorm
  78. if(Up>7630) Cp = (0.0000000000801*Math.exp(Math.pow((-50.6458-Math.log(Up)),2)/112.484)); //Conductivity to mg/L approximation by DOW FILMTEC FTNorm
  79. if(Up<=7630) Cp = (7.7E-20*Math.exp(Math.pow((-90.4756-Math.log(Up)),2)/188.884)); //Conductivity to mg/L approximation by DOW FILMTEC FTNorm
  80. if(Cf>100000) ErrorLog += "Concentration in the feed is higher than 10%, too much for the current osmotic pressure model.<BR>";
  81. if(Cp>100000) ErrorLog += "Concentration in the permeate is higher than 10%, too much for the current osmotic pressure model.<BR>";
  82. Rej = (1-Cp/Cf)*100; //Rejection in %
  83. Qp = Qp_m3h/3600; //Flow conversion
  84. Qf = (Qp_m3h+Qc_m3h)/3600; //Flow conversion
  85. Rec = Qp/Qf*100; //Recovery in %
  86. if(UsePolFact == true) {Beta = Math.pow(Math.exp(0.75*2*Rec/100/(2-Rec/100)),(1/8));} //Calculation from LG NanoH2O normalization spreadsheet
  87. else Beta = 1; //If not using the concentration polarization factor
  88. if(Tf>25)TCF = Math.exp(2640*(1/298-1/(273+Tf))); //Calculation according to DOW FILMTEC FTNorm
  89. if(Tf<=25)TCF = Math.exp(3020*(1/298-1/(273+Tf))); //Calculation according to DOW FILMTEC FTNorm
  90. CFR = (Math.log(1/(1-(Rec/100)))/(Rec/100)); //Calculation according to the ASTM D4516
  91. Cfc = Cf*CFR*Beta; //Average feed/concentrate concentration
  92. Ofc = 0.0385*Cfc*(Tf + 273.15)/(1000-(Cfc/1000))/14.5038; //Calculation according to DOW FILMTEC FTNorm
  93. Op = 0.0385*Cp*(Tf + 273.15)/(1000-(Cp/1000))/14.5038; //Calculation according to DOW FILMTEC FTNorm
  94. Pd = Pf-Pc; //Differential pressure
  95. NDP = Pf-Pd/2-Ofc+Op-Pp; //Net Driving Pressure
  96. A_SI = Qp/(Area*TCF*NDP); //International system: m³/(m².s.bar)
  97. B_SI = Cp/(Cfc*TCF*Area/Qp); //International system: m³/(m².s)
  98. A_lmhbar = A_SI*1000*3600; //Permeability at 25°C unit conversion: L/(m².h.bar) or LMH/bar
  99. B_lmh = B_SI*1000*3600; //Permeability at 25°C unit conversion: L/(m².h) or LMH
  101. //Output the results
  102. //Convert for different user output units in this section
  103. if(ErrorLog.length == 0 && incomplete_inputs == false)
  104. user_outputs = {
  105. ErrorLog: ErrorLog, //Error Messages
  106. A: A_lmhbar, //Water mass transport coefficient at 25°C (LMH/bar)
  107. B: B_lmh, //Salt mass transport coefficient at 25°C (LMH)
  108. NDP: NDP, //Net Driving Pressure (bar)
  109. Pd: Pd, //Differetial Pressure (bar)
  110. Rec: Rec, //Recovery (%)
  111. Rej: Rej, //Rejection (%)
  112. Ofc: Ofc, //Average feed/concentrate osmotic pressure
  113. Cp: Cp, //Permeate TDS
  114. Cf: Cf, //Feed TDS
  115. Beta: Beta //Concentration polarization factor
  116. };
  117. else
  118. user_outputs = {
  119. ErrorLog: ErrorLog,
  120. A: 0,
  121. B: 0,
  122. NDP: 0,
  123. Pd: 0,
  124. Rec: 0,
  125. Rej: 0,
  126. Ofc: 0,
  127. Cp: 0,
  128. Cf: 0,
  129. Beta: 0,
  130. };
  132. return user_outputs;
  134. } //End of the procedure
  136. /****************************************************************************************************************************/
  137. //Usage example - results should display in the browser console
  138. var user_inputs =
  139. {
  140. Tf: 32,
  141. Pf: 32,
  142. Pc: 28,
  143. Pp: 1,
  144. Qp: 50,
  145. Qc: 10,
  146. Uf: 20000,
  147. Up: 150,
  148. Area: 1860,
  149. UsePolFact: true
  150. };
  152. console.log(ro_normalize(user_inputs));