Base valve class

Before we can do anything with individual valves, we have to create a basic Valve class that the unique valves will inherit from. This base class will have to have as many characteristics as each valve type will need to make the base class as generic as possible. The base valve class will be broken up into multiple code listings:

# valve.py (part 1)
1 #!/usr/bin/env python3 2 """ 3 VirtualPLC valve.py 4 5 Purpose: Creates a generic Valve class for PLC-controlled SCADA systems. 6 7 Classes: 8 Valve: Generic superclass 9 Gate: Valve subclass; provides for an open/close valve 10 Globe: Valve subclass; provides for a throttling valve 11 Relief: Valve subclass; provides for a pressure-operated open/close valve 12 13 Author: Cody Jackson 14 15 Date: 4/9/18 16 ################################# 17 Version 0.1 18 Initial build 19 """

We've seen lines 1-19 before in the tank.py program; they simply provide some basic information about the program.

# valve.py (part 2)
1 import math 2 3 class Valve: 4 """Generic class for valves. 5 6 Cv is the valve flow coefficient: number of gallons per minute at 60F through a fully open valve with a press. drop of 1 psi. For valves 1 inch or less in diameter, Cv is typically < 5. 7 """ 8 def __init__(self, name="", sys_flow_in=0.0, sys_flow_out=0.0, drop=0.0, position=0, flow_coeff=0.0, press_in=0.0): 9 """Initialize valve.""" 10 self.name = name 11 self.__position = int(position) # Truncate float values for ease of calculations 12 self.Cv = float(flow_coeff) 13 self.flow_in = float(sys_flow_in) 14 self.deltaP = float(drop) 15 self.flow_out = float(sys_flow_out) 16 self.press_out = 0.0 17 self.press_in = press_in

For this file, we're also importing the math module in line 1. Line 3 is where we start the generic base Valve class. The docstring in lines 4-7 provides some basic information to the user of the program. Lines 8-17 define the initialization parameters for a valve. Specifically, we set the following:

  • We set the valve's name; not required but helpful when attempting to identify valves programatically.
  • We set the valve's position as an integer, where 0 = closed and 100 = fully open. An integer value allows us to show the throttling value for a valve, such as 30% open.
  • We set the valve's flow coefficient (Cv), which affects the pressure drop seen across the valve from inlet to outlet.
  • We set the fluid flow rate into and out of the valve.
  • We set the pressure drop across the valve.
  • We set the fluid pressure coming into and leaving the valve.

Following code snippet demonstrates valve.py (part 3):

# valve.py (part 3)
1 def calc_coeff(self, diameter): 2 """Roughly calculate Cv based on valve diameter.""" 3 self.Cv = 15 * math.pow(diameter, 2) 4 5 def press_drop(self, flow_out, spec_grav=1.0): 6 """Calculate the pressure drop across a valve, given a flow rate. 7 8 Pressure drop = ((system flow rate / valve coefficient) ** 2) * spec. gravity of fluid Cv of valve and flow rate of system must be known. 9 10 Specific gravity of water is 1. 11 """ 12 try: 13 x = (flow_out / self.Cv) 14 self.deltaP = math.pow(x, 2) * spec_grav 15 except ZeroDivisionError: 16 return "The valve coefficient must be > 0."

Line 1 defines a method to calculate the valve's flow coefficient. Normally, this value is provided by the manufacturer, but for simulations that don't require exact values, this method allows us to approximate Cv based on the diameter of the valve.

Line 5 is the method to calculate the pressure drop across a valve. The pressure across the valve is a function of the flow rate and Cv. It's not as simple as using Bernoulli's equation, as the flow coefficient is dependent on the valve structure; a gate valve has a much higher Cv compared to a globe valve:

# valve.py (part 4)
1 def valve_flow_out(self, flow_coeff, press_drop, spec_grav=1.0): 2 """Calculate the system flow rate through a valve, given a pressure drop. 3 4 Flow rate = valve coefficient / sqrt(spec. grav. / press. drop) 5 """ 6 try: 7 if flow_coeff <= 0 or press_drop <= 0: 8 raise ValueError("Input values must be > 0.") 9 else: 10 x = spec_grav / press_drop 11 self.flow_out = flow_coeff / math.sqrt(x) 12 return self.flow_out 13 except ValueError: 14 raise # Re-raise error for testing

Line 1 defines a method to calculate the outlet flow from a valve. This flow is dependent on the valve's Cv, the pressure drop across the valve, and the specific gravity of the fluid. The method ensures that both Cv and pressure drop are not less than 1; otherwise, an error is generated.

An interesting exception use is shown in lines 13 and 14. ValueError was raised in line 8 and has an error message that will be printed to the console when the exception is thrown. However, while it is caught in line 13, it is re-raised in line 14. Re-raising the error isn't normally necessary, but when we write unit tests, it is a useful characteristic to test for, as it ensures we are capturing the correct exception. As we haven't talked about testing yet, feel free to ignore this for now:

# valve.py (part 5)
1 def get_press_out(self, press_in): 2 """Get the valve outlet pressure, calculated from inlet pressure.""" 3 if press_in: 4 self.press_in = press_in # In case the valve initialization didn't include it, or the value has changed 5 self.press_drop(self.flow_out) 6 self.press_out = self.press_in - self.deltaP 7 8 @property 9 def position(self): 10 """Get position of valve, in percent open.""" 11 return self.__position

Line 1 is the start of the method to calculate outlet pressure. The method is "intelligent" enough to check for the inlet pressure and reset it to the current value; this ensures the calculation is correct, based on the current system operating parameters.

Line 8 is the start of the property method that provides for the valve's position, a measure of percentage open:

# valve.py (part 6)
1 @position.setter 2 def position(self, new_position): 3 """Change the valve's position. 4 5 If new position is not an integer, an error is raised. 6 """ 7 try: 8 if type(new_position) != int: 9 raise TypeError("Integer values only.") 10 else: 11 self.__position = new_position 12 except TypeError: 13 raise # Re-raise for testing

The preceding code listing is the start of the valve position setter method. It checks to ensure that the value input is an integer and notifies the user if not. We could have just truncated floating-point numbers, but this provides a little bit of backup to remind the user to double-check the values being input:

# valve.py (part 7)
1 def open(self): 2 """Open the valve""" 3 self.__position = 100 4 self.flow_out = self.flow_in 5 self.press_out = self.press_in 6 7 def close(self): 8 """Close the valve""" 9 self.__position = 0 10 self.flow_out = 0 11 self.press_out = 0 12 self.deltaP = 0

Lines 1-12 provide easy ways to set a valve to be fully open or closed, as they not only set the position value but also set the outlet flow rates and pressure values for the valve at the same time.

This is it for the parent valve class. The following subsections will describe the code for specific valve children.

..................Content has been hidden....................

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