Main

Feedforward or Cascade Control

Consider a cylindrical tank with an adjustable inlet flow that is controlled by a valve. The inlet flow rate is not currently measured but there is a level measurement that shows how much fluid has been added to the tank. The outlet flow is also not currently measured and the outlet pump flow is adjusted manually by an operator. The outlet flow is a combination of the pumped water outlet and an unmeasured leak. A new flow transmitter has been acquired to improve the liquid level control and reject either input pressure disturbances `\Delta P_i` or outlet flow disturbances `\dot m_{out}`. Evaluate the performance difference by installing the flow transmitter on the inlet flow line or installing the flow transmitter on the outlet flow line. For the inlet flow line installation, develop a feedforward and cascade controller. For the outlet flow line installation, add a feedforward element to the level controller. The objective of this exercise is to develop a controller that can maintain a certain water level by automatically adjusting the inlet flow rate. Comment on the performance differences for cases A-C.

  • Case A: Install flow transmitter on inlet flow line and use as a feedforward to the level controller.
  • Case B: Install flow transmitter on inlet flow line and develop flow controller as an inner loop to the level controller.
  • Case C: Install flow transmitter on outlet flow line and use as a feedforward to the level controller.

Note: The symbol LT is an abbreviation for Level Transmitter and FT for Flow Transmitter. The symbol LC is an abbreviation for Level Controller.

A beginning model is used below where the process equation is derived from a mass balance.

$$ \rho \; A \; \frac{dh}{dt} = \dot m_{in} - \dot m_{out} \quad \mathrm{with} \quad \dot m_{in} = \rho \; C_v \; lift \; \sqrt{\frac{\Delta P_{in}}{g_s}}$$

where `C_v=10^{-4}` relates valve opening and pressure drop `\Delta P_i` to inlet flow. The outlet water flow is a combination of pumped water rate and an unmeasured leak with a mass flow that is approximately 5.0 times the height of the tank. Water has a specific gravity `g_s` of 1.0. Use a PI controller for the tank to maintain a level set point of 1.0 m. Test the PI controller with `K_c=20.0` and `tau_I=50.0` with feedforward and cascade control for a simulation period of 900 seconds (15 minutes). Use a value of 1000 kg/m3 for density and 0.5 m2 for the cross-sectional area of the tank. Assume a linear valve. Make sure that the valve does not exceed the limits of 0-100 percent by clipping the requested valve opening to an acceptable range. For example, if the PI controller calculates a valve opening of 150 percent, use 100 percent instead. Use the disturbance values for inlet pressure and outlet pressure included with the starting script to evaluate the effectiveness of the controller options. Create a plot of set point and level for each of the three cases.

Step Response Simulation

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import random

# define tank model
def tank(Level,time,valve,DeltaP,FlowOut):
    Cv = 0.0001     # valve size
    rho = 1000.0 # water density (kg/m^3)
    A = 0.5      # tank area (m^2)
    gs = 1.0     # specific gravity
    # inlet mass flow
    FlowIn = rho * Cv * valve * np.sqrt(DeltaP/gs)
    # leak outlet flow
    LeakOut = 5.0*Level
    # calculate derivative of the Level
    if Level <= 0.0:
        dLevel_dt = 0.0 # for drained tank
    else:
        dLevel_dt = (FlowIn-FlowOut-LeakOut)/(rho*A)
    return dLevel_dt

tf = 3000.0           # final time
n = int(tf + 1) # number of time points

# time span for the simulation, cycle every 0.1 sec
ts = np.linspace(0,tf,n)
delta_t = ts[1] - ts[0]

# disturbances
DP = np.ones(n)*12.0
DP[1000:] = 22.0
Fout = np.ones(n)*2.0
Fin = np.zeros(n)

# Desired level (set point)
SP = 1.0
# level initial condition
Level0 = SP

# initial valve position
valve = 20.0
# Controller bias
ubias = valve
# valve opening (0-100%)
u = np.ones(n) * valve

# for storing the results
z = np.ones(n)*Level0

Cv = 0.0001     # valve size
rho = 1000.0 # water density (kg/m^3)
A = 0.5      # tank area (m^2)
gs = 1.0     # specific gravity

# simulate with ODEINT
for i in range(n-1):

    # inlet mass flow
    Fin[i] = rho * Cv * valve * np.sqrt(DP[i]/gs)

    #if i==500:
    #    valve = 30.0
    u[i+1] = valve   # store the valve position
    y = odeint(tank,Level0,[0,0.1],args=(valve,DP[i],Fout[i]))
    Level0 = y[-1] # take the last point
    z[i+1] = Level0 # store the level for plotting
Fin[-1] = Fin[-2]

# plot results
plt.figure()
plt.subplot(4,1,1)
plt.plot(ts,z,'b-',linewidth=3,label='level')
plt.ylabel('Tank Level')
plt.legend(loc='best')
plt.subplot(4,1,2)
plt.plot(ts,u,'r--',linewidth=3,label='valve')
plt.ylabel('Valve')    
plt.legend(loc=1)
plt.subplot(4,1,3)
plt.plot(ts,Fout,'b--',linewidth=3,label='Outlet Flow (kg/sec)')
plt.plot(ts,DP,'r:',linewidth=3,label='Inlet Pressure (bar)')
plt.ylabel('Disturbances')    
plt.legend(loc=1)
plt.subplot(4,1,4)
plt.plot(ts,Fin,'k:',linewidth=3,label='Inlet Flow (kg/sec)')
plt.xlabel('Time (sec)')
plt.legend(loc=1)

plt.show()

PI Control Simulation

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import random

# define tank model
def tank(Level,time,valve,DeltaP,FlowOut):
    Cv = 0.0001     # valve size
    rho = 1000.0 # water density (kg/m^3)
    A = 0.5      # tank area (m^2)
    gs = 1.0     # specific gravity
    # inlet mass flow
    FlowIn = rho * Cv * valve * np.sqrt(DeltaP/gs)
    # leak outlet flow
    LeakOut = 5.0*Level
    # calculate derivative of the Level
    if Level <= 0.0:
        dLevel_dt = 0.0 # for drained tank
    else:
        dLevel_dt = (FlowIn-FlowOut-LeakOut)/(rho*A)
    return dLevel_dt

tf = 900.0           # final time
n = int(tf + 1) # number of time points

# time span for the simulation, cycle every 0.1 sec
ts = np.linspace(0,tf,n)
delta_t = ts[1] - ts[0]

# disturbances
DP = np.zeros(n)
Fout = np.ones(n)*2.0

# Desired level (set point)
SP = 1.0
# level initial condition
Level0 = SP

# initial valve position
valve = 30.0
# Controller bias
ubias = valve
# valve opening (0-100%)
u = np.ones(n) * valve

# for storing the results
z = np.ones(n)*Level0
es = np.zeros(n)
P = np.zeros(n)   # proportional
I = np.zeros(n)   # integral
ie = np.zeros(n)

# Controller tuning
Kc = 20.0
tauI = 50.0

# simulate with ODEINT
for i in range(n-1):
    # inlet pressure (bar) disturbance
    DP[i] = np.sin(ts[i]/10.0)* 10.0 + 12.0
    # outlet flow (kg/sec) disturbance (change every 10 seconds)
    if np.mod(i+1,500)==100:
        Fout[i] = Fout[i-1] + 10.0
    elif np.mod(i+1,500)==350:
        Fout[i] = Fout[i-1] - 10.0
    else:
        if i>=1:
            Fout[i] = Fout[i-1]

    # PI controller
    # calculate the error
    error = SP - Level0
    P[i] = Kc * error
    if i >= 1:  # calculate starting on second cycle
        ie[i] = ie[i-1] + error * delta_t
        I[i] = (Kc/tauI) * ie[i]
    valve = ubias + P[i] + I[i]
    valve = max(0.0,valve)   # lower bound = 0
    valve = min(100.0,valve) # upper bound = 100
    if valve > 100.0:  # check upper limit
        valve = 100.0
        ie[i] = ie[i] - error * delta_t # anti-reset windup
    if valve < 0.0:    # check lower limit
        valve = 0.0
        ie[i] = ie[i] - error * delta_t # anti-reset windup

    u[i+1] = valve   # store the valve position
    es[i+1] = error  # store the error
    y = odeint(tank,Level0,[0,0.1],args=(valve,DP[i],Fout[i]))
    Level0 = y[-1] # take the last point
    z[i+1] = Level0 # store the level for plotting
Fout[n-1] = Fout[n-2]
DP[n-1] = DP[n-2]

# plot results
plt.figure()
plt.subplot(4,1,1)
plt.plot(ts,z,'b-',linewidth=3,label='level')
plt.plot([0,max(ts)],[SP,SP],'k--',linewidth=2,label='set point')
plt.ylabel('Tank Level')
plt.legend(loc=1)
plt.subplot(4,1,2)
plt.plot(ts,u,'r--',linewidth=3,label='valve')
plt.ylabel('Valve')    
plt.legend(loc=1)
plt.subplot(4,1,3)
plt.plot(ts,es,'k:',linewidth=3,label='error')
plt.plot(ts,ie,'r:',linewidth=3,label='int err')
plt.legend(loc=1)
plt.ylabel('Error = SP-PV')    
plt.subplot(4,1,4)
plt.plot(ts,Fout,'b--',linewidth=3,label='Outlet Flow (kg/sec)')
plt.plot(ts,DP,'r:',linewidth=3,label='Inlet Pressure (bar)')
plt.ylabel('Disturbances')    
plt.xlabel('Time (sec)')
plt.legend(loc=1)

plt.show()