Quick Start#
This section will show a simple example to illustrate various functionalities of the AMPL C interface. The full example prints the version of the AMPL interpreter used, loads a model from file and the corresponding data file, solves it, uses AMPL entities in C to get results and to assign data programmatically. This section assumes that you are already familiar with the C language. Full reference is given in C API Reference.
Complete listing#
This is the complete listing of the example. Please note that, for clarity of presentation, all the code in the examples below does not include error handling.
#include <stdio.h>
#include "ampl/ampl_c.h"
int main(int argc, char **argv) {
// Create an AMPL instance
AMPL *ampl;
AMPL_CALL(AMPL_Create(&l));
/*
// If the AMPL installation directory is not in the system search path:
AMPL_ENVIRONMENT *env;
AMPL_EnvironmentCreateWithBin(&env, "full path to the AMPL installation directory","");
AMPL_CALL(AMPL_CreateWithEnv(&l, env));
*/
if (argc > 1) AMPL_CALL(AMPL_SetOption(ampl, "solver", argv[1]));
// Read the model and data files.
const char *modelDirectory = argc == 3 ? argv[2] : "../models";
char mod[256];
char dat[256];
sprintf(mod, "%s%s", modelDirectory, "/diet/diet.mod");
sprintf(dat, "%s%s", modelDirectory, "/diet/diet.dat");
AMPL_CALL(AMPL_Read(ampl, mod));
AMPL_CALL(AMPL_ReadData(ampl, dat));
// Solve
AMPL_CALL(AMPL_Solve(ampl, "", ""));
// Get objective entity value by AMPL name
double value;
AMPL_CALL(AMPL_GetValueNumeric(ampl, "Total_Cost", &value));
printf("Objective is: %f\n", value);
// Reassign data - specific instances
const char *foods[] = {"BEEF", "HAM"};
double costs[] = {5.01, 4.55};
for (size_t i = 0; i < 2; i++) {
AMPL_TUPLE *tuple;
AMPL_TupleCreateString(&tuple, 1, &foods[i]);
AMPL_CALL(AMPL_ParameterInstanceSetNumericValue(ampl, "cost", tuple, costs[i]));
AMPL_TupleFree(&tuple);
}
printf("Increased costs of beef and ham.\n");
// Resolve and display objective
AMPL_CALL(AMPL_Solve(ampl, "", ""));
AMPL_CALL(AMPL_GetValueNumeric(ampl, "Total_Cost", &value));
printf("New objective is: %f\n", value);
// Reassign data - all instances
const double elements[8] = {3, 5, 5, 6, 1, 2, 5.01, 4.55};
AMPL_CALL(AMPL_ParameterSetArgsDoubleValues(ampl, "cost", 8, elements));
printf("Updated all costs.\n");
// Resolve and display objective
AMPL_CALL(AMPL_Solve(ampl, "", ""));
AMPL_CALL(AMPL_GetValueNumeric(ampl, "Total_Cost", &value));
printf("New objective is: %f\n", value);
// Get the values of the variable Buy in a dataframe object
AMPL_DATAFRAME *dataframe;
char *dataframe_string;
AMPL_CALL(AMPL_EntityGetValues(ampl, "Buy", NULL, 0, &dataframe));
AMPL_CALL(AMPL_DataFrameToString(dataframe, &dataframe_string));
// Print them
printf("%s\n", dataframe_string);
AMPL_StringFree(&dataframe_string);
AMPL_DataFrameFree(&dataframe);
// Get the values of an expression into a DataFrame object
const char *expr[] = {"{j in FOOD} 100*Buy[j]/Buy[j].ub"};
AMPL_CALL(AMPL_GetData(ampl, expr, 1, &dataframe));
AMPL_CALL(AMPL_DataFrameToString(dataframe, &dataframe_string));
// Print them
printf("%s\n", dataframe_string);
AMPL_StringFree(&dataframe_string);
AMPL_DataFrameFree(&dataframe);
//AMPL_EnvironmentFree(&env);
AMPL_Free(&l);
return 0;
}
Needed headers and AMPL environment creation#
For a simple hello world program, first include the needed headers. All the headers in the AMPL C API distribution reside in the directory /ampl.
The most important header is ampl_c.h, which defines the main AMPL struct.
Please note that all functions part of the AMPL C API have the prefix AMPL_.
Most of the AMPL C API functions return a pointer to the
#include "ampl/ampl_c.h"
Then copy the following statements to have a hello world application which gets the value of the option version as defined in the underlying AMPL executable and prints the result on the console.
The AMPL_Create() allocates a new AMPL object with all default settings (it has to be freed at the end).
AMPL_GetOption(), which is the preferred way to access AMPL options, gets the value of the option
version from AMPL as a string. At the end, we free the memory allocated for the string with AMPL_StringFree()
and the AMPL object with AMPL_Free().
Load a model from file#
The following lines use the method AMPL_Read() to load a model and data stored in external (AMPL) files.
If the files are not found, a runtime_error is thrown.
char mod[256];
char dat[256];
sprintf(mod, "%s%s", modelDirectory, "/diet/diet.mod");
sprintf(dat, "%s%s", modelDirectory, "/diet/diet.dat");
AMPL_CALL(AMPL_Read(ampl, mod));
AMPL_CALL(AMPL_ReadData(ampl, dat));
Once these commands are executed, the AMPL interpreter will have interpreted the content of the two files. No further communication is made between the AMPL interpreter and the C object, as every entity is created lazily (as needed).
Solve a problem#
To solve the currently loaded problem instance, it is sufficient to issue the command:
AMPL_CALL(AMPL_Solve(ampl, "", ""));
Get an AMPL entity in the programming environment#
AMPL C API provides access to AMPL entities through their names. Usually, not all the entities are of interest for the programmer. The generic procedure is to identify the entities that need interaction (either data read or modification).
// Get objective entity value by AMPL name
double value;
AMPL_CALL(AMPL_GetValueNumeric(ampl, "Total_Cost", &value));
printf("Objective is: %f\n", value);
It can be noted that we access an objective to interrogate AMPL API about the objective function.
It is a collections of objectives. The function AMPL_GetValueNumeric() should be used in case of
the objective to get the value of the objective.
The output of the snippet above is:
Objective is: 118.05940323955669
The same is true for all other entities.
Modify model data (assign values to parameters)#
The input data of an optimization model is stored in its parameters; these can be scalar or vectorial entities.
Two ways are provided to change the value of vectorial parameter: change specific values or change all values at
once. The example shows an example of both ways, reassigning the values of the parameter costs firstly specifically,
then altogether. Each time, it then solves the model and get the objective function. The function used to change the
values specifically is called AMPL_ParameterInstanceSetNumericValue().
const char *foods[] = {"BEEF", "HAM"};
double costs[] = {5.01, 4.55};
for (size_t i = 0; i < 2; i++) {
AMPL_TUPLE *tuple;
AMPL_TupleCreateString(&tuple, 1, &foods[i]);
AMPL_CALL(AMPL_ParameterInstanceSetNumericValue(ampl, "cost", tuple, costs[i]));
AMPL_TupleFree(&tuple);
}
printf("Increased costs of beef and ham.\n");
AMPL_CALL(AMPL_Solve(ampl, "", ""));
AMPL_CALL(AMPL_GetValueNumeric(ampl, "Total_Cost", &value));
printf("New objective is: %f\n", value);
The code above assigns the values 5.01 and 4.55 to the parameter cost for the objects beef and ham respectively.
If the order of the indexing of an entity is known (i.e. for multiple reassignment), it is not necessary to specify
both the index and the value. A collection of values is assigned to each of the parameter values, in the order they are represented in AMPL.
The function used to change the values altogether is called AMPL_ParameterSetArgsDoubleValues().
const double elements[8] = {3, 5, 5, 6, 1, 2, 5.01, 4.55};
AMPL_CALL(AMPL_ParameterSetArgsDoubleValues(ampl, "cost", 8, elements));
printf("Updated all costs.\n");
AMPL_CALL(AMPL_Solve(ampl, "", ""));
AMPL_CALL(AMPL_GetValueNumeric(ampl, "Total_Cost", &value));
printf("New objective is: %f\n", value);
The statements above produce the following output (when solved with HiGHS 1.6.0):
Increased costs of beef and ham. HiGHS 1.6.0:
HiGHS 1.6.0: optimal solution; objective 144.4157204 0 simplex iterations 0 barrier iterations
New objective is: 144.415720 Updated all costs. HiGHS 1.6.0:
HiGHS 1.6.0: optimal solution; objective 164.54375 4 simplex iterations 0 barrier iterations
New objective is: 164.543750
Get numeric values from variables#
To access all the numeric values contained in a variable or any other entity, use a AMPL_DATAFRAME object. Doing so, the data is detached from
the entity, and there is a considerable performance gain. We get the data of an entity with the function AMPL_EntityGetValues().
// Get the values of the variable Buy in a dataframe object
AMPL_DATAFRAME *dataframe;
char *dataframe_string;
AMPL_CALL(AMPL_EntityGetValues(ampl, "Buy", NULL, 0, &dataframe));
AMPL_CALL(AMPL_DataFrameToString(dataframe, &dataframe_string));
// Print them
printf("%s\n", dataframe_string);
AMPL_StringFree(&dataframe_string);
AMPL_DataFrameFree(&dataframe);
Get arbitrary values via ampl expressions#
Often we are interested in very specific values coming out of the optimization session. To make use of the power of AMPL expressions and avoiding
cluttering up the environment by creating entities, fetching data through arbitrary AMPL expressions is possible. For this model, we are interested
in knowing how close each decision variable is to its upper bound, in percentage.
We can obtain this data into a dataframe using the function AMPL_GetData() with the code:
// Get the values of an expression into a DataFrame object
const char *expr[] = {"{j in FOOD} 100*Buy[j]/Buy[j].ub"};
AMPL_CALL(AMPL_GetData(ampl, expr, 1, &dataframe));
AMPL_CALL(AMPL_DataFrameToString(dataframe, &dataframe_string));
// Print them
printf("%s\n", dataframe_string);
AMPL_StringFree(&dataframe_string);
AMPL_DataFrameFree(&dataframe);
Delete the AMPL object to free the resources#
It is good practice to make sure that the AMPL object is closed and all its resources released when it is not needed any more. All the internal resources are automatically deallocated by the destructor of the AMPL object, so it is suggested to keep it stored by value.