The Fluid Library allows you to model storage and transfer of fluids, bulk matter, or large amounts of discrete items, which you do not want to model as separate objects.
The library includes blocks such as Tank, Pipeline, Valve, FluidSource, and FluidDispose. There are also blocks for routing, merging, and diverging the flow. In addition, there is a block BulkConveyor designed to model transfer of bulk or condensable matter. See the full list of Fluid Library blocks in Fluid Library.
The Fluid Library interoperates with the Process Modeling Library: it can convert agents into portions of fluid and vice versa.
The Fluid Library blocks put linear constrains on the flow rates, such as maximum rates, proportional rates, and so on. The library engine maximizes the flow throughout the system. As a result, the flow rates in the library are piecewise-constant (constant within time intervals), and only change instantly at discrete moments of time.
The linear nature of the flow dynamics allows for using LP (linear programming) solver to calculate the maximum rates. The solver is invoked only at the discrete moments of change, which makes the execution speed of the Fluid Library models a lot higher than that of the System Dynamics models. At the same time, the LP calculations are much more accurate as there is no notion of time step in the LP solver. We recommend using the Fluid Library when the system is linear, and using System Dynamics only for non-linear cases, i.e. when there are continuous feedback loops in the model, or continuous rate changes.
By default, these blocks of the Fluid Library ask the LP solver to maximize their outflow: FluidSource, AgentToFluid, Tank, and BulkConveyor. In addition, the FluidMerge and FluidSplit blocks in the priority mode will ask to maximize the flow at one of the inputs or outputs. Internally, this is done by raising the priority (coefficient) of the corresponding rate in the LP objective function. By default, the rate being maximized gets the priority 1, all other rates — 0. However, this will not always result in the desirable global picture. The user can create flowchart configurations with priority conflicts, and the library will not be able to figure out the correct global priorities without additional information. The Fluid Library allows you to provide that information in the form of custom priority values, see the corresponding parameters of the blocks listed above. The color indication of priority violation in FluidSplit and FluidMerge blocks may help you to track down issues with flow distribution.
When calculating the flows, the Fluid Library will take into account all priority values along the full flow paths. Therefore, the resulting flows may not follow the local priority values at each split; for example, at a FluidSplit block with output priorities 1 and 0, the flow may go into the second output if 0 combined with other priorities further along the way gives a larger value.
If a rate evaluates to Utils.MAXIMUM_RATE (1.0E10) during runtime, the model stops with an error: “Flow rate evaluated to the maximum value. You need to set a limit for this rate.” This is done because typically maximum rate is not normal and is a result of the user forgetting to set a limit somewhere. To avoid this, please set the upper limit for rate in flowchart block(s) where required.
Amounts of fluid or bulk matter in the Fluid Library are measured in units of two types: mass and volume. Possible mass units are:
And volume units can be one of the following:
- Cubic meter
- Oil barrel
Within a connected flowchart of Fluid Library blocks (a "fluid flow network") units must be of the same type: either mass or volume. Otherwise, if some blocks have parameters specified in mass units, and other blocks – in volume units, as the model starts, you will get the error "Amount units are inconsistent".
Internally, the Fluid Library converts any mass units to kilograms and any volume units to cubic meters. Internal time units are seconds.
Therefore, when you call a function of a fluid library block, and do not specify units explicitly, the amounts and rates passed as arguments or returned by the function will be measured either in cubic meters and cubic meters per second, or kilograms and kilograms per second, depending on the amount unit type chosen for the flowchart.
Correspondingly, any tolerance-based checks are done in kilograms, cubic meters, kilograms per second and cubic meters per second.
In the Fluid Library, rates, amounts, time intervals, and other values are frequently recalculated with the use of linear formulas, e.g.: amount = rate * time interval, etc. As a result, numeric errors can occur. For example, if the tank capacity is 100 cubic meters, and the inflow keeps the rate of exactly 1 cubic meter per second for exactly 100 seconds, the “straightforward” implementation of the tank could result with the amount of, say, 100 /- 1e-12 cubic meters. The Fluid Library does its best to minimize the effect of numeric errors by snapping the calculated values to where they should be. The snapping uses Utils.TOLERANCE — an internal constant that is applied across the library.
Here are some important places where TOLERANCE is used:
- Amounts of fluid in Tank, Pipeline, BulkConveyor, FluidSource, FluidToAgent, FluidPickup, and amount that flowed through Valve are snapped to 0 and, where applicable, to capacity.
- Tank, Pipeline, and BulkConveyor do not create batches of size less than TOLERANCE.
- Detection of Tank becoming full or empty, and detection of certain level being reached (above/below) is done with TOLERANCE.
- Obviously, all of the above also applies to the composite blocks that contain Tank.
- Detecting accumulation of the given amount in FluidPickup and FluidToAgent.
- In BulkConveyor density variations that are within TOLERANCE are not counted (do not result in new bulk “portions” being created).
- Amounts defined by the user cannot be less than TOLERANCE, including: capacities, initial amounts, amounts to dispense or inject, portion size, etc.
Although normally you should not think about numeric errors while building models, sometimes the combination of time, rate, and amount units of different scale may cause very small numeric values, or small value variations, and then this knowledge may be useful.
Similarly, there is another notion of tolerance for rates, the corresponding constant is Utils.RATE_TOLERANCE. Its value is 1.0e-9 and it works like this:
- If the user specifies a positive rate value that is lower than RATE_TOLERANCE, it is handled as an error.
- If during the rate recalculation a rate value evaluates to one lower than RATE_TOLERANCE, it is snapped to 0.
Theoretically, one can create a flow configuration that may cause a livelock (infinite event loop without progress in time). The simplest example would be a user action in the On rate change field of a Fluid Library block that causes another rate recalculation and, therefore, another call of On rate change. The recommendation, obviously, would be to avoid making such actions. However, more complicated situations may occur. For example, the event of a certain tank becoming empty can cause rate recalculation, which, in turn, can switch the tank to a non-empty state. This changes the rates back to the initial state, and the tank becomes empty again. Although there is no universal method to cure a livelock, a general advice would be to play with custom rate priorities in Split / Merge blocks in order to stabilize the rate system.
How can we improve this article?