The CAPE-OPEN specifications are a set of standard interfaces that allows for an easy inter-operability between two process modelling environments and components. Let us say that we have written a custom implementation of a unit operation. There was no standard and uniform way of interfacing that unit operation with different process simulators say, Aspen plus and Pro-II. We would have to develop custom implementation for different process modeling environment. Consequently, we would need to first understand the specifications and interfaces of that environment. CAPE-OPEN specifications tries to solve this specific problem by providing a common specification that most environment adheres to.

You can know more about CAPE-OPEN specifications on the CAPE-OPEN Laboratories Network website. In this article we will explore how to really get started with development of our own simple unit operation as per CAPE-OPEN standards in .Net. Then we will try to integrate it with another process simulation and flowsheet environment.

Setting up the test environment

For the purpose of this tutorial, we will make use of a simple, open process simulation and flowsheet application that supports CAPE-OPEN specifications. Let us download and install COCO ( CAPE-OPEN to CAPE-OPEN simulation environment) from their download site. Let us then head over to our all programs menu and launch CAPE-OPEN flowsheet environment, COFE64. We will be developing a 64 bit version of our custom unit operation component.

COCO start menu

The COFE64 will open as shown in the below screenshot. A default empty flowsheet will remain open.

COFE (Cape open flowsheet environment)

You will find COFE64 similar to many other process simulation applications. We will now add a property package and compounds as shown below.

property package

Let us now add 3 material streams as shown below. Stream 1 & 2 will be our input stream and stream 3 will be our output stream. We will now define the 2 streams (1 & 2) with water as the sole component. We can just double click the stream and set the necessary parameters.

Material streams

streams

Once the above steps are complete, we will save our flowsheet. Unit operations in our flowsheet can be added by clicking the icon shown below and then choosing a unit operation block.

unit operation

However, this is where we would like to add our own custom unit operation component by registering that as a CAPE-OPEN component. This process simulation environment which is CAPE-OPEN compliant will then automatically be able to detect the registered CAPE-OPEN unit operation. Thus, it would make that available for us to use in the flowsheet. It would be great that we will be able to add our custom component to a 3rd party process simulation environment without even having or knowing its internal API or interface. Also any other process simulation application would be able to detect our new component if the application is CAPE-OPEN compliant.

Developing a CAPE-OPEN unit operation component

For this tutorial we will develop a mixer in .Net that mixes 2 streams. However, it keeps leaking a user defined percentage of total input flow. The leak percentage would be an input from user. We will call our component as “CustomMixer”.

We would need CAPE-OPEN standard interop assemblies. Also we can have ready made solution for COM interop and registration. All this can be downloaded from the colan site. The downloaded package contains all that we need.
Interop.CAPEOPEN100.dll – The interop assembly,
Mose.CapeOpenToolkit.UnitProxy.dll – Read wrapper over the interop assembly,
RegUnitAsm.exe – A simple utility to register our CAPE-OPEN component.

The package also contains a sample unit operation project and a wizard utility to generate such project. For this tutorial, however, we will refrain from using the wizard to really understand every aspect of the development. We would not like to depend upon auto generated sample code.

Now we are ready to startup our Visual Studio and create a class library by the name of “CustomMixer”. Let us keep the target .Net framework version to 3.5. Add references to the interop dll and the wrapper dll that are mentioned above.

Create our Mixer class that extends from CapeOpenUnitSimple. We would then be required to implement our own version of some methods in CapeOpenUnitSimple. Also add a GuidAttribute to the class for use in com registration.

[GuidAttribute("b4aeca84-418a-488e-80eb-80475fa3df1e")]
public class Mixer : CapeOpenUnitSimple
{
    protected override void OnCalculate()
    {
        //TODO: Add your custom calculation logic
        throw new NotImplementedException();
    }

    protected override void OnEdit()
    {
        //TODO: You can show your own custom form for editing the parameters
        throw new NotImplementedException();
    }

    protected override void OnInitialize()
    {
        //TODO: How to initialize our unit operation
        throw new NotImplementedException();
    }
}
protected override void OnInitialize()
{
    //describe basic info
    Name = "CustomMixer";
    Description = "Custom mixer unit";

    //add all inlet and outlet port to the unit operation (can be material or energy)
    Ports.Add(CapeOpenUnitPort.Create(CapeOpenPortType.Material, "Inlet1", "Inlet Port 1 of Mixer", CapeOpenPortDirection.Inlet));
    Ports.Add(CapeOpenUnitPort.Create(CapeOpenPortType.Material, "Inlet2", "Inlet Port 2 of Mixer", CapeOpenPortDirection.Inlet));
    Ports.Add(CapeOpenUnitPort.Create(CapeOpenPortType.Material, "Outlet1", "Outlet Port of Mixer", CapeOpenPortDirection.Outlet));

    //add any custom paramter that should be configurable for the unit operation
    Parameters.Add(DoubleParameter.Create("LeakagePct", "Percentage of leakage", CapeOpenParameterMode.ReadWrite, 0, 100, 10));
}

In OnInitialize, we are basically adding the basic info for the simulation application to use and the inlet/outlet stream information and our custom parameters.

protected override void OnCalculate()
{
    //Get the inlet/outlet objects
    var inlet1 = GetMaterialObject("Inlet1");
    var inlet2 = GetMaterialObject("Inlet2");
    var outlet = GetMaterialObject("Outlet1", false);

    //Get settings
    double leakage = GetDoubleValueSettings("LeakagePct");

    //Calculate enthalpy for each inlets
    inlet1.CalculateProperty(CapeOpenThermoMaterialPropertyType.Enthalpy, CapeOpenPhaseType.Overall, CapeOpenCalculationType.Mixture);
    inlet2.CalculateProperty(CapeOpenThermoMaterialPropertyType.Enthalpy, CapeOpenPhaseType.Overall, CapeOpenCalculationType.Mixture);

    //Calculate and set values for outlet
    //Note that the below calculations are just sample and not necessarily correct for a mixture unit operation

    //Pressure
    outlet.Pressure = (new double[] { inlet1.Pressure, inlet2.Pressure }).Max();
    //Total flow
    double totalFlow = inlet1.TotalFlow + inlet2.TotalFlow;
    //Outlet flow after leakage
    outlet.TotalFlow = (100 - leakage) * totalFlow / 100;
    //Total Enthalpy
    double enthalpy = inlet1.Enthalpy + inlet2.Enthalpy;
    //Outlet enthalpy after leakage
    outlet.Enthalpy = (100 - leakage) * enthalpy / 100;

    outlet.Fraction = inlet1.Fraction;

    //Calculate the equilibrium for outlet - flash the outlet
    outlet.CalculateEquilibrium(CapeOpenFlashType.PH);
}

Along with some of our utility methods to get the port data and our custom setting, the entire Mixer class would look like the following:-

using Mose.CapeOpenToolkit.UnitProxy;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Runtime.InteropServices;

namespace CustomMixer
{
    [GuidAttribute("b4aeca84-418a-488e-80eb-80475fa3df1e")]
    public class Mixer : CapeOpenUnitSimple
    {

        protected override void OnCalculate()
        {
            //Get the inlet/outlet objects
            var inlet1 = GetMaterialObject("Inlet1");
            var inlet2 = GetMaterialObject("Inlet2");
            var outlet = GetMaterialObject("Outlet1", false);

            //Get settings
            double leakage = GetDoubleValueSettings("LeakagePct");

            //Calculate enthalpy for each inlets
            inlet1.CalculateProperty(CapeOpenThermoMaterialPropertyType.Enthalpy, CapeOpenPhaseType.Overall, CapeOpenCalculationType.Mixture);
            inlet2.CalculateProperty(CapeOpenThermoMaterialPropertyType.Enthalpy, CapeOpenPhaseType.Overall, CapeOpenCalculationType.Mixture);

            //Calculate and set values for outlet
            //Note that the below calculations are just sample and not necessarily correct for a mixture unit operation

            //Pressure
            outlet.Pressure = (new double[] { inlet1.Pressure, inlet2.Pressure }).Max();
            //Total flow
            double totalFlow = inlet1.TotalFlow + inlet2.TotalFlow;
            //Outlet flow after leakage
            outlet.TotalFlow = (100 - leakage) * totalFlow / 100;
            //Total Enthalpy
            double enthalpy = inlet1.Enthalpy + inlet2.Enthalpy;
            //Outlet enthalpy after leakage
            outlet.Enthalpy = (100 - leakage) * enthalpy / 100;

            outlet.Fraction = inlet1.Fraction;

            //Calculate the equilibrium for outlet - flash the outlet
            outlet.CalculateEquilibrium(CapeOpenFlashType.PH);
        }

        protected override void OnEdit()
        {
            //TODO: You can show your own custom form for editing the parameters
        }

        protected override void OnInitialize()
        {
            //describe basic info
            Name = "CustomMixer";
            Description = "Custom mixer unit";

            //add all inlet and outlet port to the unit operation (can be material or energy)
            Ports.Add(CapeOpenUnitPort.Create(CapeOpenPortType.Material, "Inlet1", "Inlet Port 1 of Mixer", CapeOpenPortDirection.Inlet));
            Ports.Add(CapeOpenUnitPort.Create(CapeOpenPortType.Material, "Inlet2", "Inlet Port 2 of Mixer", CapeOpenPortDirection.Inlet));
            Ports.Add(CapeOpenUnitPort.Create(CapeOpenPortType.Material, "Outlet1", "Outlet Port of Mixer", CapeOpenPortDirection.Outlet));

            //add any custom paramter that should be configurable for the unit operation
            Parameters.Add(DoubleParameter.Create("LeakagePct", "Percentage of leakage", CapeOpenParameterMode.ReadWrite, 0, 100, 10));
        }

        /// <summary>
        /// Get the material object corresponding any material inlet/outlet port
        /// </summary>
        /// <param name="portName">Name of the port</param>
        /// <param name="duplicate">Duplicate the port or not</param>
        /// <returns>Material Object</returns>
        private CapeOpenThermoMaterialObject GetMaterialObject(string portName, bool duplicate = true)
        {
            //Get the material port
            MaterialPort matPort = (MaterialPort)(Ports.GetPortByName(portName));
            //Get the material object corresponding that port
            CapeOpenThermoMaterialObject matObject = (CapeOpenThermoMaterialObject)(matPort.ConnectedObject);
            //Return a duplicate of the material object for any calculation
            return duplicate ? matObject.DuplicatedObject : matObject;
        }

        /// <summary>
        /// Get any setting that is type double
        /// </summary>
        /// <param name="settingName">Setting name</param>
        /// <returns>setting value</returns>
        private double GetDoubleValueSettings(string settingName)
        {
            DoubleParameter setting = (DoubleParameter)(Parameters.GetParameterByName("LeakagePct"));
            return setting.Value;
        }

        /// <summary>
        /// Set any setting that is type double
        /// </summary>
        /// <param name="settingName">Setting name</param>
        /// <param name="value">Value</param>
        /// <returns>setting value</returns>
        private void SetDoubleValueSettings(string settingName, double value)
        {
            DoubleParameter setting = (DoubleParameter)(Parameters.GetParameterByName("LeakagePct"));
            setting.Value = value;
        }
    }
}

Let us also change some of the assembly info in Properties/AssemblyInfo.cs file of our project.

using Mose.CapeOpenToolkit.UnitProxy;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CustomMixer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CustomMixer")]
[assembly: AssemblyCopyright("Copyright ©  2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(true)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("58493265-6407-4235-b742-16cd3e799125")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

[assembly: CapeOpenUnitInfoAttribute(CapeOpenUnitInfoEnum.Name, "Custom Mixer")]
[assembly: CapeOpenUnitInfoAttribute(CapeOpenUnitInfoEnum.Description, "My own random mixer")]

And then we are done. Just build this project and go to the output folder. Copy over the com registration utility, RegUnitAsm.exe, that we downloaded to this folder. Head over to the command prompt and run the following command to register our unit operation dll. You can also add this command to the post build event of the project. “CustomMixer.Mixer” is our class name.

RegUnitAsm.exe CustomMixer.dll "CustomMixer.Mixer"

Once our unit operation is registered, we can reopen COFE64 application to test it. Open the flowsheet that we had previosly created and saved. As you can see, our custom mixer is now available in the unit operation selector dialog. Add the custom mixer and its inlet and outlet ports. Run the simulation and brilliant. Our custom mixer will calculate itself along with the leakage percent we set.

I hope this would be enough for you to get started. For any comments/questions, please comment below.


0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *