Adapt Solve Procedure with Callbacks for GMP

This article presents a general guide to converting from solve statements to using GMP, and how to adapt the existing callbacks and their activation.

As an experienced model builder, you may want to convert from solving using the solve statement to using GMP functionality (with prefix gmp::). Using GMP offers several benefits, such as:

  1. Speed up Monte Carlo analysis
  2. Work with multiple solutions
  3. Use multi-objective, both weighted and lexicographic
  4. Solve in parallel

However, when callbacks are used on the mathematical program, the callback procedures need to be modified and activated differently when using GMP.

Original solve procedure

We have a flow shop model that is solved by the following procedure pr_DoSolve with body:

1
2
3
4
5
6
7
8
Empty AllVariables;
pr_GenerateData();
empty s_Timepoints ;
FlowShopModel.CallbackTime := 'pr_TimeCallback'; ! Solve style callback.
block where progress_time_interval := 1 ;
    solve FlowShopModel;
endblock ;
pr_prepInterface;

On line 4 a callback is activated for the flowshop model by setting the corresponding suffix for the mathematical program.

The callback procedure itself is:

1
2
3
4
5
Procedure pr_TimeCallback {
    Body: {
        pr_updateGap(FlowShopModel.bestbound, FlowShopModel.Incumbent);
    }
}

As you can see, it uses the suffixes .bestbound and .Incumbent which are set before this procedure is invoked.

An example solve results in the following progress window:

../../_images/1-progress-window.png

As you can see, optimality is reached.

topic:: Example Project 1

If you want to replay this yourself, please download and run 1.flowshop...zip and press the solve button in the lower right corner.

Declaring the GMP

The Generated Mathematical Programs are objects stored in AIMMS internally. Each object is given an identification as an element in the predeclared set AllGeneratedMathematicalPrograms. We use an element parameter to store such an element after generating, so that we can reference it in later manipulations such as solving. The declaration is:

1
2
3
ElementParameter ep_GMP {
    Range: AllGeneratedMathematicalPrograms;
}

With this declaration, we can simply convert.

1
2
3
4
5
6
7
8
9
Empty AllVariables;
pr_GenerateData();
empty s_Timepoints ;
FlowShopModel.CallbackTime := 'pr_TimeCallback'; ! Solve style callback.
block where progress_time_interval := 1 ;
    ep_GMP := gmp::Instance::Generate( FlowShopModel );
    gmp::Instance::Solve( ep_GMP );
endblock ;
pr_prepInterface;

The only difference in coding the solution procedure is then on lines 6 and 7, highlighted above. Running that procedure gives the unexpected result:

../../_images/2-progress-window.png

As you can see, optimality is not reached; instead you’ll get the following warning:

After zero iterations CPLEX 12.9 found an integer solution to FlowShopModel. The minimum found for TimeSpan is 1865.

This is caused by the different interface for callbacks. We will handle that in the next section.

topic:: Example Project 2

If you want to replay this yourself, please download and run 2.flowshop...zip and press the solve button in the lower right corner.

Adapting callbacks for GMP

GMP style callback procedures have the input argument ep_session which is an element parameter in the set AllSolverSessions. This gives you access to solver session specific information. The return value of the callback procedure should be 0 to stop solving, or 1 to continue solving.

The best practice is to have an explicit return statement as the last statement of a callback procedure. This results in the following replacement of the pr_TimeCallback procedure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Procedure pr_TimeCallback {
    Arguments: (ep_session);
    Body: {
        p_BestBound := GMP::SolverSession::GetBestBound( ep_session );
        pr_updateGap(p_BestBound, p_BestIncumbent);

        return 1 ; ! Indicate to the solver to continue.
    }
    ElementParameter ep_session {
        Range: AllSolverSessions;
        Property: Input;
    }
    Parameter p_BestBound;
}

The solver session allows you to obtain various information from the session directly, but not the incumbent. Instead, we register the latest incumbent value ourselves when the solver finds a new incumbent solution. This requires the following additional procedure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Procedure pr_IncumbentCallback {
    Arguments: (ep_session);
    Body: {
        p_BestIncumbent := GMP::SolverSession::GetObjective( ep_session );

        return 1 ; ! Indicate to the solver to continue.
    }
    ElementParameter ep_session {
        Range: AllSolverSessions;
        Property: Input;
    }
}

These two callback routines are activated as shown in the following version of the procedure pr_DoSolve:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Empty AllVariables;
pr_GenerateData();
p_BestIncumbent := 1000;
empty s_Timepoints ;
block where progress_time_interval := 1 ;
    ep_GMP := gmp::Instance::Generate( FlowShopModel );
    gmp::Instance::SetCallbackTime(
        GMP      :  ep_GMP,
        callback :  'pr_TimeCallback');
    GMP::Instance::SetCallbackNewIncumbent(
        GMP      :  ep_GMP,
        callback :  'pr_IncumbentCallback');
    gmp::Instance::Solve( ep_GMP );
endblock ;
pr_prepInterface;

After running the adapted model, the progress window shows the following results:

../../_images/3-progress-window.png

Example Project 3

If you want to replay this yourself, please download and run 3.flowshop...zip and press the solve button in the lower right corner.

You have now converted the Solve statement to use GMP!

Last Updated: September, 2019