Deployment Methods¶
Deployment on Azure functions¶
To facilitate installation and deployment of AMPL in .NET, we created a set of NuGet packages that include everything needed for the execution. A deployment of AMPL usable from .NET comprises of:
The .NET Api (package
AMPL.Api
)AMPL binaries for the specific platform (package
AMPL.Module.base.[platform]
)All required solver(s) binaries (package
AMPL.Module.[solvername].[platform]
)
See for example this list for the platforms where a NuGet module with the AMPL binaries is available.
Tutorial: create and deploy an Azure function using Visual Studio¶
Visual Studio¶
Create a new project using the Azure functions template
Right click on the project icon and click
Manage NuGet Packages
Search and install
AMPL.Api
,AMPL.Module.base.mswin64
if deployment target is windows, orAMPL.Module.base.linux64
if target is linux. Also installAMPL.Module.highs.[platform]
andAMPL.Module.gurobi.[platform]
. Note: enable the “include prerelease” box.
With the current template, there is a dependency conflict. Edit the project file and downgrade the module
Microsoft.Azure.Functions.Worker.Sdk
to version1.16.2
.
When the source code is ready (see section below), right click on the project file and choose “Publish”. Create a new Azure functions app, or bind to an existing one and follow the instructions on screen.
VS Code or command line¶
Follow this guide to create an azure functions project
To add relevant references, execute:
dotnet add package AMPL.Api --prerelease
dotnet add package AMPL.Module.base.linux64 --prerelease
dotnet add package AMPL.Module.highs.linux64 --prerelease
dotnet add package AMPL.Module.gurobi.linux64 --prerelease
Source code¶
In this tutorial, we will create a simple function that accepts a JSON payload. The JSON file will represent a dictionary with three entries:
“model” : a string containing AMPL statements
“data” : a dictionary containing mappings between AMPL parameters (defined in the string above) and their values
“solver : a string defining which solver to use
[Function("SolveAMPLModel")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
{
if (req.Method == "POST")
{
// Read the request body
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var data = JsonConvert.DeserializeObject<dynamic>(requestBody);
_logger.LogInformation($"Received data: {data}");
if (data == null)
{
return new BadRequestObjectResult(new { message = "Empty data received" });
}
// Access individual members
string model = data.model;
var datavalues = data.data;
string solver = data.solver;
try
{
// Enable the NuGet packages
AMPL.LoadModules();
}
catch (Exception ex)
{
_logger.LogCritical($"LoadModules exception: {ex.Message}");
return new BadRequestObjectResult(new { message = ex.Message });
}
StringBuilder sb = new StringBuilder();
try
{
using (var ampl = new AMPL())
{
_logger.LogInformation($"Created AMPL");
// *** Output redirection *** Enable output redirection
ampl.EnableOutputRouting();
ampl.EnableErrorAndWarningRouting();
// Assign handlers
ampl.Output += (kind, message) => { _logger.LogInformation(message); sb.Append(message); };
ampl.Error += (amplexception) => { _logger.LogCritical(amplexception.Message); sb.Append(amplexception.Message); };
ampl.Warning += (amplexception) => { _logger.LogWarning(amplexception.Message); sb.Append(amplexception.Message); };
// Evaluate the statement
ampl.Eval(model);
// Assign the data (note the limitations above)
AssignDataFromJsonDictionary(datavalues, ampl);
// Set the solver
ampl.SetOption("solver", solver);
// Solve the model
ampl.Solve();
// Get the first objective and the time taken to solve
var objective = ampl.GetObjectives().First();
var timeToSolve = ampl.GetValue("_total_solve_time");
// Return information
return new OkObjectResult(new { message = sb.ToString() + $"\n{objective.Name} = {objective.Value} (solved by {solver} in {timeToSolve}s)" });
}
}
catch (Exception e)
{
return new OkObjectResult(new { message = e.Message });
}
}
return new BadRequestObjectResult("Please send a POST request with JSON data.");
}
The following helper function is also (temporarily) used to pass JSON data to AMPL:
private void AssignDataFromJsonDictionary(JObject data, AMPL a)
{
// Iterate through the key-value pairs in 'data'
foreach (var item in data)
{
string key = item.Key;
JToken value = item.Value;
_logger.LogDebug($"Key: {key}, Value: {value}");
// Check if the value is a number
if (value.Type == JTokenType.Integer)
{
int number = value.ToObject<int>();
a.Param[key].Set(number);
}
else if (value.Type == JTokenType.String)
{
string str = value.ToObject<string>();
a.Param[key].Set(str);
}
// Check if the value is an array
else if (value.Type == JTokenType.Array)
{
try
{
var arrdbl = value?.ToObject<double[]>();
a.Param[key].SetValues(arrdbl);
continue;
}
catch (Exception)
{ }
try
{
var arrstr = value?.ToObject<string[]>();
a.Param[key].SetValues(arrstr);
}
catch (Exception ex)
{
_logger.LogCritical($"Invalid type: {value.Type.ToString()}");
}
}
else
_logger.LogCritical($"Invalid type: {value.Type.ToString()}");
}
}
Running the example¶
To run the example, we can use the following JSON file:
{
"model": "param a; param b; param c{1..2}; var x<=a+b+sum{i in 1..2} c[i]; maximize z: x;",
"data" :
{
"a" : 11,
"b" : 12,
"c" : [ 13, 14 ]
},
"solver" : "gurobi"
}
and submit it with CURL:
curl -X POST -H "Content-Type: application/json" -d @model.json https://amplfunctionlinux.azurewebsites.net/api/SolveAMPLModel?code=cepQpdsOwA9eFW_ZvCb31ho674FT8_j_YEoEWfdbFnqJAzFuT28gag==
Monitoring¶
In the Azure portal, we can see the logs of any execution: