Task 2.1 - Simple pipeline

Task Overview

We will build the following model:

Batches of 1000 m³ of random products (product IDs 1 through 20) are produced into a suitably sized tank. Once full, the contents will be pumped into a pipeline with one receiving tank on the other end. The tank has a capacity of 5000 m³ to accommodate the statistical case in which multiple batches of the same type are produced in sequence. However, once the product flowing out of the pipe changes, the receiving tank first has to empty into some other process that we will not model.

Step 1 Create the basic model

Setup the basic model: connect a flow source, tank, two flow pipes, another tank and a sink together. Rotate the second flow pipe by 90 degrees. Set the following basic properties:

  • The source has a maximum outflow rate of 100.
  • The first tank has a maximum capacity of 1000 and maximum inflow and outflow rate of "unlimited".
  • The flow pipes have a maximum flow rate of 10 and a virtual content of 750.
  • The second tank has a maximum capacity of 5000 and maximum inflow and outflow rate of "unlimited".
  • The sink has an inflow rate of 100.

When the model is run, nothing interesting happens: this is a static situation in which all the flow rates are maximized. Let's add a few triggers to make the first tank fill completely, then empty completely.

Click the tank and find the Triggers panel in the quick properties. Click the Add button () to add an On Empty trigger. Then click the Add button next to the empty trigger and from the “FloWorks basics” menu, select “Open or close flow” and “Open inputs and close outputs”. This will ensure that the tank will start filling up again without releasing into the pipe as soon as it empties.

Similarly, add a trigger to the On Full event that will “Close inputs and open outputs” to stop the inflow and start releasing into the pipe when the full 1000 m³ batch has been produced.

Finally, FloWorks objects start with all their inputs and outputs opened by default, so you need to make sure that you don't end up in the static situation on reset. Add the “Open inputs and close outputs” trigger to the On Reset event as well. Run the model; the final situation for this step should look as follows:

Step 2 Producing and sending batches of different products

The production tank now fills and empties with the same product every cycle. When there is flow through the pipe, you will see the flow indicators inside the pipe move to indicate the flow speed visually.

To create the multi-product model, we will sample a new product every time the tank starts filling a new batch.

Click the tank, find the existing On Empty trigger and use the properties () button to open the existing trigger logic. Then use the Add () button at the bottom left to add another action to this trigger. From the “FloWorks basics” menu, select “Change product type”. The value current for Object references the tank itself, so leave that as it is. For the product, sample a random value between 1 and 10 (with equal probabilities) by entering the expression duniform(1, 10) into the Product field.

When you run the model now, in general you will see a different product color being produced in the tank for every new cycle. Of course, due to the nature of statistics, sometimes you may get two consecutive batches of the same product.

We will also want to change the inflow product of the pipe when the production tank starts emptying. On the tank's Triggers panel, open the properties of the On Full trigger, and add another “Change product type” action. However, this time you should not change the product of the tank itself, but of the flow pipe it's connected to. To do this, either use the Sampler button next to the Object field and click on the pipe, or use the "Connected Objects" menu in the dropdown. Enter current.as(FlowObject).product for the product to set on the first pipe.

Finally, you will notice that the product is automatically passed from the first flow pipe into the next. This is because flow pipes have the “Pass product downstream” option enabled by default in their "On Product Out Change" trigger, as you can see by inspecting the Triggers panel in the pipe's Quick Properties.

If you have followed all the steps correctly, you should now see randomly colored batches move through the pipelines into the receiving tank. In the next step you will implement the batching logic on the receiving side.

Step 3 Batching in the receiving tank

The receiving tank will have similar logic to the producing tank. When it is empty, or when the model is reset, it will open its inputs and close its outputs. Add these triggers to the tank now.

In contrast to the previous step, however, the tank does not decide for itself when to switch from input mode to output mode. This will be determined by the On Product Out Change trigger on the flow pipe. Remove the existing logic ("Pass product downstream") of this trigger and instead, add a “Close Inputs and Open Outputs” action on the second flow pipe. Instead of applying the action to the current object (i.e. the flow pipe) use the sampler button () to click on the receiving tank.

FlexSim will automatically reference the tank as current.outObjects[1]. If you are not used to working with these kinds of expressions, try reading them right to left: the object that the action applies to is the first ([1]) output object of the current object, where the current object is always the object that you are setting the trigger on – the Flow Pipe in this case. It is considered good practice to use such references as they are more robust than referencing the tank by its name in the model. For example, you could now rename the receiving tank or connect the pipe to another tank altogether without having to remember to update the trigger action. Note that, once you understand this expression, you can also select it directly from the Object dropdown in the trigger action.

If you run the model, you will notice that the trigger now also fires at the start of the model, when the tank is still empty, and therefore there will be no flow. To avoid this, check the "Suppress initial product out event" box in the Flow Object properties of the second pipe.

Step 4 Setting the product in the receiving tank

You are almost done! When you run the model, you will see different products flowing through the pipeline, into the receiving tank. The latter will continue to accept input as long as the inflow product does not change. When it does switch, it will block the inflow and first empty itself into the sink. The only thing remaining is that the receiving tank switches products based on the incoming product at the wrong time! When you run the model slowly, you will see that the “Pass product downstream” trigger of the second pipe changes the old contents of the tank to the new product type.

Just like on the sending side, the product needs to be changed in the On Empty trigger of the receiving tank. The idea is similar to what you've done before, except the expressions become a bit more complicated. First let's do it. If you have not already done so in the previous step, remove the “Pass product downstream” action from the flow pipe's On Product Out Change trigger, leaving only the "close inputs, open outputs" action to the tank. Then add a “Change product type” action to the On Empty trigger of the receiving tank, in addition to the “Open Inputs and Close Outputs” action that it already had. Set the product on current, which is the tank itself, and for the new product type enter the following expression:

current.inObjects[1].as(FlowObject).product

We'll break this down in just a moment, first reset and run the model and check that you get the desired result.

What does that expression mean?

You should see the receiving tank now taking on the correct product type every time it starts filling again. So how does that expression you entered work? Before, we have used the expression current.outObjects[1] to get at the first output object of the current object; similarly we can get the object connected to input port 1 with current.inObjects[1].

We would like to get the product type of this object, which is a property called product. So going with the “read-from-right-to-left” strategy, we would like to write current.inObject[1].product (“the product of the first input object of ...”). However, there is small catch which has to do with typing: according to FlexSim, input objects are of the type Object, which can be anything like Queues, Processors, Conveyors, etc. And of course, Objects don't – in general – have a product. It's just in this particular model that we, as smart humans, happen to know that the input object is not just any Object, it's actually a more specific type of object known as FlowObject. This hierarchy is shown in the diagram below, where an arrow pointing from A to B means that A is actually a more specific type of B, usually inheriting all its properties (so that you can still call obj.outObjects[1] defined on Object when obj happens to be a FlowObject) but adding some properties of its own (such as FlowObjects having a .product property that Objects don't have).

By inserting ....as(FlowObject) you tell FlexSim that the whole expression to the left of it, current.inObjects[1], is actually a FlowObject on which you can then call ....product.

Conclusion

In this task you have used some default trigger options to control flows and product types. In the next tutorial task, you'll learn how to control flows from trigger logic to create a concept similar to the use of tank pools in chemical processing. Continue to Tutorial Task 2.2 - Creating a tank pool.