Source code for gamspy._options

from __future__ import annotations

import logging
import os
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, Optional

from gams import GamsOptions, GamsWorkspace, SymbolUpdateType
from pydantic import BaseModel

from gamspy.exceptions import ValidationError

logger = logging.getLogger("Options")
logger.setLevel(logging.INFO)

formatter = logging.Formatter("[%(name)s - %(levelname)s] %(message)s")
handler = logging.StreamHandler()
handler.setFormatter(formatter)
handler.setLevel(logging.INFO)
logger.addHandler(handler)

if TYPE_CHECKING:
    from gamspy._model import Problem

multi_solve_map = {"replace": 0, "merge": 1, "clear": 2}

# GAMSPy to GAMS Control mapping
option_map = {
    "cns": "cns",
    "dnlp": "dnlp",
    "emp": "emp",
    "lp": "lp",
    "mcp": "mcp",
    "minlp": "minlp",
    "mip": "mip",
    "miqcp": "miqcp",
    "mpec": "mpec",
    "nlp": "nlp",
    "qcp": "qcp",
    "rminlp": "rminlp",
    "rmip": "rmip",
    "rmiqcp": "rmiqcp",
    "rmpec": "rmpec",
    "allow_suffix_in_equation": "suffixalgebravars",
    "allow_suffix_in_limited_variables": "suffixdlvars",
    "basis_detection_threshold": "bratio",
    "compile_error_limit": "cerr",
    "domain_violation_limit": "domlim",
    "job_time_limit": "etlim",
    "job_heap_limit": "heaplimit",
    "hold_fixed_variables": "holdfixed",
    "integer_variable_upper_bound": "intvarup",
    "iteration_limit": "iterlim",
    "keep_temporary_files": "keep",
    "listing_file": "output",
    "log_file": "_logfile",
    "variable_listing_limit": "limcol",
    "equation_listing_limit": "limrow",
    "node_limit": "nodlim",
    "absolute_optimality_gap": "optca",
    "relative_optimality_gap": "optcr",
    "profile": "profile",
    "profile_tolerance": "profiletol",
    "time_limit": "reslim",
    "savepoint": "savepoint",
    "seed": "seed",
    "report_solution": "solprint",
    "show_os_memory": "showosmemory",
    "solver_link_type": "solvelink",
    "merge_strategy": "solveopt",
    "step_summary": "stepsum",
    "suppress_compiler_listing": "suppress",
    "report_solver_status": "sysout",
    "threads": "threads",
    "write_listing_file": "_writeoutput",
    "zero_rounding_threshold": "zerores",
    "report_underflow": "zeroresrep",
}


[docs] class Options(BaseModel): cns: Optional[str] = None dnlp: Optional[str] = None emp: Optional[str] = None lp: Optional[str] = None mcp: Optional[str] = None minlp: Optional[str] = None mip: Optional[str] = None miqcp: Optional[str] = None mpec: Optional[str] = None nlp: Optional[str] = None qcp: Optional[str] = None rminlp: Optional[str] = None rmip: Optional[str] = None rmiqcp: Optional[str] = None rmpec: Optional[str] = None allow_suffix_in_equation: Optional[bool] = None allow_suffix_in_limited_variables: Optional[bool] = None basis_detection_threshold: Optional[float] = None compile_error_limit: int = 1 domain_violation_limit: Optional[int] = None job_time_limit: Optional[float] = None job_heap_limit: Optional[float] = None hold_fixed_variables: Optional[bool] = None integer_variable_upper_bound: Optional[int] = None iteration_limit: Optional[int] = None keep_temporary_files: bool = False listing_file: Optional[str] = None log_file: Optional[str] = None variable_listing_limit: Optional[int] = None equation_listing_limit: Optional[int] = None node_limit: Optional[int] = None absolute_optimality_gap: Optional[float] = None relative_optimality_gap: Optional[float] = None profile: Optional[int] = None profile_tolerance: Optional[float] = None redirect_log_to_stdout: Optional[bool] = False time_limit: Optional[float] = None savepoint: Optional[Literal[0, 1, 2, 3, 4]] = None seed: Optional[int] = None report_solution: Literal[0, 1, 2] = 2 show_os_memory: Literal[0, 1, 2] = 0 solver_link_type: Optional[Literal[0, 1, 2, 3, 4, 5, 6, 7]] = None merge_strategy: Optional[Literal["replace", "merge", "clear"]] = None step_summary: Optional[bool] = None suppress_compiler_listing: bool = False report_solver_status: Optional[bool] = None threads: Optional[int] = None write_listing_file: bool = True zero_rounding_threshold: Optional[float] = None report_underflow: Optional[bool] = None def _get_gams_compatible_options(self) -> dict: gamspy_options = self.model_dump(exclude_none=True) if "allow_suffix_in_equation" in gamspy_options: allows_suffix = gamspy_options["allow_suffix_in_equation"] gamspy_options["allow_suffix_in_equation"] = ( "on" if allows_suffix else "off" ) if "allow_suffix_in_limited_variables" in gamspy_options: allows_suffix = gamspy_options["allow_suffix_in_limited_variables"] gamspy_options["allow_suffix_in_limited_variables"] = ( "on" if allows_suffix else "off" ) if "merge_strategy" in gamspy_options: strategy = gamspy_options["merge_strategy"] gamspy_options["merge_strategy"] = multi_solve_map[strategy] if "listing_file" in gamspy_options: os.makedirs( Path(gamspy_options["listing_file"]).parent.absolute(), exist_ok=True, ) if not os.path.isabs(gamspy_options["listing_file"]): gamspy_options["listing_file"] = os.path.abspath( gamspy_options["listing_file"] ) if "log_file" in gamspy_options: os.makedirs( Path(gamspy_options["log_file"]).parent.absolute(), exist_ok=True, ) if not os.path.isabs(gamspy_options["log_file"]): gamspy_options["log_file"] = os.path.abspath( gamspy_options["log_file"] ) gams_options = { option_map[key]: value for key, value in gamspy_options.items() if key in option_map } gams_options["previouswork"] = ( 1 # # In case GAMS version differs on backend ) gams_options["traceopt"] = 3 if self.log_file: if self.redirect_log_to_stdout: gams_options["_logoption"] = 4 else: gams_options["_logoption"] = 2 else: if self.redirect_log_to_stdout: gams_options["_logoption"] = 3 else: gams_options["_logoption"] = 0 return gams_options def _set_extra_options( self, working_directory: str, solver: str | None, solver_options: dict | None, ): extra_options: dict[str, Any] = {} if solver is not None: extra_options["solver"] = solver if solver_options: if solver is None: raise ValidationError( "You need to provide a 'solver' to apply solver options." ) solver_file_name = os.path.join( working_directory, f"{solver.lower()}.123" ) with open(solver_file_name, "w", encoding="utf-8") as solver_file: for key, value in solver_options.items(): solver_file.write(f"{key} {value}\n") extra_options["optfile"] = 123 self._extra_options = extra_options def _get_gams_options( self, workspace: GamsWorkspace, problem: Problem | None = None ) -> GamsOptions: gams_options = GamsOptions(workspace) if hasattr(self, "_extra_options") and "solver" in self._extra_options: solver = self._extra_options["solver"] gams_options.all_model_types = solver if problem is not None and solver.lower() != getattr(gams_options, str(problem).lower()).lower(): raise ValidationError( f"Given solver `{solver}` is not capable of solving given" f" problem type `{problem}`. See capability matrix " "(https://www.gams.com/latest/docs/S_MAIN.html#SOLVERS_MODEL_TYPES)" " to choose a suitable solver" ) if ( hasattr(self, "_extra_options") and "optfile" in self._extra_options ): gams_options.optfile = self._extra_options["optfile"] gams_options_dict = self._get_gams_compatible_options() for key, value in gams_options_dict.items(): setattr(gams_options, key, value) return gams_options
update_type_map = { "0": SymbolUpdateType.Zero, "base_case": SymbolUpdateType.BaseCase, "accumulate": SymbolUpdateType.Accumulate, "inherit": SymbolUpdateType._Inherit, }
[docs] class ModelInstanceOptions(BaseModel): solver: Optional[str] = None opt_file: int = -1 no_match_limit: int = 0 debug: bool = False update_type: Literal["0", "base_case", "accumulate", "inherit"] = ( "base_case" ) def items(self): dictionary = self.model_dump() dictionary["update_type"] = update_type_map[dictionary["update_type"]] return dictionary