text: # --------------------------------------------------------------------------
  # Source file provided under Apache License, Version 2.0, January 2004,
  # http://www.apache.org/licenses/
  # (c) Copyright IBM Corp. 2015, 2016, 2017, 2018
  # --------------------------------------------------------------------------
  
  """
  Configuration of the CP Optimizer Python API
  
  This module is the top-level handler of the configuration parameters for
  the CP Optimizer Python API. It contains the default values of the different
  configuration parameters.
  
  It should NOT be changed directly.
  The preferable way is to add at least one of the following files that contain the changes
  to be performed:
  
   * *cpo_config.py*, a local set of changes on these parameters,
   * *cpo_config_<hostname>.py*, a hostname dependent set of changes.
  
  Each of these files is searched first in the directory where the Pyton main file is located,
  then in the current directory, and finally in the *PYTHONPATH*. The first one that is found is read.
  Final set of parameters is obtained by reading first this module, and then those listed above.
  
  If called as main, this module prints the actual configuration on standard output, including
  all customizations made using the mechanism described above.
  
  Following sections describe the most important parameters that can be easily modified to customize
  the behavior of the Python API.
  All available parameters are available by consulting the source code of this module.
  
  General parameters
  ------------------
  
  *context.log_output = sys.stdout*
  
      This parameter contains the default log stream.
      By default it is set to the standard output.
      A value of *None* can be used to disable all logs.
  
  *context.verbose = 0*
  
      This parameter controls the verbosity level of the log, between 0 and 9, if *log_output* is not None.
      The default value of 0 means no log.
  
  *context.model.add_source_location = True*
  
      This parameter indicates that when the model is transformed into CPO format, additional information is added
      to correlate expressions with the Python file and line where it has been generated.
      If any error is raised by the solver during the solve, this information is provided in the
      error description, which allows for easier debugging.
  
  *context.model.length_for_alias = None*
  
      This parameter allows to associate a shorter alias to variables whose name is longer than the given length.
      In the CPO representation of the model, variable is declared with its original name and an alias is created
      to use it with a shorter name in model expressions, allowing to reduce the size of the generated CPO format.
  
      In the returned solution, variable can be still retrieved with their original names.
  
      By default, the value is None, which indicates to always keep original variable names.
  
  *context.model.name_all_constraints = False*
  
      This parameter enables the naming of all constraints when the model is generated in CPO format.
      It is mandatory only if the *refine conflict* function is called.
      Anyway, if the *refine conflict* function is called, and if the CPO format of the model has already been generated,
      it is generated again with this option set in order to allow proper completion of the request.
      Setting it to *True* is preferable only if *refine conflict* function is called on a big model.
  
  *context.model.factorize_expressions = True*
  
     This parameter indicates to factorize common expressions when generating the model in CPO file format.
  
  *context.model.dump_directory = None*
  
      This parameter gives the name of a directory where the CPO files that are generated for solving models are stored
      for logging purpose.
  
      If not None, the directory is created and generated models are stored in files named `<model_name>.cpo`.
  
  *context.model.sort_names = None*
  
      This parameter precise how the variables are sorted when declared in the CPO file.
  
      The value can be None for no sort, 'alphabetical' to sort in alphabetical order, or 'natural' to sort in natural order
      (meaning for example that X11 will be declared after X2, which is not the case in alphabetical order).
  
  *context.model.cache.size = 10000*
  
      This parameter gives the maximum capacity of the internal cache used to speed-up conversion of Python expressions
      into CPO expressions.
  
  *context.model.cache.active = True*
  
      This parameter allows to enable or disable the expression cache mechanism.
      Value os a boolean (True or False). Default value is True.
  
  *context.params.xxx*
  
      The parameter `context.params` is an instance of the class
      :class:`~docplex.cp.parameters.CpoParameters` (in :doc:`parameters.py</docplex.cp.parameters.py>`)
      which describes all of the public solver parameters as properties.
  
  
  Configuration of the model solving
  ----------------------------------
  
  *context.solver.trace_log = False*
  
      This parameter indicates to trace the log generated by the solver when solving the CPO model.
      The log is printed on the `context.log_output` stream, if given.
  
      The default value of this parameter is True for a local solve, but is set to False if remote solve or if the
      Python interpreter is considered as running in a notebook (if module `ipykernel` is detected in system modules).
  
  *context.solver.trace_cpo = False*
  
      This parameter indicates to trace the CPO model that is generated before submitting it for solving.
      The model is printed on the `context.log_output stream`, if given.
  
  *context.solver.enable_undocumented_params = False*
  
      This parameter allows to enable the possibility to set solving parameters that are not in the public parameters
      detailed in the class
      :class:`~docplex.cp.parameters.CpoParameters` (in :doc:`parameters.py</docplex.cp.parameters.py>`).
  
  *context.solver.add_log_to_solution = True*
  
      This parameter indicates to add the solver log content to the solution object.
      By default, this parameter is True but it can be set to False if the log is very big or of no interest.
  
  *context.solver.add_conflict_as_cpo = True*
  
      This parameter indicates to include the conflict in CPO format in the conflict refiner result
      By default, this parameter is True.
  
  *context.solver.agent = 'local'*
  
      This parameter specifies the name of the solver agent that is used to solve the model.
      The value of this parameter is the name of a child context of `context.solver`, which contains necessary attributes
      that allow to create and run the required agent.
  
  *context.solver.log_prefix = "[Solver] "*
  
      Prefix that is added to every message that is logged by the solver component.
  
  
  Configuration of the `local` solving agent
  ------------------------------------------
  
  *context.solver.local.execfile*
  
      Name or full path of the CP Optimizer Interactive executable file.
      By default, it is set to *"cpoptimizer(.exe)"*, and is searched in the file system using the strategy described below.
  
  
  Configuration of the `lib` solving agent
  ----------------------------------------
  
  *context.solver.lib.libfile*
  
      Name or full path of the CP Optimizer library file.
      By default, it is set to *"lib_cpo_solver_\*(.dll)"*, and is searched in the file system using the strategy described below.
  
  
  Search for executable or library files
  --------------------------------------
  
  If an executable or library file is not given with its full path, it is searched in the file system in a list of
  directories that is built in this order:
  
     * the directory where the main Python file is located,
     * the current directory,
     * all directories of the environment variables 'LD_LIBRARY_PATH' or 'DYLD_LIBRARY_PATH' if defined,
     * all directories of the environment variable 'PATH'.
  
  If the file name contains an "\*", and if there are multiple files matching the pattern, the most recent is selected.
  
  
  Configuration for best performances
  -----------------------------------
  
  To configure the CP Python API for best performances, the following configuration settings may be used.
  Obviously, this performance is won at the cost of the loss of some features that may be useful in other cases.
  ::
  
      context.verbose = 0
      context.model.add_source_location = False
      context.model.length_for_alias = 10
      context.model.name_all_constraints = False
      context.model.dump_directory = None
      context.model.sort_names = None
      context.solver.trace_cpo = False
      context.solver.trace_log = False
      context.solver.add_log_to_solution = False
  
  Detailed description
  --------------------
  """
  
  from docplex.cp.utils import *
  from docplex.cp.parameters import CpoParameters, ALL_PARAMETER_NAMES
  
  import sys
  import socket
  import os
  import traceback
  import platform
  import fnmatch
  
  # Check if running in a worker environment
  try:
      import docplex.util.environment as runenv
      IS_IN_WORKER = isinstance(runenv.get_environment(), runenv.WorkerEnvironment)
  except:
      IS_IN_WORKER = False
  
  # CP Optimizer Interactive executable name
  CPO_EXEC_INTERACTIVE = "cpoptimizer" + (".exe" if IS_WINDOWS else "")
  
  # CP Optimizer Interactive executable name
  CPO_LIBRARY = "lib_cpo_solver_*" + (".dll" if IS_WINDOWS else ".so")
  
  # Environment variable for context changes
  CONTEXT_ENVIRONMENT = "DOCPLEX_CP_CONTEXT"
  
  
  #=============================================================================
  # Definition of the default context
  #=============================================================================
  
  #-----------------------------------------------------------------------------
  # Global context
  
  # Create default context infrastructure
  context = Context()
  
  # Default log output
  context.log_output = sys.stdout
  
  # Default log verbosity
  context.verbose = 0
  
  # Visu enable indicator (internal, can be disabled for testing purpose)
  context.visu_enabled = True
  
  # Indicator to log catched exceptions
  context.log_exceptions = False
  
  
  #-----------------------------------------------------------------------------
  # CPLEX Optimization Studio context
  
  context.cos = Context()
  
  # Location of CPLEX Optimization Studio, used to search for executables if needed.
  # Default value is None, meaning that configuration searches for the most recent
  # CPLEX_STUDIO_DIRxxx environment variable
  context.cos.location = None
  
  
  #-----------------------------------------------------------------------------
  # Modeling context
  
  context.model = Context()
  
  # Indicate to add source location in model
  context.model.add_source_location = True
  
  # Minimal variable name length that trigger use of shorter alias. None for no alias.
  context.model.length_for_alias = None
  
  # Automatically add a name to every top-level constraint
  context.model.name_all_constraints = False
  
  # Model format generation version that is used by default if no format version is given in the model.
  # If None, latest format is used without specifying it explicitly.
  context.model.version = None
  
  # Name of the directory where store copy of the generated CPO files. None for no dump.
  context.model.dump_directory = None
  
  # Flag to indicate to the compiler to factorize expressions used more than ones
  context.model.factorize_expressions = True
  
  # Flag to generate short model output (internal)
  context.model.short_output = False
  
  # Type of sort for model variables. Value is in {None, 'alphabetical', 'natural')
  context.model.sort_names = None
  
  # Expression cache, global to all models
  context.model.cache = Context()
  context.model.cache.size = 10000
  context.model.cache.active = True
  
  
  #-----------------------------------------------------------------------------
  # Parsing context
  
  context.parser = Context()
  
  # Enable CPO parser to automatically generate blackboxes for unknown functions
  context.parser.auto_blackbox = False
  
  # Indicate to print warning messages (for example for unknown blackbox functions) at the end of parsing
  context.parser.print_warnings = True
  
  # Indicate to check format version and raise an exception if out of range
  context.parser.check_format_version = True
  
  # Indicate to FZN parser to reduce model when possible
  context.parser.fzn_reduce = False
  
  
  #-----------------------------------------------------------------------------
  # Solving parameters
  
  context.params = CpoParameters()
  
  # Default time limit in seconds (None for no limit)
  context.params.TimeLimit = None
  
  # Workers count (None for number of cores)
  context.params.Workers = None
  
  
  #-----------------------------------------------------------------------------
  # Solving context
  
  context.solver = Context()
  
  # Indicate to trace CPO model before solving
  context.solver.trace_cpo = False
  
  # Indicate to trace solver log on log_output.
  context.solver.trace_log = True
  if is_in_notebook():
      # In notebooks, no traces by default because it may crash the environment
      context.solver.trace_log = False
  
  # Enable undocumented parameters
  context.solver.enable_undocumented_params = False
  
  # Max number of threads allowed for model solving
  context.solver.max_threads = None
  
  # Indicate to add solver log to the solution
  context.solver.add_log_to_solution = True
  
  # Indicate to add the conflict in CPO format to conflict refiner result
  context.solver.add_conflict_as_cpo = True
  
  # Indicate to replace simple solve by a search_next loop
  context.solver.solve_with_search_next = False
  
  # Log prefix
  context.solver.log_prefix = "[Solver] "
  
  # Name of the agent to be used for solving. Value is name of one of this context child context.
  context.solver.agent = 'local'
  
  # Auto-publish parameters
  context.solver.auto_publish = Context()
  
  # Indicate to auto-publish solve details in environment
  context.solver.auto_publish.solve_details = True
  
  # Indicate to auto-publish results in environment
  context.solver.auto_publish.result_output = "solution.json"
  
  # Indicate to auto-publish kpis in environment
  context.solver.auto_publish.kpis_output = "kpis.csv"
  
  # For KPIs output, name of the kpi name column
  context.solver.auto_publish.kpis_output_field_name = "Name"
  
  # For KPIs output, name of the kpi value column
  context.solver.auto_publish.kpis_output_field_value = "Value"
  
  # Indicate to auto-publish conflicts in environment
  context.solver.auto_publish.conflicts_output = "conflicts.csv"
  
  # Indicate to enable auto-publish also with local environment
  context.solver.auto_publish.local_publish = False
  
  # Default solver listeners
  context.solver.listeners = ["docplex.cp.solver.environment_client.EnvSolverListener"]
  
  
  #-----------------------------------------------------------------------------
  # Local solving using CP Interactive executable
  
  context.solver.local = Context()
  
  # Python class implementing the agent
  context.solver.local.class_name = "docplex.cp.solver.solver_local.CpoSolverLocal"
  
  # Name or path of the CP Optimizer Interactive program
  context.solver.local.execfile = CPO_EXEC_INTERACTIVE
  
  # Parameters of the exec file (mandatory, do not change)
  context.solver.local.parameters = ['-angel']
  
  # Agent log prefix
  context.solver.local.log_prefix = "[Local] "
  
  # Local sub-process start timeout in seconds
  context.solver.local.process_start_timeout = 5
  
  
  #-----------------------------------------------------------------------------
  # Local solving with CPO library (internal)
  
  context.solver.lib = Context()
  
  # Python class implementing the agent
  context.solver.lib.class_name = "docplex.cp.solver.solver_lib.CpoSolverLib"
  
  # Name or path of the CPO library
  context.solver.lib.libfile = CPO_LIBRARY
  
  # Agent log prefix
  context.solver.lib.log_prefix = "[PyLib] "
  
  # Check if library has been installed with workers specific package
  try:
      from docplex_cpo_solver import get_library_path
      lfile = get_library_path()
      if lfile:
          # Force solver to use lib by default
          context.solver.lib.libfile = lfile
          context.solver.agent = 'lib'
  except:
      pass
  
  
  #-----------------------------------------------------------------------------
  # Solver simulators
  
  # Solver simulator that always fails
  context.solver.simulatorfail = Context()
  context.solver.simulatorfail.class_name = "docplex.cp.solver.solver_simulator.CpoSolverSimulatorFail"
  context.solver.simulatorfail.log_prefix = "[SimulatorFail] "
  
  # Solver simulator generating a random solution
  context.solver.simulatorrandom = Context()
  context.solver.class_name = "docplex.cp.solver.solver_simulator.CpoSolverSimulatorRandom"
  context.solver.log_prefix = "[SimulatorRandom] "
  
  
  #-----------------------------------------------------------------------------
  # Parameters for solver interactive monitor
  
  context.interactive = Context()
  
  # Confirm solver monitor window closing
  context.interactive.user_preferences_file = "DocplexCpUserPreferences.prf"
  
  # Confirm solver monitor window closing
  context.interactive.window_confirm_exit = True
  
  # Abort solve when closing window
  context.interactive.solver_abort_on_exit = True
  
  # Daemon display thread (avoid tk exception if listener)
  context.interactive.daemon_thread = True
  
  
  #-----------------------------------------------------------------------------
  # Apply special changes if running in a worker
  
  if IS_IN_WORKER:
      context.solver.max_threads = runenv.get_environment().get_available_core_count()
  
  
  #-----------------------------------------------------------------------------
  # Create context variable
  
  # Create a local context variable
  # Now useless, comes from a time where there was LOCAL_CONTEXT and DOCLOUD_CONTEXT.
  # Kept for ascending compatibility
  LOCAL_CONTEXT = context
  
  
  #=============================================================================
  # Public functions
  #=============================================================================
  
  def get_default():
      """ Get the default context
  
      Default context is also accessible with the global variable 'context' in this module.
  
      Returns:
          Current default context
      """
      return context
  
  
  def set_default(ctx):
      """ Set the default context.
  
      Default context becomes accessible in the global variable 'context' in this module.
  
      Args:
          ctx: New default context
      """
      if ctx is None:
          ctx = Context()
      else:
          assert isinstance(ctx, Context), "Context object must be of class Context"
      sys.modules[__name__].context = ctx
  
  
  #=============================================================================
  # Private functions
  #=============================================================================
  
  def _get_port_name():
      """ Get the COS port name for the calling environment
  
      Returns:
          COS port name, None if not found
      """
      sstm = platform.system()
      if sstm == 'Windows': return 'x64_win64'
      if sstm == 'Darwin':  return 'x86-64osx'
      if sstm == 'Linux':
          machine = platform.machine()
          if machine == 'x86_64':
              machine = 'x86-64'
          return machine + '_linux'
      return None
  
  
  # List of system properties identifying candidate COS root directories
  _COS_ROOT_DIRS_ENV_VARS = ("CPLEX_STUDIO_DIR201", "CPLEX_STUDIO_DIR1210", "CPLEX_STUDIO_DIR129", "CPLEX_STUDIO_DIR128")
  
  
  def _build_search_path (ctx):
      """ Build the path where search for executables
  
      Args:
          ctx:  Context to get information from
      """
      # Initialize search path
      path = []
  
      # Set it to COS location if provided
      cosloc = ctx.get_by_path('cos.location')
      if cosloc:
          # Check existence of the directory
          if not os.path.isdir(cosloc):
              raise Exception("COS location directory '{}' does not exists.".format(cosloc))
          # As explicitly defined, add it in front of search path
          path.append(cosloc + "/bin/" + _get_port_name())
  
      # Add directory where python main is located
      mpfile = get_main_file()
      if mpfile:
          path.append(os.path.dirname(mpfile))
  
      # Add current directory
      path.append('.')
  
      # Add all system path
      path.extend(get_system_path())
  
      # Append docplex install directory
      python_home = os.path.dirname(os.path.abspath(sys.executable))
      if IS_WINDOWS:
          path.append(os.path.join(python_home, "Scripts"))
          appdata = os.environ.get('APPDATA')
          if appdata is not None:
              path.append(os.path.join(appdata, os.path.join('Python', 'Scripts')))
      else:
          path.append("~/.local/bin")
          path.append(os.path.join(python_home, "bin"))
  
      # Add all COS locations if not already forced
      if not cosloc:
          # Add all existing installed COS starting from the most recent
          for cnv in _COS_ROOT_DIRS_ENV_VARS:
              cdir = os.getenv(cnv)
              if cdir:
                  path.append(cdir + "/cpoptimizer/bin/" + _get_port_name())
  
      return path
  
  
  def _search_exec_file(file, ctx):
      """ Search the first occurrence of an executable.
  
      Args:
          file:  Executable file name
          ctx:   Context to get information from
      Returns:
          Full path of the first executable file found, given file if not found
      """
      # Check null
      if not file:
         return None
      # Check if given file is directly executable
      if is_exe_file(file):
          return file
      # Check if file contains a path
      fname = os.path.basename(file)
      if fname == file:
          # Build full search path
          path = _build_search_path(ctx)
      else:
          # Keep file path as single path
          path = [os.path.dirname(file)]
      # Check if file contains a pattern
      if "*" in fname:
          # Check in the path
          for d in path:
              if not os.path.isdir(d):
                  continue
              lf = [os.path.join(d, f) for f in os.listdir(d) if fnmatch.fnmatch(f, fname)]
              if lf:
                  # Take most recent executable file
                  lf = [f for f in lf if is_exe_file(f)]
                  if lf:
                      # Skip "ai" files if any
                      nlf = [f for f in lf if not os.path.splitext(f)[0].endswith("ai")]
                      if nlf:
                          lf = nlf
                      lf.sort(key=lambda x: os.path.getmtime(x))
                      return lf[-1]
      else:
          # Check directly
          for d in path:
              if not os.path.isdir(d):
                  continue
              nf = os.path.join(d, fname)
              if is_exe_file(nf):
                  return nf
  
      return file
  
  
  # Attribute values denoting a default value
  DEFAULT_VALUES = ("ENTER YOUR KEY HERE", "ENTER YOUR URL HERE", "default")
  
  def _is_defined(arg, kwargs):
      return (arg in kwargs) and kwargs[arg] and (kwargs[arg] not in DEFAULT_VALUES)
  
  def _change_context_attribute(ctx, key, value):
      rp = ctx.search_and_replace_attribute(key, value)
      # If not found, set in solving parameters
      if rp is None:
          params = ctx.params
          if not isinstance(params, CpoParameters):
              raise CpoException("Invalid configuration attribute '{}' (no 'params' section where put it)".format(key))
          if key in ALL_PARAMETER_NAMES or ctx.solver.enable_undocumented_params:
              setattr(params, key, value)
          else:
              raise CpoException("CPO solver does not accept a parameter named '{}'".format(key))
  
  def _get_effective_context(**kwargs):
      """ Build a effective context from a variable list of arguments that may specify changes to default.
  
      Args:
          context (optional):   Source context, if not default.
          params (optional):    Solving parameters (CpoParameters) that overwrite those in the solving context
          (others) (optional):  All other context parameters that can be changed.
      Returns:
          Updated (cloned) context
      """
      # Determine source context
      ctx = kwargs.get('context')
      if (ctx is None) or (ctx in DEFAULT_VALUES):
          ctx = context
      ctx = ctx.clone()
      # print("\n*** Source context");
      # ctx.write()
  
      # First set parameters if given
      prms = kwargs.get('params')
      if prms is not None:
          ctx.params.set_other(prms)
  
      # Process other changes, check first if undocumented params are enabled (or not)
      uk = 'enable_undocumented_params'
      if uk in kwargs:
          _change_context_attribute(ctx, uk, kwargs[uk])
      for k, v in kwargs.items():
          # Replace obsolete solve_with_start_next attribute
          if k == 'solve_with_start_next':
              k = 'solve_with_search_next'
          if (k != 'context') and (k != 'params') and (k != uk) and (v not in DEFAULT_VALUES):
              _change_context_attribute(ctx, k, v)
  
      # Get solver execfile
      try:
          if ctx.solver.agent == 'local':
              ctx.solver.local.execfile = _search_exec_file(ctx.solver.local.execfile, ctx)
          elif ctx.solver.agent == 'lib':
              ctx.solver.lib.libfile = _search_exec_file(ctx.solver.lib.libfile, ctx)
      except:
          pass
  
      # Return
      return ctx
  
  
  #=============================================================================
  # Overload default context with optional customizations
  #=============================================================================
  
  # Compute config files search path
  _CONFIG_SEARCH_PATH = []
  mpfile = get_main_file()     # Add directory of the main file if any
  if mpfile:
      _CONFIG_SEARCH_PATH.append(os.path.dirname(mpfile))
  _CONFIG_SEARCH_PATH.append('.')       # Add current directory
  _CONFIG_SEARCH_PATH.extend(sys.path)  # Add all PYTHONPATH
  
  
  def _eval_file(file):
      """ If found in python path, evaluate the content of a python module in this module.
      Args:
          file: Python file to evaluate
      """
      for dir in _CONFIG_SEARCH_PATH:
          f = dir + "/" + file if dir else file
          if os.path.isfile(f):
              # print("Loading configuration file '{}'".format(f))
              try:
                  with open(f) as fin:
                      fcont = fin.read()
                  exec(fcont)
                  return
              except Exception as e:
                  if context.log_exceptions:
                      traceback.print_exc()
                  raise Exception("Error while loading config file {}: {}".format(f, str(e)))
  
  # Load all config changes
  for f in ("cpo_config.py", "cpo_config_" + socket.gethostname() + ".py",):
      _eval_file(f)
  
  # Check particular case of renamed solve_with_start_next
  ov = context.solver.solve_with_start_next
  if ov is not None:
      context.solver.solve_with_search_next = ov
  
  # Overwrite with environment definitions if any
  envchg = os.environ.get(CONTEXT_ENVIRONMENT)
  if envchg:
      for chg in envchg.split(';'):
          cx = chg.find('=')
          if cx < 0:
              raise Exception("Invalid context change in environment variable {}: '{}'".format(CONTEXT_ENVIRONMENT, chg))
          try:
              context.set_by_path(chg[:cx].strip(), chg[cx+1:].strip())
          except:
              raise Exception("Error when applying change from environment variable {}: '{}'".format(CONTEXT_ENVIRONMENT, chg))
  
  
  #=============================================================================
  # Print configuration when called as main
  #=============================================================================
  
  if __name__ == "__main__":
      # Instanciate executable files if needed
      ctx = _get_effective_context()
      ctx.write()
  