Model#
The model class is used to group equations and label them for solving. It also allows specifying the problem type (e.g. LP, MIP, etc.), the sense of the problem (MIN, MAX, FEASIBILITY), and the objective of the problem.
The general syntax for creating a model is as follows:
from gamspy import Container, Variable, Equation, Model, Sense, Problem
m = Container()
z = Variable(m, "z") # objective variable
e1 = Equation(m, "e1")
e1[...] = <definition_of_the_equation>
e2 = Equation(m, "e2")
e2[...] = <definition_of_the_equation>
example_model = Model(m, name="dummy", equations=[e1,e2], problem=Problem.LP, sense=Sense.Max, objective=z)
Note
In case the name is not provided, it will be autogenerated.
Classification of Models#
Various types of problems can be solved with GAMS. Note that the model type must be specified before solving. The model types are briefly discussed in this section. GAMS verifies that the model is of the specified type and issues error messages if there is a mismatch, such as when a supposedly linear model contains nonlinear terms. Some problems can be solved in multiple ways, and the user must choose the appropriate method. For example, if the model contains binary or integer variables, it can be solved either as a MIP or as an RMIP.
Model Type |
Model Type Description |
|---|---|
LP |
Linear Program |
NLP |
Nonlinear Program |
QCP |
Quadratically Constrained Program |
DNLP |
Discontinuous Nonlinear Program |
MIP |
Mixed Integer Program |
RMIP |
Relaxed Mixed Integer Program |
MINLP |
Mixed Integer Nonlinear Program |
RMINLP |
Relaxed Mixed Integer Nonlinear Program |
MIQCP |
Mixed Integer Quadratically Constrained Program |
RMIQCP |
Relaxed Mixed Integer Quadratically Constrained Program |
MCP |
Mixed Complementarity Problem |
CNS |
Constrained Nonlinear System |
MPEC |
Mathematical Programs with Equilibrium Constraints |
RMPEC |
Relaxed Mathematical Program with Equilibrium Constraints |
EMP |
Extended Mathematical Program |
MPSGE |
General Equilibrium |
All model types are exposed with Problem enum, but the problem type
can be specified as a string as well.
Also the direction types of the optimization (MIN, MAX, or FEASIBILITY) are
exposed with Sense enum but it can be specified as a string similarly.
Matches for MCP Models#
Mixed Complementarity Problem (MCP) models can be defined as pair-wise complementarities between
variables and equations. The Model class accepts these pair-wise complementarities via the matches
argument in its constructor.
p = Variable(m, "p", type=VariableType.POSITIVE, domain=c)
y = Variable(m, "y", type=VariableType.POSITIVE, domain=s)
i = Variable(m, "i", type=VariableType.POSITIVE, domain=h)
mkt = Equation(m, "mkt", domain=c)
profit = Equation(m, "profit", domain=s)
income = Equation(m, "income", domain=h)
mkt[c] = Sum(s, a[c, s] * y[s]) + Sum(h, e[c, h]) >= Sum(
h.where[esub[h] != 1],
(i[h] / Sum(cc, alpha[cc, h] * p[cc] ** (1 - esub[h])))
* alpha[c, h]
* (1 / p[c]) ** esub[h],
) + Sum(h.where[esub[h] == 1], i[h] * alpha[c, h] / p[c])
profit[s] = -Sum(c, a[c, s] * p[c]) >= 0
income[h] = i[h] >= Sum(c, p[c] * e[c, h])
hansen = Model(
m,
"hansen",
problem=Problem.MCP,
matches={mkt: p, profit: y, income: i},
)
You do not need to include equations already provided in matches in the equations argument. An example MCP model can be found in the model library: HANSMCP.
Model Attributes#
Models have attributes that store a variety of information, including
information about the results of solving a model, the solve statement, and the model’s solution,
information about certain features to be used by GAMS or the solver,
information passed to GAMS or the solver specifying various settings that are also subject to option statements.
Model Attribute |
Description |
|---|---|
num_domain_violations |
Number of domain violations |
algorithm_time |
Solver-dependent timing information |
total_solve_time |
Elapsed time it took to execute a solve statement in total |
total_solver_time |
Elapsed time taken by the solver only |
num_iterations |
Number of iterations used |
marginals |
Indicator for marginals present |
max_infeasibility |
Maximum of infeasibilities |
mean_infeasibility |
Mean of infeasibilities |
status |
Integer number that indicates the model status |
num_nodes_used |
Number of nodes used by the MIP solver |
solve_number |
Number of the last solve |
num_dependencies |
Number of dependencies in a CNS model |
num_discrete_variables |
Number of discrete variables |
num_infeasibilities |
Number of infeasibilities |
num_nonlinear_insts |
Number of nonlinear instructions |
num_nonlinear_zeros |
Number of nonlinear nonzeros |
num_nonoptimalities |
Number of nonoptimalities |
num_nonzeros |
Number of nonzero entries in the model coefficient matrix |
num_mcp_redefinitions |
Number of MCP redefinitions |
num_variables |
Number of variables |
num_bound_projections |
Number of bound projections during model generation |
objective_estimation |
Estimate of the best possible solution for a mixed-integer model |
objective_value |
Objective function value |
used_model_type |
Integer number that indicates the used model type |
model_generation_time |
Time GAMS took to generate the model in wall-clock seconds |
solve_model_time |
Time the solver used to solve the model in seconds |
sum_infeasibilities |
Sum of infeasibilities |
solve_status |
Indicates the solver termination condition |
solver_version |
Solver version |
Solving a Model#
The Model class has a function named solve that allows users to solve the specified model.
from gamspy import Container, Variable, Equation, Model, Sense, Problem, Options
m = Container()
z = Variable(m, "z") # objective variable
e1 = Equation(m, "e1")
e1[...] = <definition_of_the_equation>
e2 = Equation(m, "e2")
e2[...] = <definition_of_the_equation>
model = Model(m, "dummy", equations=[e1,e2], problem=Problem.LP, sense=Sense.Max, objective=z)
summary = model.solve(solver="CONOPT", options=Options(iteration_limit=2), solver_options={"rtmaxv": "1.e12"})
print(summary)
In most cases, calling the solve function without any parameters is sufficient.
In this scenario, the default solver depending on the problem type, default options will be used.
However, users who require more control can specify the solver, general options, and solver-specific
options. All installed solvers on your system can be queried by running the following command:
gamspy list solvers
To see all available solvers that can be installed and used, run the following command.:
gamspy list solvers -a
solve function returns a Pandas DataFrame which contains the summary of the solve.
Redirecting Output#
The output of GAMS after solving the model can be redirected to a file, to standard input or to any
custom stream that supports write and flush operations by specifying the output parameter in
the solve function.:
from gamspy import Container, Variable, Equation, Model, Sense, Problem
import sys
m = Container()
z = Variable(m, "z") # objective variable
e1 = Equation(m, "e1")
e1[...] = <definition_of_the_equation>
e2 = Equation(m, "e2")
e2[...] = <definition_of_the_equation>
model = Model(m, "dummy", equations=[e1,e2], problem=Problem.LP, sense=Sense.Max, objective=z)
# redirect output to stdout
model.solve(output=sys.stdout)
# redirect output to a file
with open("my_out_file", "w") as file:
model.solve(output=file)
# redirect to custom stream
class MyStream:
def write(self, data):
logger.info(data.strip())
def flush(self): ...
my_stream = MySteam()
model.solve(output=my_stream)
Solving Locally#
By default, models are solved locally (on your machine).
Solving with GAMS Engine#
Synchronous Solve#
In order to send your model to be solved to GAMS Engine,
you need to define the GAMS Engine configuration.
This can be done by importing EngineClient and creating an instance. The user can then pass this instance to the
solve method and specify the backend as engine.
from gamspy import Container, Variable, Equation, Model, Sense, Problem, EngineClient
m = Container()
z = Variable(m, "z") # objective variable
e1 = Equation(m, "e1")
e1[...] = <definition_of_the_equation>
e2 = Equation(m, "e2")
e2[...] = <definition_of_the_equation>
model = Model(m, "dummy", equations=[e1,e2], problem=Problem.LP, sense=Sense.Max, objective=z)
client = EngineClient(
host=os.environ["ENGINE_URL"],
username=os.environ["ENGINE_USER"],
password=os.environ["ENGINE_PASSWORD"],
namespace=os.environ["ENGINE_NAMESPACE"],
)
model.solve(solver="CONOPT", backend="engine", client=client)
Note
Extra model file paths that are provided through the extra_model_files argument of EngineClient must be
relative to the working directory. For example, if your working directory is “/foo/bar”, your extra
model file path cannot be “/foo”.
Asynchronous Solve#
If you just want to send your jobs to GAMS Engine without blocking until the results are received, the is_blocking parameter can be set to False in EngineClient.
Tokens of the submitted jobs are stored in client.tokens
from gamspy import Container, Variable, Equation, Model, Sense, Problem, EngineClient
m = Container()
...
...
<define_your_model>
...
...
client = EngineClient(
host=os.environ["ENGINE_URL"],
username=os.environ["ENGINE_USER"],
password=os.environ["ENGINE_PASSWORD"],
namespace=os.environ["ENGINE_NAMESPACE"],
)
for _ in range(3):
...
...
<changes_in_your_model>
...
...
model.solve(backend="engine", client=client)
print(client.tokens) # This prints all tokens for the submitted jobs
The results of the non-blocking jobs can be retrieved later. For example if want to retrieve the results of the last submitted job, we can do that following:
token = client.tokens[-1]
client.job.get_results(token, working_directory="out_dir")
The results would be downloaded to the given working directory. The downloaded gdx file will have the same name with m.gdxOutputPath(). Then, if one wants to read the results, they can simply create a new Container and read the results from the downloaded gdx file:
gdx_out_path = os.path.join("out_dir", os.path.basename(m.gdxOutputPath()))
container = Container(load_from=gdx_out_path)
# or
container = Container()
container.read(gdx_out_path)
Solving with NEOS Server#
Synchronous Solve#
In order to send your model to be solved to NEOS Server, you need to create a NeosClient.
This can be done by importing NeosClient and creating an instance. The user can then pass this instance to the
solve method and specify the backend as neos.
from gamspy import Container, Variable, Equation, Model, Sense, Problem, NeosClient
m = Container()
z = Variable(m, "z") # objective variable
e1 = Equation(m, "e1")
e1[...] = <definition_of_the_equation>
e2 = Equation(m, "e2")
e2[...] = <definition_of_the_equation>
model = Model(m, "dummy", equations=[e1,e2], problem=Problem.LP, sense=Sense.Max, objective=z)
client = NeosClient(
email=os.environ["NEOS_EMAIL"],
username=os.environ["NEOS_USER"],
password=os.environ["NEOS_PASSWORD"],
)
model.solve(backend="neos", client=client)
Providing your username and password is optional for the NEOS Server backend, but it is recommended as it allows you to review your models on the NEOS web client. The environment variables can be set in a .env file or with export statements in command line. Example of running your model on NEOS Server without authentication:
NEOS_EMAIL=<your_email> python <your_script>
If one wants to investigate the results later on NEOS Server web client, they can provide the username and password in the same way:
NEOS_EMAIL=<your_email> NEOS_USER=<your_username> NEOS_PASSWORD=<your_password> python <your_script>
Alternatively, the output of NEOS can be redirected to a file by specifying the output stream:
model.solve(backend="neos", client=client, output=sys.stdout)
Asynchronous Solve#
If you just want to send your jobs to NEOS server without blocking until the results are received, is_blocking parameter can be set to False in NeosClient.
All submitted jobs are stored in client.jobs in case you want to reach to the job numbers and job passwords you already sent to the server.
from gamspy import Container, Variable, Equation, Model, Sense, Problem, NeosClient
m = Container()
...
...
<define_your_model>
...
...
client = NeosClient(
email=os.environ["NEOS_EMAIL"],
username=os.environ["NEOS_USER"],
password=os.environ["NEOS_PASSWORD"],
)
for _ in range(3):
...
...
<changes_in_your_model>
...
...
model.solve(backend="neos", client=client)
print(client.jobs) # This prints all job numbers and jon passwords as a list of tuples
The results of the non-blocking jobs can be retrieved later. For example if want to retrieve the results of the last submitted job, we can do that following:
job_number, job_password = client.jobs[-1]
client.get_final_results(job_number, job_password)
client.download_output(
job_number, job_password, working_directory="my_out_directory"
)
The results would be downloaded to the given working directory. The downloaded gdx file will always have the name “output.gdx”. Then, if one wants to read the results, they can simply create a new Container and read the results from the downloaded gdx file:
container = Container(load_from="my_out_directory/output.gdx")
# or
container = Container()
container.read("my_out_directory/output.gdx")
Terms of use for NEOS can be found here: Terms of use.
Solve Options#
Solve options can be specified using the gamspy.Options() class. For example:
from gamspy import Container, Variable, Equation, Model, Sense, Problem, Options
m = Container()
...
...
Definition of your model
...
...
model = Model(m, "my_model", equations=m.getEquations(), problem=Problem.LP, sense=Sense.Max, objective=z)
model.solve(options=Options(iteration_limit=2))
Here is the list of options and their descriptions:
Option |
Description |
Possible Values |
|---|---|---|
cns |
Default cns solver |
Any solver installed in your system that can solve cns |
dnlp |
Default dnlp solver |
Any solver installed in your system that can solve dnlp |
emp |
Default emp solver |
Any solver installed in your system that can solve emp |
lp |
Default lp solver |
Any solver installed in your system that can solve lp |
mcp |
Default mcp solver |
Any solver installed in your system that can solve mcp |
minlp |
Default minlp solver |
Any solver installed in your system that can solve minlp |
mip |
Default mip solver |
Any solver installed in your system that can solve mip |
miqcp |
Default miqcp solver |
Any solver installed in your system that can solve miqcp |
mpec |
Default mpec solver |
Any solver installed in your system that can solve mpec |
nlp |
Default nlp solver |
Any solver installed in your system that can solve nlp |
qcp |
Default qcp solver |
Any solver installed in your system that can solve qcp |
rminlp |
Default rminlp solver |
|
rmip |
Default rmip solver |
Any solver installed in your system that can solve rmip |
rmiqcp |
Default rmiqcp solver |
|
rmpec |
Default rmpec solver |
Any solver installed in your system that can solve rmpec |
allow_suffix_in_equation |
Allow variables with suffixes in model algebra |
bool |
allow_suffix_in_limited_variables |
Allow domain limited variables with suffixes in model |
bool |
basis_detection_threshold |
Basis detection threshold |
float |
compile_error_limit |
Compile time error limit |
int |
domain_violation_limit |
Domain violation limit solver default |
int |
hold_fixed_variables |
Treat fixed variables as constants |
bool |
iteration_limit |
Iteration limit of solver |
int |
keep_temporary_files |
Controls keeping or deletion of process directory and scratch files |
bool |
license |
Use alternative license file |
Path to the alternative license |
listing_file |
Listing file name |
Name of the listing file |
log_file |
Log file name |
Name of the log file |
variable_listing_limit |
Maximum number of columns listed in one variable block |
int |
equation_listing_limit |
Maximum number of rows listed in one equation block |
int |
node_limit |
Node limit in branch and bound tree |
int |
absolute_optimality_gap |
Absolute Optimality criterion solver default |
float |
relative_optimality_gap |
Relative Optimality criterion solver default |
float |
memory_tick_interval |
Wait interval between memory monitor checks: ticks = milliseconds |
float |
monitor_process_tree_memory |
Monitor the memory used by the GAMS process tree |
bool |
profile |
Execution profiling |
0: No profiling 1: Minimum profiling 2: Profiling depth for nested control structures |
profile_file |
Write profile information to this file |
str |
profile_tolerance |
Minimum time a statement must use to appear in profile generated output |
float |
reference_file |
Symbol reference file |
str |
time_limit |
Wall-clock time limit for solver |
float |
savepoint |
Save solver point in GDX file |
0: No point GDX file is to be saved 1: A point GDX file from the last solve is to be saved 2: A point GDX file from every solve is to be saved 3: A point GDX file from the last solve is to be saved 4: A point GDX file from every solve is to be saved |
seed |
Random number seed |
int |
report_solution |
Solution report print option |
0: Remove solution listings following solves 1: Include solution listings following solves 2: Suppress all solution information |
show_os_memory |
0: Show memory reported by internal accounting 1: Show resident set size reported by operating system 2: Show virtual set size reported by operating system |
|
solve_link_type |
Solve link option |
“disk”: The model instance is saved to the scratch directory, “memory”: The model instance is passed to the solver in-memory |
multi_solve_strategy |
Multiple solve management |
“replace” | “merge” | “clear” |
step_summary |
Summary of computing resources used by job steps |
bool |
suppress_compiler_listing |
Compiler listing option |
bool |
report_solver_status |
Solver Status file reporting option |
bool |
threads |
Number of threads to be used by a solver |
int |
trace_file |
Trace file name |
Name of the trace file |
trace_level |
Modelstat/Solvestat threshold used in conjunction with action=GT |
int |
trace_file_format |
Trace file format option |
0: Solver and GAMS step trace 1: Solver and GAMS exit trace 2: Solver trace only 3: Trace only in format used for GAMS performance world 5: Gams exit trace with all available trace fields |
write_listing_file |
Controls listing file creation |
bool |
zero_rounding_threshold |
The results of certain operations will be set to zero if abs(result) LE ZeroRes |
float |
report_underflow |
Report underflow as a warning when abs(results) LE ZeroRes and result set to zero |
bool |
Solver Options#
In addition to solve options, user can specify solver options as a dictionary.:
from gamspy import Container, Variable, Equation, Model, Sense, Problem
m = Container()
...
...
Definition of your model
...
...
model = Model(m, "my_model", equations=m.getEquations(), problem=Problem.LP, sense=Sense.Max, objective=z)
model.solve(solver="CONOPT", solver_options={"rtmaxv": "1.e12"})
For all possible solver options, please check the corresponding solver manual
Exporting Model To Latex#
GAMSPy models can be exported to a .tex file in LaTex format by using the toLatex function of the model.
The generated .tex file can be automatically compiled into a PDF file by using pdflatex
from gamspy import Container, Variable, Equation, Model, Sense, Problem
m = Container()
...
...
Definition of your model
...
...
model = Model(m, "my_model", equations=m.getEquations(), problem=Problem.LP, sense=Sense.Max, objective=z)
model.toLatex(path=<latex_path>, generate_pdf=True)
Note
To generate a PDF file from a .tex file, you must install pdflatex on your system and add it to your PATH.