|
Objects in this document may appear out of sequence if not viewed with Internet Explorer |
|
ORMSware NMOD Primer: Chapter 12 |
|
| Caution: One common mistake people make while trying to understand a model within our current constraint of lack of an IDE is trying to read the NET file sequentially, as if it were an ordinary text document. In the absence of an IDE, one has to follow the approach described below to gain full understanding of a model. |
|
Understanding the details of a model |
![]() |
To understand a model we have to start with
its Visio diagram. Upon surveying the relationships and flows, and
looking in dialog boxes of nodes and arcs of interest to
understand the overall structure of the model, we should go to the logic
block of the top network's Start node in the model's NET file.
After studying the logic content of any network object of interest in NET file, we must return to Visio to decide what the next object of interest is to understand the model further. We must repeat that process as much as necessary to get sufficient understanding of the model. In our example model, first page is the CostPerPiece network. Scanning for the green Start Node in this network in Figure 11a (PrimerVSD) window, we see that it is n[6]Initializations. Looking at the description in the network display, we want to see what those initializations are. We can double-click on the node to see what is in the dialog box (or look at the image on the left for now). |
|
Expanding object content logic, and continuation of logic lines |
We see that the Trans text box contains just the string {}... Apparently, as explained previously, the analyst has chosen to enter all logic for this node's Trans property in the NET file. So, let us switch to Figure 11b (PrimerNET.TXT) window to look at the Trans property content of [6].
The very first record in the NET file shows that the top network in the model is CostPerPiece. Since we know that the Start Node is [6], let us scan down to the code block for object 6. Code blocks are arranged in ascending order in NET file according to the Visio-assigned IDs of the objects to which they belong.
We see that [6]'s code block contains only one property code block and that the property is Trans. The first record in Trans block is setting the initial value of PiecesPerHour property of the surrogate that triggers [6]. The analyst is using the dbl KIND real variable to store that property value. The next two records (after a couple of WRITE statements) set initial values of two global properties, both of the big KIND integer.
Next record is the first part of a logical (binary) assignment statement setting the value of logical global property with the name CurveBuild. The second part of the assignment statement is on the next record. We could have included the whole statement on one line, but we split the statement this way to demonstrate how to do it when the right-hand-side of the assignment is wider than the width of a window and you want to continue the logic to the next record. Analyst indicates that the statement continues to the next record by entering an ampersand (&) at the end of the first record.
| Note: Fortran 95 does not require a space between the preceding string and the &, but NETrans does. The reason is that NETrans considers an ampersand at the end of a string as part of the string and so attempts to translate the string with the & at the end of it. It will not recognize ampersand that is part of a string as line continuation indicator. |
|
Example of using NMOD's exposed functions and procedures |
With the CurveBuild flag the analyst is creating a global indicator as to whether or not the Type property of a particular node (CostPerPiece.[2]) is Arrival. Specifically, to set this flag value the analyst is using NMOD function NodeType to compare the Type property of CostPerPiece.[2] against the value of NMOD constant tnyArrival. NodeType function retrieves the Type value of any node, given the node's sequence number, or its object ID [and, optionally, its page number; if page number, or a function yielding page number, is not present as the second argument, NodeType assumes that the object is on the page/network in which NodeType is referenced].
NodeType explained above is an example of an exposed NMOD function available for use by analysts. Anything denoted using ORMSware notations, such as {Object.2}, is translated by NETrans so that there are no repeated interpretations of these notations when the model runs. As you may have already gathered, the purpose of NETrans is to interpret everything that can be interpreted just once and to generate corresponding source code which can be compiled and linked to NMOD's core code.
|
NMOD constants |
Getting back to the test statement in [6] to determine the Type of [2], tnyArrival used towards the end of the statement for setting the value of the CurveBuild flag is an integer constant of tny KIND. The actual value of tnyArrival has been already assigned to it during the design of ORMSware. The actual value is irrelevant to the user, as the only concern here is to determine if the tny KIND value returned by NodeType function has the same value as that of the predefined value of the tnyArrival constant.
A list of NMOD constants that may come in handy while formulating nontrivial models can be found in Appendix A.
| Note: We could have easily provided a NMOD function for translating a node name to its corresponding Visio object number as argument to the NodeType function (and we will probably do it in the future anyway), but there are some problems involved in using node names. First, you may recall that a node's name is not part of its unique identification. A node is uniquely identified by the Visio page the node is on and its Visio object ID. Since node names are not necessarily unique, there could be ambiguity in mapping node names to Visio object IDs. Second, if the user knows the node to specify its name, s/ he knows its object ID to specify it directly. Third, even if there were a requirement that node names be unique, arc names without object IDs may not be unique, since NMOD permits multiple arcs from any given node to any other node. Besides, arc names are a handful to handle. |
|
Logical operators |
Notice that the equality test operator in the logical statement is ==, not just one = sign. Inequality operators are < , <=, >, >=, and /=.
Lexical comparison for inequality should be done using the functions below:
|
Example of inserting debug statements |
Upon determining the value of the CurveBuild flag, the analyst wanted to verify during model development that the flag value was getting computed correctly as expected. A free-format WRITE statement was used to track the value. In the WRITE statement, 9 indicates handle number of the file to which the feedback is to be written, and the * following the 9 indicates free format.
The analyst used file handle 9, because NMOD opens a file named NMOD.TXT with a file handle of 9 at the beginning of a model run to serve as its execution feedback file. So, the analyst was using the same file to write this custom feedback statement.
|
Notes
1. The analyst could have opened and written that custom feedback to another file as you may have already seen in models posted in the Examples section. We will cover this topic in Chapter 20 (Reinforcing concepts through PrimerSubNet's feedback file). 2. The analyst has the option of using a Fortran 95 package's IDE to watch variable values. However, since a big part of the model's program involves code that is hidden from the user, because the model USEs core NMOD code, using the IDE may not expedite model development and debugging. When NMOD has a critical mass of users, we will build an IDE that has variable-watch functionality. In the mean time, in the near future, we will be building into NMOD the capability for the analyst to just flag the variables and properties to be traced. |
| Alert: If an analyst wants to open a file for reading and/or writing, the file handle number should be > 20. Numbers <= 20 are used by NMOD to open various files during model execution. We are working on a method that will spare analysts from having to worry about file handle numbers. |
We are finished looking at [6]'s code block. At this point we should not go on to read the code block for the next object (a[8]) in the NET file, because it will make no sense. We should go back to the Visio drawing. The network suggests that after looking at [6], the next interesting object to look at is a[11].
![]() |
Double-click [11] (or look at [11]'s dialog box image on the left). You will notice that the analyst has not entered any logic in any of its properties. Switch to Figure 11b (PrimerNET.TXT) window to look at object 11's code block. As you move down from [6] to find [11], you come across the block for [8] and then the block for [13]. There is no block for a[11]. NETrans did not include a[11] in the NET file, because values of all fields in a[11] are default values or have behavior known a priori. Adding a code block for [11] contributes nothing to the functionality of the model. |
|
Network/page demarcation in NET file |
Continue moving downwards in the NET file. There is a record full of slashes right below [16]. A record of slashes are inserted by NETrans to give us quick visual clue of the end of code block of a Page/Network.
So, let us switch back now to model diagram and see what the next object of interest might be. n[2]PiecesPerHour would be a good pick.
| Note: In case you have been wondering if using NMOD does not involve the same amount of work as programming with higher order languages such as Fortran 95, C++, etc., we hope the explanation of the lack of presence of [11] in the NET file answered part of that question. In any program of a model, the programmer spends quite a bit of time dealing explicitly with basic structural issues. What any modeling tool attempts to do is move many of those "model production" activities into the tool so that that kind of repetitive tasks as well all associated quality factors are built into the tool. It is comparable to building quality into the design of a process or product to simplify production, reduce defects and compress cycle time. |
![]() |
Suppose we want to track down how the value of a particular entity property, say @D.PiecesPerHour, seems to get changed in some unexpected fashion. Then, we would want to find out where, other than in n[6], @D.PiecesPerHour may be getting changed unexpectedly so as to influence the value of #D.PiecesPerHour in [2]. Double-clicking [2] (or looking left at the image of the dialog box), we see that there is a simple assignment statement in the Trans property, where the Customer property PiecesPerHour is being set equal to the Surrogate property PiecesPerHour. A question that may also arise at this point to someone looking in on the model is why Customer property PiecesPerHour is being set to Surrogate property PiecesPerHour to begin with. We will explain that shortly.
|
Recall that we set @D.PiecesPerHour to an initial value first in n[6]Initializations. That surrogate will get to n[2] via a[11]. In addition to that, we also see a[15] coming to n[2] from n[5].
We already know about a[15] and n[6]. So, we can try to figure out what happens to @D.PiecesPerHour on its way to n[2] along the path which has a[15] as its last leg. We can do this by following backwards the flows into n[5] and further back, one by one. Alternatively, we can go to the NET file and do a reference check for the "@D.PiecesPerHour" string. It is always a good idea to start with a reference check while trying to find out what is happening to a property or variable in a model.
Shown below is the result of a reference check on @D.PiecesPerHour:
Searching in C:\ORMSware\PrimerModels\Primer.NET for: @D.PiecesPerHour
CostPerPiece.[2]PiecesPerHour
{Trans}
{nop.P}Trans:{#D.PiecesPerHour = @D.PiecesPerHour}
CostPerPiece.[5]CostPerPiece
{Trans}
WRITE(9,*)"2:@PiecesPerHour =",@D.PiecesPerHour
@D.PiecesPerHour = #D.PiecesPerHour
CostPerPiece.[6]Initializations
{Trans}
@D.PiecesPerHour = 24
CALL OptimizationStimulus(@D.PiecesPerHour)
WRITE(9,*)"1:@PiecesPerHour =",@D.PiecesPerHour
CostPerPiece.[15]CostPerPiece--->|PiecesPerHour
{Branch}
{nop.L}Branch = {nop.L}Branch .AND. @D.PiecesPerHour < &B.PiecesLim
WRITE(9,*)"&L.CurveBuild,@D.PiecesPerHour < &B.PiecesLim,{nop.L}Branch:", &
&L.CurveBuild,@D.PiecesPerHour,&B.PiecesLim,{nop.L}Branch
{Trans}
{nop.P}Trans:{@D.PiecesPerHour = @D.PiecesPerHour + &B.PiecesIncrement}
PiecesPerHour.[4]PiecesPerHour
{Trans}
{nop.P}Trans:{@D.PiecesPerHour = #B.ProdApieces + #B.ProdBpieces}
|
Since we already know about n[2], let us look at n[5]. Here @D.PiecesPerHour is being set equal to #D.PiecesPerHour. So, we have to remember to track down locations where #D.PiecesPerHour changes, in case we end up not finding the answers we are looking for regarding @D.PiecesPerHour. Alternatively, we could redo the reference check by selecting just PiecesPerHour.
As a device for discussing "how to model it with NMOD," let us explain what is being done here before we continue the discussion of tracking down how @D.PiecesPerHour changes during the travels of a surrogate. Since n[5] is a Departure node, and we need to use the value of #B.PiecesPerHour after a customer leaves to continue with calculations for the next customer (i.e. next point on the CostPerPiece curve), we have to store this value somewhere from which we can easily retrieve it. @D.PiecesPerHour (user-defined PiecesPerHour property of surrogates in this model) is an ideal place to store it for ease of further use.
The next occurrence of @D.PiecesPerHour in the NET file is in n[6]. We have already seen that occurrence. Moving to the next occurrence, we see that it is in a[15]. We see the branch condition being tested using @D.PiecesPerHour. We also see @D.PiecesPerHour's value being incremented in the arc's Trans property block. So, here is another place that is contributing the value which #D.PiecesPerHour takes on at [2].
The next occurrence is in the PiecesPerHour network of the model. We will leave it up to you to investigate the impact of that on your own.
The original how-to-model-it question as to why set #D.PiecesPerHour equal to @D.PiecesPerHour in n[2] may still linger. Why not just use one or the other? We will answer this question in the next section.
|
Example of using Type property of a node to change model granularity |
If we use just the Customer property, we will lose that value when a customer leaves the system at Departure node [5]. Subsequently, we will get an error if we try to test the branch condition on a[15] with the property of a token that was taken out of circulation when its customer left the system (because the memory allocated to user-defined customer token properties is deallocated when a token is taken out of circulation).
If we use only the Surrogate property, we will have the headache of trying to figure out if there is a possibility of a surrogate, carrying the original value set in n[6]Initialize, surviving the convergence at some node while another surrogate carrying the latest update gets disposed before getting back to n[2] for a new loop.
| Note: At a Convergence node, only one surrogate of a customer arriving at that node can go forward. The rest have to be disposed off. If we do not write explicit Merging property code, the surrogate that survives to continue the thread is the last one arriving at that node. Since in our example model Duration properties of all network objects are 0, it will be too much brain damage to try to figure out which AND arc will deliver the last surrogate for convergence at a given node for any given customer. You will gain a better understanding of this when we cover NMOD Events and go through an execution feedback file. |
If we set #D.PiecesPerHour when a customer arrives (at n[2]), we don't have to worry about anything happening to this property value somewhere in the network until the customer is in a Departure node. It survives all convergences, since it is a customer property. In the CostPerPiece network of this model, there is only one Departure node, viz. n[5]CostPerPiece.
As you have already seen in the code block for n[5], @D.PiecesPerHour is set to #D.PiecesPerHour at the bottom of n[5]'s Trans property block. There the customer is handing over the PiecesPerHour value to its surrogate so that after the node is finished and a customer departs, its PiecesPerHour property value is still available for testing the Branch property on a[15]. NMOD executes customer departure procedure as soon as a Departure node event is complete, before executing the node's SignalTarget property. At the time of departure of the customer, NMOD switches the Token ID property of the surrogate to the default Token ID of 0. By then, of course, the surrogate already has the departing customer's PiecesPerHour value for the test on a[15].
| Note: At this point it is natural for anyone with no experience with NMOD to wonder if these kinds of steps aren't too much to go through to model a problem like this. The answer is, yes, it is too much trouble for this problem. But, this is a simple, contrived problem. NMOD mitigates operational risk and eases maintenance pain when it comes to large scale and/or complex models, and simple manipulations required like this, at times, are well worth the small price one has to pay to have the benefits of reliable rapid modeling capability. |
Recall that the objective of the current version of the example model (aside, of course, from facilitating discussion of how-to-model-it with NMOD) was to facilitate switching between two modes of operation as well as running the model at different levels of granularity by simply changing the Type property of a node (CostPerPiece.n[2]).
In one mode the model generates x-y values for a cost-per-piece curve to help us find the optimum piece-per-hour production rate for producing a widget. In this mode, the model operates at a lower granularity level, without bothering to go down to the PiecesPerHour subnet to gather total number of widgets-per-hour value.
In the other mode, the model calculates the cost-per-piece value for any given production rate of the widget, where the production rate is driven by multiple products that use the same widget. In this mode the production rate is calculated at a lower level of detail (finer granularity level) in the PiecesPerHour subnet.
If we wanted the model to operate only in the second mode mentioned above, there would be no a[15] and a[16] in the model (check PrimerVSD window), and there would be no branch conditions on a[8] and a[13].![]() |
Let us look at the branch condition on a[8]. Branch conditions on [13] and [16] are similar to the one on a[8]. Notice in the network diagram that a[15] creates a loop for calculating CostPerPiece for different values of PiecesPerHour. Each loop causes the arrival of a new customer in n[2] and departures of customers in n[5]. Since WearParameters are constants regardless of PiecesPerHour, we need to fetch these values only once, at the very beginning, when the first customer arrives. We want to bypass fetching WearParameters for all subsequent customers. Since all customers arriving after the first one will have a Customer ID > 1, a[13] as well as a[8] will execute only once when this model is running in the curve-building mode. We will explain a little later the significance of [8] executing only once. |
|
Note
Fetching the same WearParameters constants more than once is really not an issue in our little example model, but it might be if this were a large scale model, the calculations in n[1] were extensive, and/or we were generating thousands of x-y points. Often, however, it may be an insignificant issue that is not worth the extra effort. At any rate, our purpose of going through that exercise here was simply to illustrate how model execution can be controlled in many ways in NMOD. In other situations factors such as these WearParameters may involve one or more subnets for defining their complex structure. But, if it is logic that need to be executed only once when a model runs, we certainly would not want to repeat that logic unnecessarily during every cycle. Notice that if we were doing traditional programming to implement this model, we will set up the top subprogram for calculating WearParameters at the beginning of the program and would not even give this issue a second thought. It becomes an issue of interest in NMOD, because here we are concerned first about showing the relationships among variables in a model and then figuring out how to make those relationships come alive for calculating the results. Therefore, it is important to keep in mind that when we use NMOD to model a problem, we are not setting up the flowchart of a program to implement a model we have already formulated. We formulate the model using NMOD's Visio interface and then supply some logic to make the resulting code produce the results we need. If you understand what we just explained, you have gotten the hang of NMOD and are well on your way to benefiting from it. |
CustomerID() in [8]'s branch condition is an exposed NMOD function that allows user to retrieve Customer ID of a token. bigTkn is an optional argument and the user can use whatever symbol is relevant. When this argument is absent NMOD assumes that the token of concern is the currently active token (i.e. bigTkn). As explained in Chapter 9 (Creation, assignment and disposal of tokens and surrogates), the symbol bigTkn always carries the value of the Token ID property of the surrogate triggering a given network object. We did not have to use bigTkn as an argument in the CALL in[8]'s Branch property, but wanted to do so to discuss this point.
When the Type property of n[2].PiecesPerHour is set to Normal, the model runs in the second mode using the value of @D.PiecesPerHour set in n[6]Initialize. Since there will be no customer arrivals generated, there will be no looped execution of the model. The Customer ID of all surrogates will be the default value of 0. You can see by looking at the Description properties of [8], [13] and [16] (assuming the analyst is correctly displaying their branch conditions) that [16] will execute only in the curve building mode while [8] and [13] will execute in either mode (but only once, when Customer ID is <= 1).
Notice that [8] is an AND arc while [13] and [16] are OR arcs. In the curve building mode, surrogates travel along a[8] and a[13]-n[1]-a[9] to n[3]MachineCostPerHour only when the Customer ID is <= 1. Since both [9] and [8] are AND arcs, NMOD will make sure that Convergence node n[3] fires only after a total of two surrogates, representing the same customer, have arrived through AND arcs terminating at that node.
| Note: If you had wondered if NMOD is not similar to, if not the same as, flowcharts, this may be a good time to pause and reflect on the differences. |
In our example model, NMOD's convergence process ensures that, in the first or only loop, WearParemeters will be fetched in either mode before calculating MachineCostPerHour. Because of the Branch property of [13], WearParemeters will not be fetched more than once.
Arc [16] will execute only when Customer ID is > 1 (i.e. in curve building mode, because Customer ID can be > 1 only when n[2]'s Type is Arrival); every surrogate traversing [16] to n[3] each time a new customer > 1 arrives will cause the firing of [3], without waiting for any convergence, because [16] is an OR arc.
When the Type property of n[2]PiecesPerHour is set to Network, the model runs in the second mode, but uses the PiecesPerHour network to calculate the hourly production rate of widgets based on the production rates of Products A and B. Model will be operating at a finer granularity level in this mode, allowing accommodation of lower level factors that affect the total production rate of the widget.
|
Using CASE construct |
Two other changes from the previous version of the model are addition of CostPerPiece.[12] from [2]PiecesPerHour to [4]LaborCostPerHour, and the content of [4]. Check code block for object 4 in PrimerNET.TXT window. We have introduced the notion that labor cost is a function of the machine speed (widgets per hour); the idea is that faster operation requires labor with higher skills. The arc from [2] to [4] reflects this notion that labor cost is a function of machine speed (pieces per hour) used. Using CASE construct, content logic of [4] says that labor rate goes up from $20 per hour to $25 per hour when production rate equals or exceeds 30 widgets per hour.
You can see use of other constructs such as DO, IF-THEN-ELSE, etc. in some of the problems posted in the Examples section and in Chapter 5 (Storing more network object content in a model's NET and ADD files) of Hands-on Tutorial.
Our discussion so far covers all of the issues involved in the current version of the example problem. We suggest that you investigate the model further by going through the remaining objects in the two networks in this model.
|
Click to go to Chapter 11: Properties and notations |
|
Click to go to Chapter 13: Events, execution feedback, and model debugging |
|
Click to go to Introduction: NMOD Primer |