ampl / amplpy

Python API for AMPL

Home Page:https://amplpy.ampl.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Save and reuse a whole-ampl problem

xrixhon opened this issue · comments

Hello,

I would like to know if it is possible, after the ampl problem has been solved, to store the whole ampl object in python (with something similar to PICKLE) to be able to interact further on with this object and it solution (i.e. values of the variables, dual variables, etc.)

Thank you in advance for your answer,

Best regards,

Xavier Rixhon

Hi Xavier,

In order to achieve this, it is necessary to use an upcoming feature of AMPL called snapshot that even though is still not part of the official version of AMPL, it is part of the development version that is now available to the public. The development version of AMPL is called ampldev, and it is available on portal.ampl.com (in My Downloads) and it is included on newly generated bundles (if you are using a bundle, you just need to generate a new one and ampldev will be there). At the moment the only difference between ampl and ampldev is the snapshot command.

You need to be using at least amplpy 0.8.0 (you can install it with python -m pip install amplpy==0.8.0). With this version of amplpy it is now possible to pass an additional argument to Environment (https://amplpy.readthedocs.io/en/stable/classes/environment.html) that allows specifing the executable name as follows:

ampl = AMPL(Environment('', 'ampldev'))

You can then use AMPL.getOutput/AMPL.get_output to retrieve the output of the new command "snapshot;" as a string. The string returned is a compact representation of the AMPL state (model declaration, data, solution loaded, options, etc.)

snapshot = ampl.getOutput('snapshot;')
print(snapshot)

This string can then be passed to another AMPL object using AMPL.eval. The following example produces the output below:

from amplpy import AMPL, Environment

print('First object:')
ampl = AMPL(Environment('', 'ampldev'))
ampl.read('diet.mod')
ampl.read_data('diet.dat')
ampl.option['solver'] = 'gurobi'
ampl.solve()

print('Second object:')
ampl2 = AMPL(Environment('', 'ampldev'))
snapshot = ampl.get_output('snapshot;')
print(snapshot, file=open('snapshot.run', 'w'))
ampl2.eval(snapshot)
ampl2.display('Buy')

print('Third object:')
ampl3 = AMPL(Environment('', 'ampldev'))
ampl3.eval(ampl2.get_output('snapshot;'))
ampl3.display('_VARS;')
ampl3.eval('option solver;')
First object:
Gurobi 9.1.2: optimal solution; objective 88.2
1 simplex iterations
Second object:
Buy [*] :=
BEEF   0
 CHK   0
FISH   0
 HAM   0
 MCH  46.6667
 MTL   0
 SPG   0
 TUR   0
;

Third object:
set _VARS := Buy;

option solver gurobi;

One thing that may also be useful: In the example, there is the line print(snapshot, file=open('snapshot.run', 'w')) that writes the snapshot to a file called snapshot.run. This file can be loaded into AMPL (e.g., for debugging) as follows:

$ ampl
ampl: include "snapshot.run";
ampl: display Buy;
Buy [*] :=
BEEF   0
 CHK   0
FISH   0
 HAM   0
 MCH  46.6667
 MTL   0
 SPG   0
 TUR   0
;

The snapshot feature is not finished and it is still being perfected. If you encounter any issues, please let us know.

Hi Xavier,

We have just release a new version of this development version with snapshot. If fixes a couple of issues that were reported meanwhile.

Please note that the name of the experimental binary was changed from ampldev to x-ampl (eXperimental AMPL). This new version is available from the AMPL portal under this new name and is included in every bundle including any newly generated time-limited bundles.

Starting from amplpy v0.12.0, AMPL.snapshot is now available by default and it produces a very reliable representation of the AMPL state. The following now works without any issues:

from amplpy import AMPL

ampl1 = AMPL()
ampl1.eval(r"""
set NUTR;
set FOOD;

param cost {FOOD} > 0;
param f_min {FOOD} >= 0;
param f_max {j in FOOD} >= f_min[j];

param n_min {NUTR} >= 0;
param n_max {i in NUTR} >= n_min[i];

param amt {NUTR,FOOD} >= 0;

var Buy {j in FOOD} >= f_min[j], <= f_max[j];

minimize Total_Cost:  sum {j in FOOD} cost[j] * Buy[j];

subject to Diet {i in NUTR}:
   n_min[i] <= sum {j in FOOD} amt[i,j] * Buy[j] <= n_max[i];
""")
ampl1.eval(r"""
data;

set NUTR := A B1 B2 C ;
set FOOD := BEEF CHK FISH HAM MCH MTL SPG TUR ;

param:   cost  f_min  f_max :=
  BEEF   3.19    0     100
  CHK    2.59    0     100
  FISH   2.29    0     100
  HAM    2.89    0     100
  MCH    1.89    0     100
  MTL    1.99    0     100
  SPG    1.99    0     100
  TUR    2.49    0     100 ;

param:   n_min  n_max :=
   A      700   10000
   C      700   10000
   B1     700   10000
   B2     700   10000 ;

param amt (tr):
           A    C   B1   B2 :=
   BEEF   60   20   10   15
   CHK     8    0   20   20
   FISH    8   10   15   10
   HAM    40   40   35   10
   MCH    15   35   15   15
   MTL    70   30   15   15
   SPG    25   50   25   15
   TUR    60   20   15   10 ;
""")
snapshot = ampl1.snapshot()

ampl2 = AMPL()
ampl2.eval(snapshot)
ampl2.display("amt")

You can store the snapshot string in the pickle and use it anywhere else to get AMPL to the same state as when the snapshot was taken.