Source code for line_solver.solvers._solver_base

"""
Base ``Solver`` and ``SolverOptions`` for the native solvers.

Re-exported by ``line_solver.solvers``; the solver classes themselves live in
the ``solver_*`` subpackages.
"""

import os

from line_solver.constants import VerboseLevel


class OptionsDict(dict):
    """A dict that supports attribute-style access.

    Allows both `options['key']` and `options.key` access patterns
    for backward compatibility with notebooks.
    """
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(f"'OptionsDict' object has no attribute '{name}'")

    def __setattr__(self, name, value):
        self[name] = value

    def __delattr__(self, name):
        try:
            del self[name]
        except KeyError:
            raise AttributeError(f"'OptionsDict' object has no attribute '{name}'")


[docs] class SolverOptions: """Options for solvers. SolverOptions(SolverType.X) returns solver X's own default options, as MATLAB SolverOptions(SolverType.X) does: the generic object carried no config dict and none of X's fields, so assigning it to a solver's options (the manual's ENV listing) crashed that solver at run time. """ _BY_TYPE = {'AUTO': 'SolverAUTO', 'BA': 'SolverBA', 'CTMC': 'SolverCTMC', 'LDES': 'SolverLDES', 'ENV': 'SolverENV', 'FLUID': 'SolverFLD', 'JMT': 'SolverJMT', 'LN': 'SolverLN', 'LQNS': 'SolverLQNS', 'MAM': 'SolverMAM', 'MVA': 'SolverMVA', 'NC': 'SolverNC', 'SSA': 'SolverSSA'} def __new__(cls, solver_type=None): name = getattr(solver_type, 'name', None) if name is None and isinstance(solver_type, str): # SolverOptions('CTMC'), as the manual's listings spell it; 'FLD' is the solver's short name for FLUID name = {'FLD': 'FLUID', 'DES': 'LDES'}.get(solver_type.upper(), solver_type.upper()) if cls is SolverOptions and name in SolverOptions._BY_TYPE: import line_solver solver_cls = getattr(line_solver, SolverOptions._BY_TYPE[name]) return (getattr(solver_cls, 'default_options', None) or solver_cls.defaultOptions)() return super().__new__(cls) def __init__(self, solver_type=None): self.solver_type = solver_type self.method = 'default' self.verbose = VerboseLevel.STD self.cutoff = float('inf') self.seed = 23000 # fixed for determinism harness (MATLAB/JAR use a random draw) self.iter_max = 1000 self.iter_tol = 1e-4 self.tol = 1e-4 self.samples = 10000 # see _kb/11-conventions-and-gotchas.md (Python long-tail low-hit gotchas) for rationale self.events = None self.keep = True self.force = False self.cache = True self.hide_immediate = False self.stiff = True self.timespan = [float('inf'), float('inf')] self.timestep = None # see _kb/11-conventions-and-gotchas.md (Python long-tail low-hit gotchas) for rationale self.timeout = float('inf') self.init_sol = None # Solver console (see line_solver.api.io.console): None follows the # session setting, True or False forces it for this run alone. self.console = None self.lang = os.environ.get('LINE_SOLVER_LANG', 'python') # env LINE_SOLVER_LANG overrides; 'python' (native) or 'java' (delegate to jline.jar via JSON)
def generic_option_keys(): """Keys of the generic SolverOptions. MATLAB accepts each of them on every solver and ignores the ones that solver does not read (the examples pass cutoff and keep to JMT and LDES), so they are not misspellings.""" return frozenset(k for k in vars(SolverOptions()) if k != 'solver_type')
[docs] class Solver: """Base class for all queueing network solvers."""
[docs] @staticmethod def defaultOptions(): """Get default solver options.""" return OptionsDict({ 'keep': True, 'verbose': VerboseLevel.STD, 'cutoff': float('inf'), 'seed': 23000, 'iter_max': 1000, 'samples': 10000, 'events': None, 'method': 'default', 'timeout': float('inf') })
# Snake_case alias default_options = defaultOptions def __init__(self, options=None, *args, **kwargs): self.solveropt = options if options else SolverOptions() self._verbose_silent = False self._table_silent = True # Suppress automatic table printing by default self._result = None # Process kwargs for key, value in kwargs.items(): self._process_solver_option(key, value) def _process_solver_option(self, key, value): """Process a single solver option.""" if hasattr(self.solveropt, key): setattr(self.solveropt, key, value) if key == 'verbose': self._process_verbose_option(value) def _process_verbose_option(self, value): """Process verbose option.""" if isinstance(value, bool): self._verbose_silent = not value self._table_silent = not value elif value == VerboseLevel.SILENT: self._verbose_silent = True self._table_silent = True def getName(self): """Get solver name.""" return self.__class__.__name__.replace('Solver', '') @property def name(self): """Get solver name (property access).""" return self.getName()
[docs] def supports(self, model): """Check if solver supports the model.""" return True
get_name = getName default_options = defaultOptions