This is a special blog post by guest writer Aqeel Tariq. In this post he shares a simple trick, that has a number of applications, to measure the time an agent spent in an assembler block.
Calculating the time an agent spends in a system can be a seamless process thanks to the TimeMeasure blocks in the process modeling library. These blocks allow us to easily examine the length of time an agent spends in the Delay, Service, and MoveTo blocks, as well as the time spent by an agent in the entire system.
However, TimeMeasure blocks do not work with the Assembler block, resulting in a runtime error.
To understand why TimeMeasure blocks do not work with the Assembler block, we must first understand how TimeMeasure blocks work.
TimeMeasureStart blocks keep a record of all agents that have passed through them and have not yet gone through a paired TimeMeasureEnd block.
The TimeMeasureEnd block measures the time an agent has spent since passing through the corresponding TimeMeasureStart block. It can only measure the time for an agent that has already passed through the corresponding TimeMeasureStart block. Otherwise, an error will occur.
In the Assembler, agents that enter are different than the ones that exit, making it impossible to use TimeMeasure blocks for measuring the time spent by an agent in the Assembler.
Solution
However, there is a solution to this issue. By following these simple steps, we can easily calculate the processing time an output agent spends in the Assembler:
Step 1: Create an agent that will be the output of the Assembler.
Step 2: Assign a variable "startTime" of type double to the agent.
Step 3: On the "on enter delay" of the Assembler block, write
agent.startTime = time();
Step 4: On the "on exit" of the Assembler block, write
time() - agent.startTime;
Step 5: Store the result in a variable called "timeSpentInAssembler" to display it in a chart.
timeSpentInAssembler = time() - agent.startTime;
In the above steps, we store the system time in a variable called "startTime" when input agents enter the Assembler block delay. Then, we get the system time when the output agent leaves the Assembler block and subtract "startTime" from it. Then we store the result in a variable called "timeSpentInAssembler" so we can display it in a chart. This is how we can calculate the processing time of the Assembler without using TimeMeasure blocks.
Can we also calculate the time spent by an agent waiting for an assembly to start?
The answer is yes, we can calculate the time each agent spends waiting in the Assembler block for an assembly to occur. The method for doing so will be similar to the one used above for calculating the delay time / processing time of the Assembler block, but the implementation will be slightly different.
Solution
Let's say we are assembling a product that requires two components to be assembled. In this case, it is not always possible to have both components in the Assembler block at the same time. So we are going to measure the time each component spends waiting in the Assembler block before all the required components enter the assembler and the assembly process begins.
Time (seconds) spent by Component_1 in the Assembler
Step 1: Create an agent named "Component_1" that will be Input 1 for the Assembler block.
Step 2: Assign a variable "Component_1StartTime" of type double to the agent.
Step 3: On the "on enter 1" of the Assembler block, write
agent.Component_1_StartTime = time();
Component_1_StartTime = agent.Component_1_StartTime;
In step 3, we are calculating the time taken by Component_1 to reach the Assembler and storing it in a variable called "Component_1_StartTime".
Step 4: On the "on enter delay" of the Assembler block, write
Component_1_assembly_wait_time = time() - Component_1_StartTime;
In step 4, we are calculating the time Component_1 has to wait for assembly to start, and storing that time into a variable called "Componant_1_assembly_wait_time" so we can display the data in the chart.
Time (seconds) spent by Component_2 in the Assembler
Step 1: Create an agent named 'Component_2' that will be Input 2 for the Assembler block.
Step 2: Assign a variable "Component_2StartTime" of type double to the agent.
Step 3: On the "on enter 2" of the Assembler block, write
agent.Component_2_StartTime = time();
Component_2_StartTime = agent.Component_2_StartTime;
In step 3, we are calculating the time taken by Component_2 to reach the Assembler and storing it in a variable called "Component_2_StartTime".
Step 4: On the "on enter delay" of the Assembler block, write
Component_2_assembly_wait_time = time() - Component_2_StartTime;
In step 4, we are calculating the time Component_2 has to wait for assembly to start and storing that time into a variable called "Componant_2_assembly_wait_time" so we can display the data in the chart.
As you can see in the GIF above, Component_2 had to wait around 3 seconds for the assembly to start because Component_1 entered the Assembler block much later. However, Component_1 did not have to wait at all because as soon as it entered the Assembler block, the assembly started.
Hold on, we're not finished yet! There are still a few things that require further explanation.
Why am I using a queue before an Assembler?
Assembler have separate waiting queues for each input port, and the number of items that can wait in each queue can be adjusted using the Assembler's properties. Once the waiting queues reach their set quantities, the Assembler takes all the items from the queues and uses them to create a new agent.
The number of items that can wait in the Assembler input port queue is limited to the amount required to complete the assembly. Any extra items produced cannot be stored in the Assembler queue. To handle this, a separate queue must be added, to store any extra items produced by the source block. If we don't do this, we will end up with the error shown below.
Why am I Using Variables Outside of Agents?
The "On enter delay" property of the Assembler block only allows access to the assembled product agent and not the input components.
Since there is no way to access the individual input items once they are combined in the Assembler, we need to use variables outside of the block to store data related to the input items.
For example, in the "On enter delay" property of the Assembler block, we can't access individual input agents like this:
Component_2_assembly_wait_time = time() - agent.Component_2_StartTime;
Since the Assembler block can only access the assembled product agent in the 'On enter delay' property, we need to store the value of "agent.Component_2_StartTime" in an external variable "Component_2_StartTime" in order to calculate the wait time for Component 2:
Component_2_assembly_wait_time = time() - Component_2_StartTime;
Batch block, allows access to both the batch agent and its individual input components in the "On add" property, the Assembler block does not provide this capability. Therefore, we rely on variables outside of the Assembler to store data related to the input components that were used to create the assembled product.
How can we calculate the waiting time for both components using external variables if the input port of the assembler requires two components?
The current setup will only work if we have one component for each input port. However, if two components are entering the second input port of the Assembler, the system will store the "start time" of the first component in an external variable. When the next component enters, the previous value stored in the variable will be overwritten with the "start time" of the new component. Which means we end up not having the waiting time for both components.
Solution
The time spent by both components at the second input port of the Assembler before assembly starts can easily be calculated using a collection and a data set.
Step 1: Drag a collection "Comp2_StartTimes" and a data set "Comp2WaitTimes" on the main canvas.
Step 2: In "On enter 2" property of Assembler store all the components StartTime in a Collection "Comp2_StartTimes".
Comp2_StartTimes.add(time());
Step 3: In "On enter delay" property of Assembler, calculate the waiting time for each component and add it into a data set "Comp2WaitTime" using a forEach loop.
Comp2_StartTimes.forEach(e -> Comp2WaitTimes.add(time() - e));
Step 3.1: In "On enter delay" property of Assembler, clear the collection.
Comp2_StartTimes.clear();
As we can see in the above GIF, we can now calculate the waiting time for both components of the second input port. The data set shows that the assembly started on the 5th second, the first Component_2 waited for 3 seconds in the Assembler, and the second Component_2 waited for 1 second in the Assembler before the assembly started.
Conclusion
It is impossible to use the TimeMeasure blocks for measuring the processing time/delay time of the Assembler block and the time agent waits for the assembly to start, because the TimeMeasureEnd block can only measure the time for an agent that has already passed through the corresponding TimeMeasureStart block. Instead, you can follow the simple method given above to calculate the processing time of the assembler block and the time the agent waits for the assembly to start.
Please take a look at the example model on the AnyLogic Cloud here.
Aqeel Tariq
Aqeel is a simulation consultant and guest writer for The AnyLogic Modeler. Contact him on LinkedIn to get in touch.
What next?
If you liked this post, you could read more posts by following the links above. Why not subscribe to our blog or follow us on any of the social media accounts for future updates? The links are in the Menu bar at the top, or the footer at the bottom. You can also join the mobile app here!
If you really want to make a difference in supporting us please consider joining our Patreon community here
If you want to contact us for some advice, maybe a potential partnership or project or just to say "Hi!", feel free to get in touch here, and we will get back to you soon!
תגובות