Introduction
No matter what line of business you are in, it is all about data, lots of data, data from every source possible. The data can be collected manually, i.e., by someone writing down information onto a sheet of paper, or by punching numbers into spreadsheets or data collectors, or it can be collected automatically using automation systems such as control system historians or siphoned off into other system by specialist data repositories.
As a oil producing platform, we have data everywhere, from basic water system pressures, diesel inventories, oil producing well test data, even data on the current weather conditions. And the majority of the data is collected. There has been many a time I have sat back and thought to myself, why are we collecting this, what benefit it is? We often collect data just in case it might be useful one day, and yes, that does happen.
We even spend vast amounts of money on systems just to do the collecting, and then as time advances, figure out what we can and will do with all the information.
This article is an example of how some basic mathematics and raw data was put to use in a real world example I implemented recently.
Background
Over the last few years, we have been upgrading and replacing our controls systems to DeltaV, which included replacing the field instrumentation that performs all the process measurement and control. The DeltaV system has its own local data historian which collects the process parameters and records any alarm/events and operator interaction. Now with the system being located in the middle of the North Sea, it doesn't make it very easy for the onshore engineering teams to access the data. We did implement operator stations in the onshore control room, but with the risk of a non qualified person accidentally shutting down the operation, access to these workstations are restricted, and there are many people wanting access to the process data.
Add to this that there is a DeltaV system installed on each of the other platforms in the field, as well as other standalone systems, e.g., gas compressors and PLC based control systems and you end up with data fragmented across the field. If you then start to interconnect these platforms with subsea pipelines for transferring Oil and Gas, Produced Water and Electricity around the field, it could become difficult to marry up the data across the field and see the direct interaction of all the disparate processes in realtime.
The solution to this problem was to then introduce a standalone specialist data historian. This is capable of sucking all the data required from the various systems and centralising it, and making it available to all and sundry who want the information. This gives you a key segregation/security layer between the live control systems and the data.
Figure 1: Forties Field Platform Layout and Infrastructure
Down to Business - Putting the Historian to Work
Here we are, spending millions every year getting the oil out of the ground. As you could imagine, any dowtime immediately starts to cost you in lost revenue. Two of the main issues that can very rapidly start to impact plant operations are corrosion of pipework and scale deposition within the pipework. To manage both of these factors, chemical injection plays a key role in ensuring the product stays in the pipes and can flow freely. See the picture and text contained in this article to see and read how quickly scale can form, it will surprise you! Not only does it cost you in lost revenue, but the cost of repair can also start to become really expensive.
As a method of mitigating the impact of both corrosion and scale, chemicals are injected into the process. There are many other things we use chemicals, for e.g., oxygen scavaging, demulsification, etc., but on my installation, the two main ones are the corrosion inhibitor and scale inhibitor. These chemicals are stored onboard and injected automatically.
Firstly, the operations team conduct regular well testing to determine the oil and water volumes produced, and this information is used by the onshore chemists to determine the optimum rate for injection of the relevant chemical. The chemicals are expensive and obviously over injecting can waste money, but can also have an adverse effect on the process, so getting this number right is important. This injection rate is then entered into the control system which then regulates the flow of the chemical. As stocks onboard the installation are depleted, new shipments are ordered and decanted into the local platform chemical storage vessels.
As chemistry management is of such a key importance, we record in the control system historian and the central historian the levels in the storage tank, as well as the injected flow rates. Great, now we are recording the data. What will we do with it? How can we make use of the data?
With the recorded injection flowrates, we can immediately see if the injected chemicals stop for any reason, maybe the injection quill has blocked up or the injection pump as failed. We could also in theory get a flow totaliser of the injected chemicals.
Along comes the onshore platform engineer and asks, "Can I calculate how much volume of chemical I have in the storage vessel at any given time? Can I also determine how much chemical we have used in the last fiscal 24hr period?" This information can then be used to see if the performance of the injection system is meeting expectations and also allow a cross check of utilisation versus procurement of chemical.
The engineer then duly hands me some equations he has obtained from the process engineers relating to standard vessel volume calculations and wants to know if these can be used in some way automatically.
Head Scratching Time - Putting It Together
From the engineering data, we have the vessel dimensions, the location information on the vessel of the level instrumentation and measured range limits of the level transmitters. Contained within the historian, we have the percentage of the measured range stored. Using basic mathematics, it is therefore possible to determine the volumes of chemical stored within the vessel. Let's take a look at this vessel;
Figure 2: Chemical Storage Vessel
As you can see in Figure 2, the vessel has 3 storage compartments, each one is independent, each one being for a different chemical. As can be seen, Compartment 1 consists of a dome end and a cylindrical section, Compartment 2 is just a cylindrical section. The Percentage shown in each compartment represents the historian value of the indicated level and a representative bar graph (ok, I admit, not quite to scale!) The engineering dimensional data we have available and required by the calculations are:
- D = The vessel internal diameter
- L = The cylindrical section length
- b = The dome section length
- Is = Instrument measured start point height
- Ih = Instrument measured height
Let's concentrate on Compartment 1, as this consists of both a Dome End and a Cylinder section, Compartment 2 would only need the cyclindrical section from the calculations.
The volume in the Compartment 1 can be summarised as; Volume Total = Volume End + Volume Cylinder; With all dimensional data used in the calculations in metres (m), the volume will be in cubic metres (m3), therefore;
where:
Now, we are only interested in partial volumes based on the liquid level, to achieve this, we need some additional equations:
Dome End Factor:
Cylinder Factor:
;
and:
;
Note: a is in radians
For both Factors,
Let's work an example just to make sure where are all in the same place! For Compartment 1, the dimensions are:
- Vessel ID: 2m
- Cylinder Length: 0.9m
- Dome Length: 0.408m
- Instrument start: 0.16m
- Instrument range: 1.84m
- Indicated Level: 60% [yes, I know it shows 45.7% in the sketch, but will stick to whole numbers!]
So, start by obtaining H1 = 0.16 + (1.84 * (60/100)) = 1.264m.
Volume in Cyclinder:
a = 2 * Atan(1.264 / v(((2 * 1.264 * 2)/2) -(1.264)2)) => 2 * Atan(1.31) = 1.84
F(Zc) = (1.84 - sin(1.84) * cos(1.84)) / p
F(Zc) = 0.67
Volume in Cyclinder = 1/4 p D2LF(Zc) => (22 * 0.90 * 0.67 * p) / 4 => 1.89m3
Volume in Dome:
K1 = b/D => 0.408/2 = 0.204
F(Ze) = -(1.264 / 2)2 * ( -3 + ((2*1.264)/2)) => -(0.399)*(-1.736) = 0.69
Volume in Dome End = 1/6 * p * 0.204 * 23 * 0.69 = 0.59m3
Total Volume = Dome + Cylinder = 1.89 + 0.59 = 2.48m3
Implementing in Code on the Historian Client Tools
Now that we have the necessary formula and worked data, we can concentrate on turning this into something useful.
The data historian that we purchased and implemented is an OSISoft PI system, and the client tool we used for this example is called ProcessBook. One thing I have noticed whilst working with this is how very little information is available on the net. Normally user groups sprout up and everyone shares code snippets, etc., but with these products, it is extremely hard to find anything. This was another reason for wanting to share this here, maybe help to get more of the user community sharing.
ProcessBook can be programmed using Visual Basic for Applications. When ProcessBook is installed, the PISystem SDK is installed with all the necessary libraries and services required to access the data stored within the PI historian. The user will configure connections to any number of PI Servers and also will logon as required with their security credentials which will then permit them access to their authorised data sets.
A new ProcessBook display was created which contains the graphics showing realtime data, display text and a button. When the user clicks the button, the VBA code is fired off, which obtains the current Level Indication used for the current snapshot volume. It also determines the last fiscal 24hour period, then obtains the historical level information and uses that to determine the overall volume of chemical used during that period.
The equations used above were broken down into functions that can be called within the code as required.
When the user clicks the buttons, it fires of the function DoCalcs()
;
Public Sub DoCalcs()
On Error GoTo Error_Handler
There are a few PI System specific objects that need to be defined to grab the data from the historian server, the server object and 2 data point objects:
Dim currentServer As Server
Dim scaleTag As PIPoint
Dim corrTag As PIPoint
Set currentServer = Servers.DefaultServer
Set scaleTag = currentServer.PIPoints.Item("LI1T605E/PV.CV")
Set corrTag = currentServer.PIPoints.Item("LI1T604E/PV.CV")
Next, we go and grab the current snapshot realtime value which we can then use to determine the current snapshot volume in the compartments:
Dim scaleLevel As Single: scaleLevel = CSng(scaleTag.Data.Snapshot.Value)
TextScaleSnapshot.Contents = scaleLevel
TextScaleSnapshotTime.Contents = scaleTag.Data.Snapshot.TimeStamp.LocalDate
Dim scaleVol As Single
scaleVol = CalculateScaleVolume(scaleLevel) * 1000
textScaleSnapshotVolume.Contents = scaleVol
Dim corrLevel As Single: corrLevel = CSng(corrTag.Data.Snapshot.Value)
TextCorrSnapshot.Contents = corrLevel
TextCorrSnapshotTime.Contents = corrTag.Data.Snapshot.TimeStamp.LocalDate
Dim corrVol As Single
corrVol = CalculateCorrossionVolume(corrLevel) * 1000
textCorrSnapshotVolume.Contents = corrVol
The next part of the code determines the previous fiscal 24hour period. On our installation, we have a reporting fiscal period of 18:00hrs to 18:00hrs, i.e. a Day starts at 6pm in the evening and runs through the night until the next day's 6pm.
Dim fiscalPeriodStartDate As Date
Dim fiscalPeriodEndDate As Date
fiscalPeriodStartDate = returnPreviousStartDate(CDate_
(scaleTag.Data.Snapshot.TimeStamp.LocalDate))
fiscalPeriodEndDate = returnPreviousFinishDate(CDate_
(scaleTag.Data.Snapshot.TimeStamp.LocalDate))
textPeriodStartDate.Contents = fiscalPeriodStartDate
textPeriodFinishDate.Contents = fiscalPeriodEndDate
The recorded data values are grabbed from the historian from the fiscal period, and the start and end values are used to calculate the volumes used during that fiscal period. The historian will interpolate the data it has and provide a value for the start time and end time, if an exact reading does not exist at that instance in time. It then updates the user display with the information it has and how many data values have been found for the period. It repeats this for both the compartments (Scale and Corrosion).
Dim scaleValues As PIValues
Set scaleValues = scaleTag.Data.RecordedValues_
(CDate(textPeriodStartDate.Contents), CDate(textPeriodFinishDate.Contents), btAuto)
If scaleValues.Count > 0 Then
TextScaleRecordCount.Contents = scaleValues.Count & _
" Recorded values for fiscal period."
Dim scalePeriodStartValue As Single: scalePeriodStartValue = _
scaleValues.Item(1).Value
Dim scalePeriodFinishValue As Single: scalePeriodFinishValue = _
scaleValues.Item(scaleValues.Count).Value
TextScalePeriodStartValue.Contents = scalePeriodStartValue
TextScalePeriodStartTime.Contents = scaleValues.Item(1).TimeStamp.LocalDate
textScalePeriodStartVolume.Contents = _
CalculateScaleVolume(scalePeriodStartValue) * 1000
TextScalePeriodFinishValue.Contents = scalePeriodFinishValue
TextScalePeriodFinishTime.Contents = _
scaleValues.Item(scaleValues.Count).TimeStamp.LocalDate
textScalePeriodFinishVolume.Contents = _
CalculateScaleVolume(scalePeriodFinishValue) * 1000
textScalePeriodVolume.Contents = Val(textScalePeriodStartVolume.Contents) - _
Val(textScalePeriodFinishVolume.Contents)
Else
TextScaleRecordCount.Contents = "0 Recorded values for fiscal period."
TextScalePeriodStartValue.Contents = "0"
TextScalePeriodStartTime.Contents = "No Data"
textScalePeriodStartVolume.Contents = "0"
TextScalePeriodFinishValue.Contents = "0"
TextScalePeriodFinishTime.Contents = "No Data"
textScalePeriodFinishVolume.Contents = "0"
textScalePeriodVolume.Contents = "0"
End If
Dim corrValues As PIValues
Set corrValues = corrTag.Data.RecordedValues(CDate(textPeriodStartDate.Contents), _
CDate(textPeriodFinishDate.Contents), btAuto)
If corrValues.Count > 0 Then
Dim corrPeriodStartValue As Single: corrPeriodStartValue = corrValues.Item(1).Value
Dim corrPeriodFinishValue As Single: corrPeriodFinishValue = _
corrValues.Item(corrValues.Count).Value
TextCorrRecordCount.Contents = corrValues.Count & _
" Recorded values for fiscal period."
TextCorrPeriodStartValue.Contents = corrPeriodStartValue
TextCorrPeriodStartTime.Contents = corrValues.Item(1).TimeStamp.LocalDate
textCorrPeriodStartVolume.Contents = _
CalculateCorrossionVolume(corrPeriodStartValue) * 1000
TextCorrPeriodFinishValue.Contents = corrPeriodFinishValue
TextCorrPeriodFinishTime.Contents = _
corrValues.Item(corrValues.Count).TimeStamp.LocalDate
textCorrPeriodFinishVolume.Contents = _
CalculateCorrossionVolume(corrPeriodFinishValue) * 1000
textCorrPeriodVolume.Contents = Val(textCorrPeriodStartVolume.Contents) - _
Val(textCorrPeriodFinishVolume.Contents)
Else
TextCorrRecordCount.Contents = "0 Recorded values for fiscal period."
TextCorrPeriodStartValue.Contents = "0"
TextCorrPeriodStartTime.Contents = "No Data"
textCorrPeriodFinishVolume.Contents = "0"
TextCorrPeriodFinishValue.Contents = "0"
TextCorrPeriodFinishTime.Contents = "No Data"
textCorrPeriodFinishVolume.Contents = "0"
textCorrPeriodVolume.Contents = "0"
End If
Exit Sub
Error_Handler:
MsgBox "Error Calculating: " & Err.Description
End Sub
Two helper functions for calculating the fiscal period start and end date/time:
Private Function returnPreviousStartDate(snapshotdate As Date) As Date
Dim workingdatetime As Date
workingdatetime = CDate(Format(snapshotdate, "YYYY-MMM-DD") & " 18:00:00")
If DateDiff("s", snapshotdate, workingdatetime) <= 0 Then
returnPreviousStartDate = DateAdd("d", -1, workingdatetime)
Else
returnPreviousStartDate = DateAdd("d", -2, workingdatetime)
End If
End Function
Private Function returnPreviousFinishDate(snapshotdate As Date) As Date
Dim workingdatetime As Date
workingdatetime = CDate(Format(snapshotdate, "YYYY-MMM-DD") & " 18:00:00")
If DateDiff("s", snapshotdate, workingdatetime) <= 0 Then
returnPreviousFinishDate = workingdatetime
Else
returnPreviousFinishDate = DateAdd("d", -1, workingdatetime)
End If
End Function
We then have the 2 calculation functions, one for the Scale
(which has the dome end) and one for the Corrosion
:
Private Function CalculateCorrossionVolume(theLevel As Single) As Single
On Error GoTo Error_Handler
Dim result As Single: result = 0
Dim cylLength As Single: cylLength = 2.8
Dim cylDiam As Single: cylDiam = 2
Dim instStart As Single: instStart = 0.16
Dim instLength As Single: instLength = 1.84
Dim pi As Single: pi = 3.14159265358979
Dim level As Single: level = theLevel
level = instStart + (instLength * (level / 100))
result = (Zc(level, cylDiam) * cylLength * (cylDiam) ^ 2 * pi) / 4
CalculateCorrossionVolume = result
Exit Function
Error_Handler:
CalculateCorrossionVolume = 0
MsgBox "Error with Corrossion Calc: " & Err.Description
End Function
Private Function CalculateScaleVolume(theLevel As Single) As Single
On Error GoTo Error_Handler
Dim cylResult As Single: cylResult = 0
Dim domeResult As Single: domeResult = 0
Dim cylLength As Single: cylLength = 0.9
Dim cylDiam As Single: cylDiam = 2
Dim instStart As Single: instStart = 0.16
Dim instLength As Single: instLength = 1.84
Dim domeLength As Single: domeLength = 0.408
Dim pi As Single: pi = 3.14159265358979
Dim level As Single: level = theLevel
level = instStart + (instLength * (level / 100))
cylResult = (Zc(level, cylDiam) * cylLength * (cylDiam) ^ 2 * pi) / 4
domeResult = (Ze(level, 2) * (cylDiam ^ 3) * (domeLength / cylDiam) * pi) / 6
CalculateScaleVolume = cylResult + domeResult
Exit Function
Error_Handler:
CalculateScaleVolume = 0
MsgBox "Error with Scale Calc: " & Err.Description
End Function
The function below is used to determine a factor used for the partial volume in the dome end and takes the liquid height and vessel diameter as input values:
Private Function Ze(ByVal liquidHeight As Single, ByVal vesselDiam As Single) As Single
Dim result As Single
result = 0 - ((liquidHeight / vesselDiam) ^ 2)
result = result * (-3 + ((2 * liquidHeight) / vesselDiam))
Ze = result
End Function
The function below is used to determine a factor used for the partial volume in the cylinder and takes the liquid height and vessel diameter as input values:
Private Function Zc(ByVal liquidHeight As Single, ByVal vesselDiam As Single) As Single
Dim result As Single
Dim alpha As Single
Dim pi As Single: pi = 3.14159265358979
alpha = getAlpha(liquidHeight, vesselDiam)
result = (alpha - (Sin(alpha) * Cos(alpha))) / pi
Zc = result
End Function
The function below is one used to calculate a and takes the liquid height and vessel diameters as input values:
Private Function getAlpha(ByVal liquidHeight As Single, _
ByVal vesselDiam As Single) As Single
Dim result As Single
result = Sqr(((2 * liquidHeight * vesselDiam) / 2) - (liquidHeight ^ 2))
result = liquidHeight / result
result = CSng(2 * Atn(CDbl(result)))
getAlpha = result
End Function
What We End Up With
So when the user clicks the buttons and the code runs, the graphical components are updated with the information required. Below is a snapshot for the display I implemented, the code also converts from the m3 to litres, as this helps to tie up the injection rates against the usage rates.
Figure 3: ProcessBook Display Implementation
So there you have it, how to make use of historical data to help manage your chemical policies, complete with some code on how to grab this data if you are using an Enterprise class OSISoft PI Historian and ProcessBook client tools.
Points Of Interest
The Google Chart API was used to create the images for the formula, see here for more information.
History
- 23rd January, 2011 - Replaced text formula with images
- 15th January, 2011 - First article release