The control frameworks for the Unit Pool Allocation control and the Transfer to/from Unit Pool control in the new INOSIM version 13 have changed. The changes offer some new possibilities to use the controls. Even though they offer new features, you do not have to change the way to write these controls. Here we want to show what has changed and what the benefits of the new behavior of the controls are.
Unit Pool Allocation control
In INOSIM, Unit Pool Allocation controls are used to decide which unit out of a pool should be allocated by a UnitProcedure. With the new INOSIM version, the functionality of this control is improved. In version 12, the framework for the Unit Pool Allocation control looked like this:
Function Sample_UnitPoolAllocation(proc As ProcedureInstance, u As Unit) As Boolean ' Always allocate the requested unit Sample_UnitPoolAllocation = True End Function
The control has two input arguments: the ProcedureInstance calling the control and the unit it tries to allocate. The return value of the new control is a Boolean that allows the proposed unit to be allocated. When the control is called, it proposes the first available unit (u) in the pool. Within your control, you can decide if a unit is accepted or not. If you decline a unit, the control is called again for the next available unit in the pool. This behavior can result in many calls of the control, even though you know right away which unit must be allocated.
In Version 13, this behavior has been improved. The new framework looks like the following:
Sub Sample_UnitPoolAllocation(proc As ProcedureInstance, p As UnitPool, _ u As Unit, ByRef allocate As Boolean) End Sub
First thing you notice is that the new control is, like other controls in INOSIM, a sub instead of a function. Instead of setting the return value of the control to true or false to accept or decline a unit, the variable allocate is used. The main improvement is that in the first call of the control it is possible to decide whether a unit can be allocated for the procedure by setting the PendingUnitsMode of the procedure for the members of the unit pool. The PendingUnitsMode can be set to puAllocate, puIgnore and puDefault. For puAllocate, the unit is used for the Procedure Instance, with puIgnore it is not used. Units with the PendingUnitsMode puDefault are treated like in Version 12, the control is called for all units with this PendingUnitsMode until a unit is accepted for the procedure. Differently than in Version 12, where u is the first proposed unit in the first call, the first call of the control in Version 13 can be identified when the argument u is nothing. Additionally, the unit pool for which the control is called has been introduced as a new argument of the control. This makes it easier to access the members of the unit pool.
Example: Multi-Product Plant
We will have a look at a multi-product plant as an example for the benefits of the new control. In our example, we have a plant which produces 20 different products and for these products has 20 different product tanks. In the model, the production of each product takes one day. After the production, the product is filled into trucks. The same Allocation Control is used for choosing the tank within the production and for choosing the tank that is emptied by a truck.
Layout for the example. There is one production line which produces 20 different products. The products are stored in 20 different tanks.
Both, the production orders and the filling orders, do have the material which they are currently producing as a property. In this example, the products and tanks are numbered by 1 to 20. So, the tank can be chosen according to the product’s name. The behavior of the control is like in Version 12. For every order, the control is called for the members of the tank pool until the fitting unit is found:
Sub Product_Tank_V12(proc As ProcedureInstance, p As UnitPool, _ u As Unit, ByRef allocate As Boolean) If u Is Nothing Then Exit Sub End If 'skipping the first call of the control to have the same behavior as in Version 12 If u.Name="Tank " & Right(proc.Order.Material.Name,Len(proc.Order.Material.Name)-Len("Produkt ")) Then allocate=True 'If the number of the product name is the same as the number of the tank Else allocate=False ' End If End Sub
To find the right tank, the control tests every member of the unit pool that is currently available. If the control does not find the right unit, e.g., because it is allocated by another order, the control must be called again if any of the members of the pool is becoming available again. This can lead to a lot of calls of the control to find the correct unit.
The benefit of the behavior in Version 13 is now that in the first call of the control you can decide which unit is allowed for the allocation of the procedure. As the call is only called once, this saves a lot of simulation time.
Dim pm As Unit If u Is Nothing Then 'identifies the first call of the control For Each pm In p.Members If pm.Name="Tank " & Right(proc.Order.Material.Name,Len(proc.Order.Material.Name)-Len("Produkt ")) Then proc.PendingUnitsMode(pm.Name)=PendingUnitMode.puAllocate 'the tank which has the same number as the product is allowed to be allocated Else proc.PendingUnitsMode(pm.Name)=PendingUnitMode.puIgnore 'all other tanks are ignored End If Next End If
Within the same model, the use of the control that exploits the new behavior of the Version 13 control saves 36% of simulation time in this simple model, when simulating one year of production. In more complex models, this could be even more advantageous. Furthermore, it is possible to assign a procedure to a unit that is not ready for allocation at the moment, e.g., because it is still allocated. In Version 12, the control would have been called for this procedure every time a unit from the pool becomes available and all units would have been tested. What you might have accomplished with custom code, INOSIM 13 is now supporting natively by assigning a fixed unit from a pool to a procedure.
Workaround / Compatibility with V12 controls
If you wish to use a Version 12 model with Version 13 or want to have the behavior of the control like it was in Version 12, you can just skip the first call of the control by inserting the following code at the very top of your control:
If u Is Nothing Then Exit Function 'in the first call the control is skipped End If
Transfer from/to Unit Pool Control
The behavior of the Transfer from/to Unit Pool Control changed as well. In a transfer control, you can select the sink or source for the transfer operations SimpleInflux, SimpleOutflux or SimpleTransfer. In Version 12, the framework for the Transfer from/to Unit Pool Control looks like this:
Function Sample_Transfer(cop As OrderOperation, u As Unit, _ amount As Double, ByRef retry As Boolean) As Unit ' take the first suitable unit Set Sample_Transfer = cop.SourceSinkUnits(1) End Function
The inputs of the control are the current OrderOperation that is calling the control, the remaining amount of the transfer, and the Boolean retry. If retry is set to true, the control is called again to select another suitable unit in case the transfer is stopped due to full or empty units. The parameter u is nothing in the first call of the control. In later calls, it is the currently used unit for the transfer. The return value of the function is the unit that should be used for the transfer.
In the new version, the framework has slightly changed:
Sub Sample_Transfer(p As UnitPool, cop As OrderOperation, amount As Double, _ ByRef u As Unit, ByRef retry As Boolean) End Sub
First of all, the control is no longer a function with a return value but a procedure. The parameter u is the unit that is used for the transfer. In the first call, the proposed unit for the transfer is stored in the parameter u. To identify the first call of the control in Version 13, you would use:
If cop.Transfer Is Nothing Then 'first call Else 'later call End If
In case the transfer property is nothing, you can change the parameter u to the unit that should be used for the transfer. As an additional parameter, you have the unit pool for which the control is called.
Example: Educt Transfer
In the following example, Educt A for a reaction should be taken from a tank pool. In the first call of the control, a tank is set for the transfer. When this tank runs empty the control is called again and the fullest tank from the pool is selected to continue the transfer. For the next transfer, the same tank is used until it is empty. Then again, the now fullest tank should be selected. In Version 12, the control could look like this:
Public LastEductTank As Unit 'Global Variable Function Educt_Transfer(cop As OrderOperation, u As Unit, amount As Double, _ ByRef retry As Boolean) As Unit Dim pm As Unit Dim EductTank As Unit Dim i As Integer retry=True If LastEductTank Is Nothing Then 'in the beginning the last tank has to be set to the first member of 'the SourceSinkUnits collection, the pool cannot directly be accessed Set LastEductTank=cop.SourceSinkUnits(1) End If If u Is Nothing Then 'for the first call of the control the tank from the last 'transfer should be used until it is empty Set EductTank=LastEductTank Else 'if one tank runs empty, the control is called again and 'the tank with the highest content should be used Set EductTank=u For i=1 To cop.SourceSinkUnits.Count If cop.SourceSinkUnits.Item(i).Contents > EductTank.Contents Then Set EductTank=cop.SourceSinkUnits.Item(i) End If Next End If Set Educt_Transfer=EductTank 'set the chosen unit Set LastEductTank=EductTank 'update the last educt tank used End Function
In Version 12, the unit pool for which the control is called cannot be addressed directly. Instead, you could access the SourceSinkUnits collection of the operation. In Version 13, it is possible to directly access the unit pool that calls the control.
Public LastEductTank As Unit 'Global Variable Sub Educt_Transfer(p As UnitPool, cop As OrderOperation, amount As Double, _ ByRef u As Unit, ByRef retry As Boolean) Dim pm As Unit Dim EductTank As Unit retry=True If LastEductTank Is Nothing Then 'in the beginning the Last Tank has to be set to 'the first member of the unit pool lastEductTank=p.Members.Item(1) End If If cop.Transfer Is Nothing Then 'identifies the first call of the control for this transfer 'for the first call of the control the tank from the 'last transfer should be first used until it is empty EductTank=LastEductTank Else 'if one tank runs empty, the control is called again 'and the tank with the highest content should be used EductTank=u For Each pm In p.Members If pm.Contents > EductTank.Contents Then EductTank=pm End If Next End If u=EductTank 'set the chosen unit LastEductTank=u 'update the last Educt tank used End Sub
With the control in Version 12, it was not possible to access information about the Unit Pool for which the control is called. Now it is not only possible to access the members of this pool, but also other properties, like its name.
Workaround / Compatibility with V12 Controls
If you want to use an existing model from Version 12 in Version 13, you can keep the old controls. One thing that needs to be changed is the way to check if it is the first call of the control. In Version 12, u is nothing, but in Version 13 in the first call u is the first proposed unit. Here, to identify the first call you can use:
If cop.Transfer Is Nothing Then 'first call Else 'later call End If