Simulating storage tanks

Now that we have our utility formulas defined, we can move into modeling the environment. First, we will figure out how to write the storage tanks, as they are fairly simple constructs: all we need to figure out is the pressure of the fluid in the tanks, as well as the flow rate out due to gravity. In addition, we need to know the tank level, as that affects the pressure of the fluid.

The code for the tanks is shown next, followed by explanations:

# tank.py (part 1)
1 #!/usr/bin/env python3 2 """ 3 VirtualPLC tank.py 4 5 Purpose: Creates a generic Tank class for PLC-controlled SCADA systems. 6 7 Author: Cody Jackson 8 9 Date: 5/28/18 10 ################################# 11 Version 0.2 12 Added path extension to alleviate errors 13 Version 0.1 14 Initial build 15 """

Line 1 is the "she-bang" command for the OS. Lines 2-15 provide a brief description of the program and its version history:

# tank.py (part 2)
1 import sys 2 sys.path.extend(["/home/cody/PycharmProjects/VirtualPLC"]) 3 from Utilities import utility_formulas 4 import numbers 5 6 class Tank: 7 """Generic storage tank.""" 8 def __init__(self, name="", level=0.0, fluid_density=1.94, spec_gravity=1.0, outlet_diam=0.0, outlet_slope=0.0): 9 self.name = name 10 self.__level = float(level) # feet 11 self.fluid_density = fluid_density # slugs/ft3 12 self.spec_grav = spec_gravity 13 self.__tank_press = 0.0 14 self.flow_out = 0.0 15 self.pipe_diam = outlet_diam 16 self.pipe_slope = outlet_slope 17 self.pipe_coeff = 140

In this part, with lines 1 and 2, we import the sys module and use it to extend the system path (the locations where files are looked for) to add the project's root directory. Without this, the importation of some of the project modules, such as the utility formulas, will error out, as Python doesn't know exactly where they are located.

In lines 3 and 4, we import the previously written utility formulas, as well as the built-in numbers module. The numbers module provides for a hierarchy of abstract base classes of numeric objects. While it provides a number of useful features, the main thing we'll use it for in this program is to simply determine whether an argument is a number or not.

In line 6, we define our Tank class. Lines 9-17 define the parameters that will be used when an instance of Tank is created. The parameters defined are the following:

  • The tank instance name
  • The fluid level in the tank
  • The fluid's density and specific gravity, with default values for water
  • The fluid pressure as measured at the bottom of the tank (the static pressure)
  • The gravity flow rate of the fluid
  • The outlet pipe's diameter, slope, and roughness coefficient

The next code listings are all part of the Tank class, until we reach if __name__ == "__main__":

# tank.py (part 3)
1 @property 2 def static_tank_press(self): 3 """Return hydrostatic tank pressure.""" 4 return self.__tank_press 5 6 @static_tank_press.setter 7 def static_tank_press(self, level): 8 """Calculate the static fluid pressure based on tank level.""" 9 try: 10 if not isinstance(level, numbers.Number): 11 raise TypeError("Numeric values only.") 12 elif level <= 0: 13 self.__tank_press = 0.0 14 else: 15 self.__tank_press = utility_formulas.static_press(self.level, self.fluid_density) 16 except TypeError: 17 raise # Re-raise for testing

Starting with line 1, we define the static pressure property. With a Python property, whenever the specific variable is called without arguments, the current value of the variable is returned. To set this variable, we use the setter method in lines 6-17.

The setter method takes the tank level as an input argument and checks to ensure it is actually a number. If not, then an error is generated. Next, if the level is zero or less, the tank pressure is set to zero. Finally, if there is a fluid level in the tank, the pressure for that fluid level is calculated. Note that no value is returned; a setter method simply sets the value for a property but doesn't return anything:

# tank.py (part 4)
1 @property 2 def level(self): 3 """Return fluid level in tank.""" 4 return self.__level 5 6 @level.setter 7 def level(self, level): 8 """Set the level in the tank.""" 9 try: 10 if not isinstance(level, numbers.Number): 11 raise TypeError("Numeric values only.") 12 elif level <= 0: 13 self.__level = 0.0 14 else: 15 self.__level = level 16 except TypeError: 17 raise # Re-raise error for testing 18 finally: 19 self.static_tank_press = self.level 20 self.gravity_flow(self.pipe_diam, self.pipe_slope, self.pipe_coeff)

The preceding code listing creates the property and setter method for tank level. The operations are similar to fluid pressure, except a finally statement is provided to pass the tank level into the pressure setter, as well as calculate the gravitational flow rate based on the level:

# tank.py (part 5)
1 def gravity_flow(self, diameter, slope, pipe_coeff): 2 if self.level > 0: 3 self.flow_out = utility_formulas.gravity_flow_rate(diameter, slope, pipe_coeff) 4 else: 5 self.flow_out = 0.0 6 7 8 if __name__ == "__main__": 9 tank1 = Tank("tank1", 10) 10 print(tank1.level) 11 tank1.static_tank_press = tank1.level 12 print(tank1.static_tank_press) 13 tank1.level = 8.0 14 print(tank1.level) 15 tank1.static_tank_press = tank1.level 16 print(tank1.static_tank_press) 17 tank1.level = "a" 18 print(tank1.level)

In the preceding code listing, lines 1-5 comprise the method to calculate the gravitational flow rate. First, it checks that the tank level is greater than zero and, if true, it calculates the flow rate. Otherwise, it sets the flow rate to zero.

Finally, we have the simple tests in lines 8-18. These are not full-blown unit tests, but verification tests while writing the code.

The output of directly running the tank code is shown in following screenshot:

Tank code output
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset