Tricks to Improve AIMMS Execution Time

The time spent by AIMMS applications can be divided into AIMMS execution time ( including evaluation parameters with definition, executing procedures, generate matrix for solvers, etc), the time spent by solvers, and the I/O time.  Here are some coding tricks that help you improve AIMMS execution time.

1. Avoid ‘For’ Loop

Use bulk execution of assignment as much as possible. If a ‘for’ loop is necessary, try to minimize calculation inside the loop. For example,
for (i,j) do
         A(i,j) := B(i,j) + C(i,j)
endfor;
can be written as the following bulk statement
A(i,j) := B(i,j) + C(i,j);

2. Pay attention to index order

When declaring a parameter with multiple indices, usually index with small cardinality goes first and running index goes last. For example, in the following statement, k is used as running index:

 A(i,j) := Sum[(k), D(i,j,k)];

Another thing to keep in mind is to put the indices in same order. For example the following statement

isActive(p,t,s):= 1 $ (t  >= Begin(p,s) and t < (Begin(p,s)+Duration(t,s)));

runs much faster than

isActive(p,s,t):= 1 $ (t >= Begin(p,s) and t < (Begin(p,s)+Duration(t,s)));

3. Use index domain condition

Domain condition puts restriction on the indices and thus reduces memory and time consumption. Use it whenever possible. The usage of index domain can be found on related posts. One thing to be careful when using domain condition is to avoid sub index expression.

A sub index expression is the expression depend on fewer indices than the entire expression. For example, in the following statement,

F(i,k) := G(i,k) * Sum[j | A(i,j) = B(i,j), H(j)]
the entire expression depends on indices (i,j,k), but expression Sum[j | A(i,j) = B(i,j), H(j)] only has (i,j). During calculating the value of F(i,k),  AIMMS will evaluate the result of sum term for each combination of (i,k), although the its result will be the same of all k. To avoid unnecessary evaluation for k, the one statement can be separated into two statements:
FP(i) := Sum[j | A(i,j) = B(i,j), H(j)] ;
F(i,k) := G(i,k) * FP(i) ;
Another example, although domain condition is added, the following statement is still inefficient:
sum[(t,s,i,j,k) | ElementPara(i,j) = k,]
Since ElementPara(i,j) = k is a sub index expression, AIMMS will create a temporary identifier index over (t,s,i,j,k) to evaluate the condition over the full domain. And comparison operation is a dense operation, thus the calculation needs to go over every (t,s,i,j,k). The result will be time and memory consuming.
The problem can be solved by introducing a new parameter SumCondition(i,j,k) and having
SumCondition(i,j,k) := (ElementPara(i,j) = k);
sum[(t,sc,i,j,k) | SumCondition(i,j,k),];
These are some general rules. In practice, lots of the performance improvements are done on by trials and errors.  And AIMMS diagnostic tools, such as Debugger, Profiler, and Identifier Cardinalities Viewer can help with identifying the performance bottleneck. You can refer to AIMMS User’s Guide for how to use them. And more insights of AIMMS execution engine can be found in chapter Sparse Execution Engine in AIMMS Language References.

Posted in Beginner, Technical | Tagged , , | Leave a comment

Creating StopWatch in AIMMS to time execution

There are situations where you would like to know how long the execution of something in AIMMS took.

When you are working as an AIMMS developer, one of the tools you have for this is the AIMMS profiler. This profiler will provide you with information about how long each statement in an execution took, as well as how long the evaluation of the definition of a parameter took. More information about the profiler can be found in the chapter “Debugging and Profiling an AIMMS Model” in the AIMMS User’s Guide.

When running in End-user mode, the profiler is not available. To still be able to give the end-user feedback on how much time certain steps took, you can create a ‘stopwatch’ in AIMMS code. This can be achieved by introducing the following identifiers into your model:

QUANTITY:
   identifier  :  SI_Time_Duration
   base unit   :  s
   conversions :  tick -> s : # -> # / 100
   comment     :  "Expresses the value for the duration of periods." ;
 
STRING PARAMETER:
   identifier  :  StartTime
   comment     :  "Time the stopwatch was started" ;
 
PARAMETER:
   identifier  :  ElapsedTime
   unit        :  s
   comment     :  "Time that has elapsed since the stopwatch was started. The 
                   value for this is updated by the StopStopwatch procedure";
 
PROCEDURE
  identifier :  StartStopwatch
  comment    :  "Set the starttime of the stopwatch"
  body       :  
    !Use the CurrentToString AIMMS function to store the current time 
    !in YYYY-MM-DD HH:MM:SS:TT format
    StartTime := CurrentToString( "%c%y-%m-%d %H:%M:%S:%t");
 
ENDPROCEDURE  ;
 
PROCEDURE
  identifier :  StopStopwatch
  comment    :  "Deterine how many ticks have elapsed since the start 
                 of the stopwatch"
  body       :  
    !Using the CurrentToMoment AIMMS function, we can ask for the number
    !of ticks that have elapsed at the moment since the given StartTime 
    !(which was stored by calling the StartStopwatch procedure).
    !Please note that we do not actually 'stop' the stopwatch, but only 
    !store the time elapsed.
    ElapsedTime := CurrentToMoment( [tick], StartTime );
 
ENDPROCEDURE  ;

If your model already contains the SI_Time quantity, just make sure that the units second and tick (1/100th of one second) are defined (either as conversion, or as base unit).

You can download the above code as an aim file from the link below. Please see the instructions in the post Exporting a section and importing it in another AIMMS project to import this aim file into your own project. If your project already contains the SI_Time quantity, please remove the quantity from the aim file after downloading it.

Source code Stopwatch Support Section
Title : Source code Stopwatch Support Section
Caption : Source code Stopwatch Support Section
File name : StopwatchSupport.aim
Size : 1 kB

After you have imported the section, the stopwatch code can be used as follows:

StartStopwatch ; 
SomeLongLastingProcedure ; 
StopStopwatch ; 
DialogMessage(formatstring("Execution of procedure took %n seconds"
                           , ElapsedTime)
             ) ;

When running this code, you will get a dialog window telling you how many seconds the execution of SomeLongLastingProcedure took.

Posted in Beginner, Technical | Tagged , , | Leave a comment

Improve performance of for loop containing if-then statement

I often get the question from customers to help them with improving the performance of their project. There are different components of your AIMMS project that you can optimize. For example, you can try to reduce the time required by the solver by reformulating your problem, or in the case of CPLEX tune the options (see the article Tuning CPLEX options from within AIMMS for more details).

Sometimes the efficiency of the AIMMS code in procedures can be improved. One simple example is when your code looks like the following:

for someIndex do
   if conditionParameter(someIndex) then
      !Do something based on the value of someIndex	
   endif ; 
endfor ;

This code loops over all elements of the set of index someIndex. For each of these elements, it checks if conditionParameter(someIndex) has a non-default value and if this is the case it will perform some other tasks.

This way of using the if condition in a for loop is standard in most programming languages. The problem however is that the if-condition is evaluated for each element in the set of someIndex. With AIMMS, you can improve the performance of such loops.

By making use of domain conditions, you can restrict the for-loop to loop over only those elements for which the conditionParameter holds, therefore eliminating the need for checking the if condition for each element. If you modify the above code to the following, performance will improve:

for someIndex | conditionParameter(someIndex) do
   !Do something based on the value of someIndex	
endfor ;

To find out which parts of your model are taking the most amount of time, you can use the AIMMS profiler. More details about the usage of the profiler can be found in the section “Debugging and Profiling an AIMMS Model” in the AIMMS User’s Guide.

For more advanced information about improving the execution performance, please see the chapters “Sparse Execution Engine” and “Execution Efficiency Cookbook” in the AIMMS Language Reference.

Posted in Beginner, Technical | Tagged , , , , | Leave a comment

Tuning CPLEX options from within AIMMS

The CPLEX solver has a large amount of options that influence the way CPLEX solves your model. For certain types of constraints and/or models, you can make an educated guess which combination of options works best for your problem. Unfortunately, this is not always the case.

To help you out with this problem, CPLEX has the possibility to do some automatic tuning of the options. In AIMMS, you can access this CPLEX tuning tool via the following two functions:

  • GMP::Tuning::TuneMultipleMPS
  • GMP::Tuning::TuneSingleGMP

As the names already suggest, the first function will tune the CPLEX options for a set of LP/MPS files. As an argument, you will have to specify the directory containing the LP/MPS files. The second function will tune the options based on one single GMP, which you will have to provide as an argument. As the GMP::Tuning::TuneSingleGMP works as the other GMP function, the solver to use is known. In case of the function GMP::Tuning::TuneMultipleMPS, you must also provide which solver to use as an argument.

In order to create the MPS files for multiple instances of your problem, you can set the project setting General > MPS under the CPLEX specific solver options to “At every solve”. Each time that you solve an instance of your problem, the solver will generate a MPS file.

The rest of the options that you need to provide are the following three:

  • FixedOptions: A subset of the set AllOptions that are to be considered unchangeable and for which the current project options will be used
  • ApplyTunedSettings: A binary argument indicating whether the found tuned option settings should be set in the project options
  • OptionFileName: File to which the tuned option settings should be written. This file can be imported in the Project Options screen.

To use these functions, we first need the following declarations:

SET:
   identifier : FixedOptions
   subset of  : AllOptions
 
ELEMENT PARAMETER:
   identifier :  genMathProgram 
   range      :  AllGeneratedMathematicalPrograms

To actually tune the solver settings, you can use the following code:

 
!Determine which options we consider to be unchangable by CPLEX
!It will use the current value for this setting in the project options.
!As an example, we will forbid the tuning of the setting mip_search_strategy.
FixedOptions := { ’CPLEX 12.3::mip_search_strategy’ } ;
 
 
!First create the GMP out of the Math Program
genMathProgram := GMP::Instance::Generate( MP );
!Then call the TuneSingleGMP function, which will try to find a good
!combination of settings for the solver options for this instance.
!The settings that are in the set FixedOptions will not be considered
!for tuning.
!The found settings will be applied directly in the project settings
!and also will be written to the file "tuned_options_gmp.opt"
gmp::Tuning::TuneSingleGMP(
   GMP                : genMathProgram , 
   FixedOptions       : FixedOptions , 
   ApplyTunedSettings : 1 , !Save found settings directly in project
   OptionFileName     : "tuned_options_gmp.opt" ) ; !Store found settings in this file
 
 
 
!This call will try to find a combination of settings for
!the solver options that are good for the set of MPS/LP files that 
!are found in the subdirectory mps-files in the directory of 
!the project. 
!The settings that are in the set FixedOptions will not be considered
!for tuning.
!The found settings will be applied directly in the project settings
!and also will be written to the file "tuned_options_gmp_mps.opt"
gmp::Tuning::TuneMultipleMPS(
   DirectoryName      : "mps-files" , ! location of mps files, relative to project 
   Solver             : 'CPLEX 12.3' ,! Which solver to use, in this case CPLEX 12.3 
   FixedOptions       :  FixedOptions , !Consider these options unchangable. 
   ApplyTunedSettings :  1 , !Save found settings directly in project 
   OptionFileName     : "tuned_options_gmp_mps.opt" ) ;  !Store found settings in this file

Please note that you must always be careful when you are tuning: you must always ensure that you have one or more instances that are a good representation of all possible instances. If the instances you are tuning are not representative for all possible instances, you will over tune to one specific instance. This might lead to worse performances with the tuned settings for all instances combined.

Posted in Advanced, Technical | Tagged , , , | 1 Comment

Determine where each identifier is used in your model

Over time, AIMMS projects tend to grow larger. During such an evolution of a project, some identifiers can become obsolete.

With larger models, often the question is where each identifier is used in the model, or more important, if it is still used in the model. In this article, we want to provide you with a way to determine which of the identifiers in your model might be obsolete.

The goal is to create the indexed subset sIdentfiersUsing, which contains a subset of AllIdentifiers for each identifier declared in your model.

SET:
   identifier   :  sIdentifiersUsing
   index domain :  iRegularIdentifier
   subset of    :  AllIdentifiers
 
SET:
   identifier :  sRegularIdentifiers
   subset of  :  AllIdentifiers
   index      :  iRegularIdentifier
   definition :  { indexIdentifiers |
                            IdentifierType(IndexIdentifiers) <> 'Section'
                        and IdentifierType(IndexIdentifiers) <> 'Module'
                        and IdentifierType(IndexIdentifiers) <> 'LibraryModule'
                        and IdentifierType(IndexIdentifiers) <> 'declaration'
                 }

As you can see, the set sIdentifiersUsing is indexed over a subset of AllIdentifers, namely all the identifiers except for the Sections, Modules, Libraries, and declaration sections.

To fill the sIdentifiersUsing set with the identifiers that are using the identifier iRegularIdentifier, we make use of the intrinsic procedure ReferencedIdentifiers (see Function Reference) in the following procedure:

PROCEDURE
  identifier :  prDetermineReferenced
 
  DECLARATION SECTION 
 
    SET:
       identifier :  sReferencedIdentifiers
       subset of  :  AllIdentifiers
       index      :  iReferencedIdentifier ;
 
    SET:
       identifier :  sIdentifierSingleton
       subset of  :  AllIdentifiers ;
 
  ENDSECTION  ;
 
  body       :
    !First empty the set
    empty sIdentifiersUsing ;
 
    for iRegularIdentifier do
        !For each non {section/module/library/declaration} identifier in your
        !model, create a singleton set, containing only this identifier
        !(needed because ReferencedIdentifiers must have a set as argument
        sIdentifierSingleton := iRegularIdentifier ;
 
        !Retrieve all the identifiers that are referenced in all attributes
        !of the current identifier
        sReferencedIdentifiers := ReferencedIdentifiers(
                                                sIdentifierSingleton,
                                                AllAttributeNames
                                                )
                                   * sRegularIdentifiers ;
 
        !We now know which identifiers are referenced by iRegularIdentifer.
        !For each of these identifiers, we must add a link that it is used by
        !iRegularIdentifier
        for iReferencedIdentifier do
            sIdentifiersUsing( iReferencedIdentifier ) += iRegularIdentifier ;
        endfor ;
     endfor ;
ENDPROCEDURE  ;

Please note that if the above procedure indicates that an identifier is not used anymore, this only means it does not have a reference within your model anymore.

We have created an .aim file of a section containing the above identifiers, which you can download via the following link:

Source code Where are identifiers used Section
Title : Source code Where are identifiers used Section
Caption : Source code Where are identifiers used Section
File name : Where_are_identifiers_used.aim
Size : 2 kB

After downloading the above file, please follow the Importing instructions in the blog post Exporting a section and importing it in another AIMMS project to import this .aim file into your project.

Additional information: Even if an identifier is not used anymore in your model, it could still be present on pages in your project. With a combination of the two functions PageGetAll and PageGetUsedIdentifiers (See the Page functions in the Function Reference) you can first obtain a list of all the pages in your model, then query per page all the identifiers used on the page, and finally check if the selected identifier is one of them. This approach will give you per identifier the list of pages it is used on.

Alternatively, if you are only interested in the binary question “is an identifier used on any page yes or no?”, you can also make use of the new function IdentifierGetUsedInformation that has been introduced in AIMMS 3.12 FR2. This function also allows you to determine if there exists a reference to the identifier in any of the case types or menus.

Posted in Advanced, Technical | Tagged | Leave a comment

Exporting a section and importing it in another AIMMS project

Sometimes there are parts of a model that you would like to re-use in another AIMMS model. If it is a very generic component, you could choose to create an AIMMS library or an AIMMS module out of it. Please see the chapter “Organizing a Project Into Libraries” in the AIMMS User’s guide for more information about this. In the cases that you only want to quickly export/import a set of identifiers once, you can also use the export/import functionality in AIMMS.

Also, on this blog we will provide the AIMMS code where applicable as .aim files. You  can import these into your existing projects with the instructions found below. In some cases where the whole project is needed (and not only some snippets), we will provide the whole project as an AimmsPack file.

Exporting a section

Before you start exporting, you should place all the identifiers you wish to export from your current model into one section in your AIMMS model. When you have done this, there are two possible ways of exporting this section:

  • Select the section in the model tree and select Export… in the edit menu
  • Use the source attribute of the section identifier to store the section in a separate file

The first option has the advantage that it requires less steps. However, the second approach has the advantage that you can export to .aim files, which is a text based format. This allows you to export something from a newer version of AIMMS and then import it in an older version of AIMMS. This is not possible if you use the export/import functionality under the edit menu, as this works with binary .amb files only, which can only be opened in the same version in which it was created or higher.

To export a section to an .aim file, please follow these steps:

  • Open attribute window of the section you wish to export
  • Select wizard button of the source file attribute
  • Select Write in the menu
  • Select the location you want to store the .aim file
  • Set the “Save as type” to “Text Model Files (*.aim)”
  • Set the filename to what you want and press Save
  • Select wizard button of the source file attributes again
  • Select “Remove (keep subtree)…” in the menu

If you do not perform the last two steps, all identifiers declared in the section will not be stored in the main project file anymore, but separately in the .aim file that you selected.

Importing a section

To import an .aim file or an .amb file, please use the following steps:

  • Create a new section in your model that will hold the identifiers that will be imported
  • Select this section identifier in the model tree
  • In the Edit menu, select Import…
  • Select the .aim or .amb file that you want to import

After this, AIMMS will present you with a dialog that shows which identifiers will be imported and which ones are conflicting with already existing identifiers in your model.

Important note: When using the source file attribute of a section, you can store the contents of the section in a separate file (if you do not use the “Remove (keep subtree)…”). Please note that if you use an .aim file for the source file attribute, changes that are made to this text file with any other program (e.g. a text editor or version management system) while the AIMMS project is open will not be picked up by AIMMS! Only after you close and re-open the project, will these changes be visible in your project.

Posted in Beginner, Technical | Tagged , , | Leave a comment

Using google to search AIMMS documentation

Often I hear from customers that they are really happy with the amount of documentation that we have about AIMMS. However, sometimes they also tell me that due to the large amount of documentation available, it can be difficult to find exactly what they are looking for.

Since we have all of our documentation also online on our website, including all of our PDFs broken up into separate smaller files, luckily, Google can help with this last problem. One of the features they provide is that you can limit your search to certain domains by adding an additional term to your search query. In order to limit your search to only domains that are below aimms.com (e.g. www.aimms.com, blog.aimms.com), you must add the term

site:aimms.com

to your search query.

Below you can find an example that shows the results for searching for the function ExcelRetrieveTable. The results include not only the Function and Language Reference, but also an article posted on this blog.

Search through AIMMS.com with google

Search through AIMMS.com with google

If you only want to search through the contents of this blog, you can either use the search button on the top of this blog, or you can use the additional search term “site:blog.aimms.com” to your query.

Posted in Technical | Tagged , , , | Leave a comment

Reading multi-dimensional Excel data with ExcelRetrieveTable

A lot of applications need to read the input data for the model from a Spreadsheet file. AIMMS supports both reading data from and writing data to spreadsheets. For retrieving data from Excel into a parameter in your AIMMS model, you can make use of the functions ExcelRetrieveParameter and ExcelRetrieveTable (see the section Excel functions in the Function Reference).

There are some differences between these two functions. The first one is that ExcelRetrieveParameter can be used to read in the data for scalar, 1, and 2 dimensional data from Excel. With the ExcelRetrieveTable, you can also read in higher dimensional data. The second difference is that the ExcelRetrieveParameter function assumes the data in Excel is in the same order as the elements in your sets in AIMMS. This means that if your set in AIMMS has the elements in the order {a, c, b}, the function will assume that the first item it reads from Excel corresponds to element a, the second item corresponds to element c, and the third item corresponds to element b. This behavior can lead to inconsistencies if you are not aware about this.

The ExcelRetrieveTable function tackles this last problem by not only reading the data, but also reading the information about the elements the data corresponds to. To be able to do this, the ExcelRetrieveTable function requires some additional arguments about ranges. In the rest of this article, we will explain the different arguments for this function.

When using the ExcelRetrieveTable function, you must provide the following arguments:

ExcelRetrieveTable(
	ExcelWorkbook           :  , 
	Parameter               :  , 
	DataRange               :  , 
	RowsRange               :  , 
	ColumnsRange            :  , 
	Sheet                   :  , 
	AutomaticallyExtendSets :  )

The ExcelWorkbook and Sheet arguments tell AIMMS which sheet and workbook to use. The parameter argument tells AIMMS to which identifier the data from Excel should be read.

The three ranges (Data, Rows, and Columns) that are required can be visualized as follows:

Range explanation

Range explanation

Note that in the example above, the three ranges are consecutive in Excel (i.e. they are next to each other). This does not need to be the case, i.e. there might be empty columns between the row range and the data range, and empty rows between the column range and the data range. You only need to ensure that the following conditions hold:

  • n+m = dimension of parameter in AIMMS;
  • Number of rows in row range = Number of rows in data range;
  • Number of columns in column range = Number of columns in data range.

During the execution of the call, AIMMS will actually verify that the above conditions indeed hold and return with an error if this is not the case.

If all the conditions hold, then ExcelRetrieveTable must match the columns of the row range and the rows of the column range to the indices for the parameter denoted by the parameter argument. The way this matching done is fixed and means that the order of the indices in your AIMMS model must match the order of the indices in the following way: AIMMS will assume the first index of the AIMMS parameter corresponds to the first column in the row area, the second index to the second column, etc. After the columns of the row range, the next index corresponds with the first row of the column range. The total mapping follows the following order: (r_1, r_2, \ldots, r_n, c_1, c_2, \ldots, c_m)

This mapping is displayed in the example below:

Small example for ranges Excel

Small example for ranges Excel


In this example, the parameter in AIMMS will get the value 3.14 for the indices with the values (aa,bb,dd,ee,ww,xx,yy,zz). The three ranges in the above Excel example would be the following:

  • Row range: A8:D18
  • Column range: E4:K7
  • Data range: E8:K18

The final argument (AutomaticallyExtendSets) is a binary argument that instructs AIMMS to extend the sets corresponding to the indices with values from the row and column range if it has value 1. If the value is 0 and elements exist in the row or column range that do not exist in the corresponding AIMMS Set, the ExcelRetrieveTable will result in an error.

To write multi-dimensional data from AIMMS to Excel, you can use the function ExcelAssignTable. This function uses the same three ranges in the same way as the ExcelRetrieveTable function does.

Additional information: please note that with AIMMS 3.12 FR1 and up, there is also support for OpenOffice Calc. Because this means that the functionality is generic instead of specific to Excel only, the naming has changed to Spreadsheet::RetrieveTable (i.e. the Excel prefix is replaced by a Spreadsheet:: prefix for all Excel functions). Please see the Function Reference within your AIMMS installation for more details about these spreadsheet functions.

Posted in Beginner, Technical | Tagged , , | 3 Comments

Using GMP functions instead of regular solve statement

In the simplest form solving a Mathematical Program identifier is done by using the intrinsic solve statement of AIMMS:

solve MathProgram ;

For the majority of the AIMMS modelers, this suffices for their needs. Whenever you want to have more advanced control over what happens, you have to start working with Generated Mathematical Programs (GMP). With GMP’s, you have full control over the constraint matrix: you can edit coefficients and add new constraints and variables.

The initial transition from using only standard Mathematical Program identifiers to using GMP functions is very easy and boils down to introducing an additional element parameter and changing the original solve statement into two statements: one for generating the GMP and one for solving the GMP. The element parameter you need is the following:

ELEMENT PARAMETER:
   identifier :  genMathProgram 
   range      :  AllGeneratedMathematicalPrograms

The original one-line solve statement now needs to be converted to the following two lines:

genMathProgram := gmp::Instance::Generate(MathProgram) ; 
gmp::Instance::Solve(genMathProgram ) ;

The first line generates the GMP and the second line solves this GMP. With these two minor additions, you have moved to using GMP’s. For more information about what you can do with the GMP’s, you can take a look at the Chapter “Implementing Advanced Algorithms for
Mathematical Programs” in the Language Reference and the GMP section in the Function Reference.

Additional information: The exact workings of gmp::Instance::Solve statement can actually be emulated in a couple of lower-level GMP statements. If you look at the Language Reference, you will see that you can emulate its behavior with the following calls:

! Create a solver session for genMP, which will create an element
! in the set AllSolverSessions, and assign the newly created element
! to the element parameter session.
session := GMP::Instance::CreateSolverSession(genMP);
! Copy the initial solution from the variables in AIMMS to
! solution number 1 of the generated mathematical program.
GMP::Solution::RetrieveFromModel(genMP,1);
! Send the solution stored in solution 1 to the solver session
GMP::Solution::SendToSolverSession(session, 1);
! Call the solver session to actually solve the problem.
GMP::SolverSession::Execute(session);
! Copy the solution from the solver session into solution 1.
GMP::Solution::RetrieveFromSolverSession(session, 1);
! Store this solution in the AIMMS variables and constraints.
GMP::Solution::SendToModel(genMP, 1);

Posted in Beginner, Technical | Tagged | Leave a comment

Generate indexed constraint, except for certain elements

When you have an indexed constraint, sometimes you want this constraint not to be generated for certain elements of the index domain.

Examples of these cases are:

  • In the constraint you are referring to the previous or the next element. This means that the constraint should not be generated for the first element and the last element, respectively. A simple example of this is a Stock Balance constraint:
    StockLevel(t) = StockLevel(t-1) + AmountProduced(t) – AmountSold(t)
  • You want to create a constraint that is indexed over two indices of the same set for all combinations, except for the situation where the two indices refer to the same element. An example would be a precedence constraint: a task i must be either before or after another task j and this constraint should be created for all i,j such that i≠j

You can achieve this goal by restricting the index domain of the constraint with a domain condition. A domain condition can be introduced with the | operator (the so-called such-that operator). With this operator, AIMMS will only consider those elements (or combination of elements in case of multiple indices) for which the expression after the | operator holds a non-zero value.

For the first example, in case of using the previous element in the constraint, you would like the constraint to be generated for all timeperiods t, except for the first one. This can be expressed in AIMMS by setting the attributes of the constraint as follows:

CONSTRAINT:
   identifier   :  StockBalanceConstraint
   index domain :  t | ord(t) > 1
   definition   :  StockLevel(t) = StockLevel(t-1) 
                                     + AmountProduced(t) 
                                     - AmountSold(t)

The intrinsic ord function returns the position of the element in the set. The domain condition (the part after the | operator) will only have a non-zero value for those elements that are not on the first position of the set. This means that this constraint will be generated for all t, except for the first one.

For the second example, if we have a binary variable TaskStartsBeforeOther(i,j) that gets the value 1 in case task i starts before task j and 0 otherwise, we can model the constraint that for each combination of tasks it must hold that one of the two is started before the other one. This constraint is not valid for the combination where task i is equal to task j. This can be achieved by setting the attributes of the constraint as follows

CONSTRAINT:
   identifier   :  EitherBeforeOrAfter
   index domain :  (i,j) | i <> j
   definition   :  TaskStartsBeforeOther(i,j) 
                     + TaskStartsBeforeOther(j,i) 
                     = 1

Posted in Beginner, Technical | Tagged , | Leave a comment