QUEST has a pretty powerful construct that I haven't really seen in any other simulation packages: processes. A process is a data structure in QUEST that holds pretty much all the information you need to run a process in a simulation.
SCL exposes a lot of the information in a process, which you can see in the SCL documentation. However, selecting a process interactively in SCL is not built in (as far as I can tell). So here's a routine for selecting a process in a QUEST model.
routine select_a_process() : Process
Const
max_process 200
Var
result , the_process : Process
num_processes_found , pick_process , list_picked : Integer
the_processes : Array[ max_process ] of String
Begin
num_processes_found = 0
the_process = first_process
while( the_process <> NULL and num_processes_found < max_process ) do
the_processes[ num_processes_found ] = the_process->name
num_processes_found = num_processes_found + 1
the_process = the_process->next
endwhile
list_picked = item_pop_up( 'Select a process' , the_processes , pick_process )
return get_process( the_processes[ pick_process ] )
End
Basically the macro traverses the QUEST model's linked list of processes and adds them to an array. Then we use the item_pop_up routine to select a process from a list generated by our array. The item_pop_up returns the index of the selected process in the array, and we return that process pointer. You can use this routine to select a process that you want to act on in a SCL macro.
Thursday, June 5, 2008
Wednesday, June 4, 2008
File I/O
QUEST SCL has a set of easy to use subroutines for dealing with file input and output. If you've done file I/O in VB or VBA you'll see the syntax is very similar.
File I/O is performed in SCL using file streams. This basically means that you use SCL to open a file and either read it or write to it. To open a file you first need to determine the file stream you want to use. This is just an integer value unique to the file you want to access. If you're planning to open a file stream and close it within a single subroutine the file stream should be an integer between 1 and 15. Anything above that is a global stream, which can be manipulated from any subroutine you're running. This complicates things a bit, and I personally don't use global streams very often. This is because you can pretty easily just append data to a file if you prefer.
There are two ways to pick a stream. You can use a literal integer value and just keep an eye on which you use (in case you decide to manipulate several files at once). Or you can do what I do and use an integer variable so it's easy to change things around, and it makes the code easier to read.
There is a way to ensure you always have a good file stream to use, which is to check file streams starting with one until you find one that's not opened yet. This is facilitated with a while loop and the QUEST built in function is_open.
The is_open function just takes in an integer value and returns true if it's in use as a file stream and false if it's not. Here's the start of a procedure for writing data to a file:
procedure print_data_to_file()
Var
out_stream : Integer
Begin
out_stream = 1
while( is_open( out_stream ) ) do
out_stream = out_stream + 1
endwhile
End
When this procedure gets to the end, out_stream will have a value that is acceptable for use in manipulating file operations. But how do we start file manipulation? With the "open" procedure. This procedure is a little different in that arguments are not passed in parenthesis, but instead the command is built like a sentence. The syntax is:
OPEN FILE name FOR [ BINARY | TEXT ] { OUTPUT | APPEND | INPUT } AS unit_no
In this procedure we can leave out the binary/text declaration, as SCL will default to text. We need to declare how we're opening the file, though. We have the option of: output, append, or input. If we output, then if the file exists it will be wiped clean and started new. If we choose append we have to make sure the file exists to begin with or we'll get an error. If we do append, then any data we write to the file just gets appended to the end of the file. Lastly, if we open for input then we can't make any changes to the file; we're just reading its contents.
From this point on things are pretty straightforward if you're writing out to the file. You simply use the "write" procedure and specify the output stream in order to output to that stream. Here's an example procedure:
procedure print_machine_class_names()
Var
out_stream : Integer
check_class : Element_Class
Begin
out_stream = 1
while( is_open( out_stream ) ) do
out_stream = out_stream + 1
endwhile
OPEN FILE 'C:/Tmp/machine_names.txt' FOR OUTPUT AS out_stream
check_eclass = first_element_class
while( check_eclass <> NULL ) do
if( check_eclass->type == MACHINE ) then
write( #out_stream , check_eclass->name , cr )
endif
check_eclass = check_eclass->next_eclass
endwhile
close #out_stream
End
This is a very straightforward example that first creates/resets the machine_names.txt file, and then proceeds to print out the name of every machine encountered in the model. The last thing it does is close the file stream using the close procedure.
Reading data from a text file is a little less straightforward but not much harder to do. We need to start the same way as writing: find a valid file stream and open the file. Here's the base code:
procedure read_data_from_file()
Var
in_stream : Integer
Begin
in_stream = 1
while( is_open( in_stream ) ) do
in_stream = in_stream + 1
endwhile
OPEN FILE 'C:\Tmp\file_read_example.txt' FOR INPUT AS in_stream
End
Once the file is open, we need to start reading the file. We can do this using the read_line procedure. The syntax is as follows:
READ_LINE ( [ #unit_no,] lvalue )
where lvalue is a string variable we pass in whose value gets assigned as the next line read from the specified file stream. One thing to note here, is that when SCL gets to the end of the file, read_line will return a special predefined string called $EOF. We just keep reading until read_line gives us the $EOF string.
Here is some sample code for reading lines from a file and writing them to the screen.
procedure read_data_from_file()
Var
in_stream : Integer
curr_line : String
Begin
in_stream = 1
while( is_open( in_stream ) ) do
in_stream = in_stream + 1
endwhile
OPEN FILE 'C:\Tmp\file_read_example.txt' FOR INPUT AS in_stream
read_line( #in_stream , curr_line )
while( not curr_line == $EOF ) do
write( curr_line , cr )
read_line( #in_stream , curr_line )
endwhile
close #in_stream
End
File I/O is a powerful tool for use in QUEST. You can use this to read in a tab-delimited data file with any number of possible uses. For example you can read in process cycle times and assign those times to the specified processes. The possibilities here are really endless, and hopefully this will give you a good idea of how to get started.
File I/O is performed in SCL using file streams. This basically means that you use SCL to open a file and either read it or write to it. To open a file you first need to determine the file stream you want to use. This is just an integer value unique to the file you want to access. If you're planning to open a file stream and close it within a single subroutine the file stream should be an integer between 1 and 15. Anything above that is a global stream, which can be manipulated from any subroutine you're running. This complicates things a bit, and I personally don't use global streams very often. This is because you can pretty easily just append data to a file if you prefer.
There are two ways to pick a stream. You can use a literal integer value and just keep an eye on which you use (in case you decide to manipulate several files at once). Or you can do what I do and use an integer variable so it's easy to change things around, and it makes the code easier to read.
There is a way to ensure you always have a good file stream to use, which is to check file streams starting with one until you find one that's not opened yet. This is facilitated with a while loop and the QUEST built in function is_open.
The is_open function just takes in an integer value and returns true if it's in use as a file stream and false if it's not. Here's the start of a procedure for writing data to a file:
procedure print_data_to_file()
Var
out_stream : Integer
Begin
out_stream = 1
while( is_open( out_stream ) ) do
out_stream = out_stream + 1
endwhile
End
When this procedure gets to the end, out_stream will have a value that is acceptable for use in manipulating file operations. But how do we start file manipulation? With the "open" procedure. This procedure is a little different in that arguments are not passed in parenthesis, but instead the command is built like a sentence. The syntax is:
OPEN FILE name FOR [ BINARY | TEXT ] { OUTPUT | APPEND | INPUT } AS unit_no
In this procedure we can leave out the binary/text declaration, as SCL will default to text. We need to declare how we're opening the file, though. We have the option of: output, append, or input. If we output, then if the file exists it will be wiped clean and started new. If we choose append we have to make sure the file exists to begin with or we'll get an error. If we do append, then any data we write to the file just gets appended to the end of the file. Lastly, if we open for input then we can't make any changes to the file; we're just reading its contents.
From this point on things are pretty straightforward if you're writing out to the file. You simply use the "write" procedure and specify the output stream in order to output to that stream. Here's an example procedure:
procedure print_machine_class_names()
Var
out_stream : Integer
check_class : Element_Class
Begin
out_stream = 1
while( is_open( out_stream ) ) do
out_stream = out_stream + 1
endwhile
OPEN FILE 'C:/Tmp/machine_names.txt' FOR OUTPUT AS out_stream
check_eclass = first_element_class
while( check_eclass <> NULL ) do
if( check_eclass->type == MACHINE ) then
write( #out_stream , check_eclass->name , cr )
endif
check_eclass = check_eclass->next_eclass
endwhile
close #out_stream
End
This is a very straightforward example that first creates/resets the machine_names.txt file, and then proceeds to print out the name of every machine encountered in the model. The last thing it does is close the file stream using the close procedure.
Reading data from a text file is a little less straightforward but not much harder to do. We need to start the same way as writing: find a valid file stream and open the file. Here's the base code:
procedure read_data_from_file()
Var
in_stream : Integer
Begin
in_stream = 1
while( is_open( in_stream ) ) do
in_stream = in_stream + 1
endwhile
OPEN FILE 'C:\Tmp\file_read_example.txt' FOR INPUT AS in_stream
End
Once the file is open, we need to start reading the file. We can do this using the read_line procedure. The syntax is as follows:
READ_LINE ( [ #unit_no,] lvalue )
where lvalue is a string variable we pass in whose value gets assigned as the next line read from the specified file stream. One thing to note here, is that when SCL gets to the end of the file, read_line will return a special predefined string called $EOF. We just keep reading until read_line gives us the $EOF string.
Here is some sample code for reading lines from a file and writing them to the screen.
procedure read_data_from_file()
Var
in_stream : Integer
curr_line : String
Begin
in_stream = 1
while( is_open( in_stream ) ) do
in_stream = in_stream + 1
endwhile
OPEN FILE 'C:\Tmp\file_read_example.txt' FOR INPUT AS in_stream
read_line( #in_stream , curr_line )
while( not curr_line == $EOF ) do
write( curr_line , cr )
read_line( #in_stream , curr_line )
endwhile
close #in_stream
End
File I/O is a powerful tool for use in QUEST. You can use this to read in a tab-delimited data file with any number of possible uses. For example you can read in process cycle times and assign those times to the specified processes. The possibilities here are really endless, and hopefully this will give you a good idea of how to get started.
Tuesday, June 3, 2008
Simulation Control Language (SCL)
Simulation Control Lanugage (SCL) is the runtime language used in QUEST for defining logics. It's pretty similar to Visual Basic and Pascal as far as I can tell, in terms of syntax. It's a fairly high level language allowing easy manipulation of files, and even execution of Windows API functions.
SCL is not only used for defining logics in a model (init, process, route, etc), but can also be used to build QUEST macros that you run using custom buttons or even remote execution through QUEST's Batch Control Lanugage (BCL). The possibilities with SCL aren't necessarily limitless, but it's a very robust language and it takes a lot of effort to run into its limitations.
SCL files are stored in text files and are not embedded within a simulation model like some other simulation packages do. This makes it easy to apply SCL code to different models, but it also means you can destroy the functionality of any number of models just by modifying a single logic file to fit a single case.
To get you started with SCL, I'll go with the old standby and just create a simple "Hello World" program to illustrate some basic SCL concepts.
To start, you first need to open a text editor and start your scl file. You can use notepad or any text editor. I prefer to use RJ-TextEd (See here) because it allows code folding and syntax highlighting; so if you have a large number of subroutines in a file you can more easily navigate the file than using notepad.
With your text editor of choice open, first create a new subroutine by defining it:
procedure hello_world()
The word "procedure" tells the scl compiler that this is a subroutine that does not return anything. If you want to return a value you replace the word "procedure" with "routine". I'll cover routines in more detail at a later time. You're also able to pass in different arguments to subroutines, but we don't need to worry about that just yet. We just want to display "hello world" in QUEST.
With your procedure name defined, we can finish defining the procedure by defining the start and end of the program:
procedure hello_world()
Begin
End
All code that you execute must fall between the Begin and End lines of the subroutine you're using. Each line of code represents one command (unless you use the line continuation character, "\").
QUEST has some decent documentation on SCL built in subroutines. From this documentation under the I/O Routines section we can find a procedure called "write" that will let us print a message to the QUEST user screen. The syntax is:
WRITE ( #unit_no, pr_expr [ , pr_expr,... ] )
where "#unit_no" is an optional argument which can specify a file or window or tcp/ip stream to write to. Disregard this for now. "pr_expr" is the message we want to write, which can be a variable of just about any type (although some types just print their memory address), though we should stick to printing simple data types( String , Real , Integer ). So the "[ , pr_expr ]" means we can specify any number of variables we want.
We don't need to pass a variable to the "write" procedure; we can pass literal strings or integer/double values as well. To write "hello world" in QUEST, we'd simply call:
write( 'hello world' , cr )
where cr is a built in QUEST variable that is basically a carriage return and line feed.
You can see I used a single quote to define a literal string, "hello world". This is the SCL compiler's preferred way of defining a literal string, but you can use double quotes if you want, as long as you are consistent in a single string definition (you can't say "hello world').
The finished procedure reads as follows:
procedure hello_world()
Begin
write( 'hello world' , cr )
End
Save your scl file into the "SCLMACROS" folder of any QUEST library you have appended. You can save with a txt or scl extension, it really doesn't matter. As long as the information is present in ASCII text the SCL compiler won't care.
We don't need to create a user button to run this macro, and so it'll probably be easier for now to just compile and run the file using BCL. To execute BCL go to File->BCL. You will be given a BCL prompt where you can type or paste a command to execute. The first line of BCL to execute is:
SCL_COMPILE 'hello_world.scl' 0
Replace 'hello_world.scl' with whatever you named your file. Don't worry about the full file path; as long as it's in an "SCLMACROS" folder QUEST should be able to find it. If the file is not found, make sure the library it's in is appended.
The next line of BCL will read:
SCL_EXEC hello_world
This line will execute the hello_world procedure stored in memory after compiling your logic file. When you run this a window should pop up in QUEST that says "hello world".
That was a bit long winded for a hello world type subroutine, but hopefully it explains how you need to define subroutines for QUEST SCL files.
SCL is not only used for defining logics in a model (init, process, route, etc), but can also be used to build QUEST macros that you run using custom buttons or even remote execution through QUEST's Batch Control Lanugage (BCL). The possibilities with SCL aren't necessarily limitless, but it's a very robust language and it takes a lot of effort to run into its limitations.
SCL files are stored in text files and are not embedded within a simulation model like some other simulation packages do. This makes it easy to apply SCL code to different models, but it also means you can destroy the functionality of any number of models just by modifying a single logic file to fit a single case.
To get you started with SCL, I'll go with the old standby and just create a simple "Hello World" program to illustrate some basic SCL concepts.
To start, you first need to open a text editor and start your scl file. You can use notepad or any text editor. I prefer to use RJ-TextEd (See here) because it allows code folding and syntax highlighting; so if you have a large number of subroutines in a file you can more easily navigate the file than using notepad.
With your text editor of choice open, first create a new subroutine by defining it:
procedure hello_world()
The word "procedure" tells the scl compiler that this is a subroutine that does not return anything. If you want to return a value you replace the word "procedure" with "routine". I'll cover routines in more detail at a later time. You're also able to pass in different arguments to subroutines, but we don't need to worry about that just yet. We just want to display "hello world" in QUEST.
With your procedure name defined, we can finish defining the procedure by defining the start and end of the program:
procedure hello_world()
Begin
End
All code that you execute must fall between the Begin and End lines of the subroutine you're using. Each line of code represents one command (unless you use the line continuation character, "\").
QUEST has some decent documentation on SCL built in subroutines. From this documentation under the I/O Routines section we can find a procedure called "write" that will let us print a message to the QUEST user screen. The syntax is:
WRITE ( #unit_no, pr_expr [ , pr_expr,... ] )
where "#unit_no" is an optional argument which can specify a file or window or tcp/ip stream to write to. Disregard this for now. "pr_expr" is the message we want to write, which can be a variable of just about any type (although some types just print their memory address), though we should stick to printing simple data types( String , Real , Integer ). So the "[ , pr_expr ]" means we can specify any number of variables we want.
We don't need to pass a variable to the "write" procedure; we can pass literal strings or integer/double values as well. To write "hello world" in QUEST, we'd simply call:
write( 'hello world' , cr )
where cr is a built in QUEST variable that is basically a carriage return and line feed.
You can see I used a single quote to define a literal string, "hello world". This is the SCL compiler's preferred way of defining a literal string, but you can use double quotes if you want, as long as you are consistent in a single string definition (you can't say "hello world').
The finished procedure reads as follows:
procedure hello_world()
Begin
write( 'hello world' , cr )
End
Save your scl file into the "SCLMACROS" folder of any QUEST library you have appended. You can save with a txt or scl extension, it really doesn't matter. As long as the information is present in ASCII text the SCL compiler won't care.
We don't need to create a user button to run this macro, and so it'll probably be easier for now to just compile and run the file using BCL. To execute BCL go to File->BCL. You will be given a BCL prompt where you can type or paste a command to execute. The first line of BCL to execute is:
SCL_COMPILE 'hello_world.scl' 0
Replace 'hello_world.scl' with whatever you named your file. Don't worry about the full file path; as long as it's in an "SCLMACROS" folder QUEST should be able to find it. If the file is not found, make sure the library it's in is appended.
The next line of BCL will read:
SCL_EXEC hello_world
This line will execute the hello_world procedure stored in memory after compiling your logic file. When you run this a window should pop up in QUEST that says "hello world".
That was a bit long winded for a hello world type subroutine, but hopefully it explains how you need to define subroutines for QUEST SCL files.
Introduction Post
The purpose of this blog will primarily be somewhere for me to put some of my knowledge of DELMIA QUEST (QUeueing Event Simulation Tool) out on the Internet. This is for two main reasons: one, I'd like to have somewhere to keep some of the tools I've written for QUEST, and two, maybe someone somewhere will get some value out of what I leave here.
I spend a lot of my time these days working on ways to automate the use of QUEST through QUEST's Batch Control Language (BCL) and Simulation Control Language (SCL), so that's probably what most posts will focus on for now.
I'm always looking to learn more about QUEST and simulation packages in general, so leave a comment or send an email (if that's available).
I run a Google Group on DELMIA QUEST, which at the moment doesn't have a lot of activity. If you're interested in joining go to http://groups.google.com/group/delmia_quest and join, and I'll more than likely approve you.
Happy simulating.
I spend a lot of my time these days working on ways to automate the use of QUEST through QUEST's Batch Control Language (BCL) and Simulation Control Language (SCL), so that's probably what most posts will focus on for now.
I'm always looking to learn more about QUEST and simulation packages in general, so leave a comment or send an email (if that's available).
I run a Google Group on DELMIA QUEST, which at the moment doesn't have a lot of activity. If you're interested in joining go to http://groups.google.com/group/delmia_quest and join, and I'll more than likely approve you.
Happy simulating.
Subscribe to:
Posts (Atom)