def _get_lib_handler(self):
        """ Access the CPO library
        Returns:
            Library handler, with function prototypes defined.
        Raises:
            CpoSolverException if library is not found
        """
        # Access library
        libf = self.context.libfile
        if not libf:
            raise CpoSolverException(
                "CPO library file should be given in 'solver.lib.libfile' context attribute."
            )

        # Load library
        lib = self._load_lib(libf)

        # Define function prototypes
        self.absent_funs = set()
        for name, proto in _LIB_FUNCTION_PROTYTYPES.items():
            try:
                f = getattr(lib, name)
                f.restype = proto[0]
                f.argtypes = proto[1]
            except:
                if not name in _LIB_FUNCTION_OPTIONAL:
                    raise CpoSolverException(
                        "Function '{}' not found in the library {}".format(
                            name, lib))
                else:
                    self.absent_funs.add(name)

        # Return
        return lib
    def _call_lib_function(self, dfname, json, *args):
        """ Call a library function
        Args:
            dfname:  Name of the function to be called
            json:    Indicate if a JSON result is expected
            *args:   Optional arguments (after session)
        Raises:
            CpoDllException if function call fails or if expected JSON is absent
        """
        # Reset JSON result if JSON required
        if json:
            self.last_json_result = None

        # Call library function
        try:
            rc = getattr(self.lib_handler, dfname)(self.session, *args)
        except:
            raise CpoNotSupportedException(
                "The function '{}' is not found in the library. Try with a most recent version."
                .format(dfname))
        if rc != 0:
            errmsg = "Call to '{}' failure (rc={})".format(dfname, rc)
            if self.first_error_line:
                errmsg += ": {}".format(self.first_error_line)
            raise CpoSolverException(errmsg)

        # Check if JSON result is present
        if json and self.last_json_result is None:
            raise CpoSolverException(
                "No JSON result provided by function '{}'".format(dfname))
    def _wait_event(self, xevt):
        """ Wait for a particular event while forwarding logs if any.
        Args:
            xevt: Expected event
        Returns:
            Message data
        Raises:
            SolverException if an error occurs
        """
        # Initialize first error string to enrich exception if any
        firsterror = None

        # Read events
        while True:
            # Read and process next message
            evt, data = self._read_message()

            if evt == xevt:
                return data

            elif evt in (EVT_SOLVER_OUT_STREAM, EVT_SOLVER_WARN_STREAM):
                if data:
                    # Store log if required
                    if self.log_enabled:
                        self._add_log_data(data)

            elif evt == EVT_SOLVER_ERR_STREAM:
                if data:
                    if firsterror is None:
                        firsterror = data.replace('\n', '')
                    out = self.log_output if self.log_output is not None else sys.stdout
                    out.write("ERROR: {}\n".format(data))
                    out.flush()

            elif evt == EVT_TRACE:
                self.context.log(4, "ANGEL TRACE: " + data)

            elif evt == EVT_ERROR:
                if firsterror is not None:
                    data += " (" + firsterror + ")"
                self.end()
                raise CpoSolverException("Solver error: " + data)

            elif evt == EVT_CALLBACK_EVENT:
                event = data
                # Read data
                evt, data = self._read_message()
                assert evt == EVT_CALLBACK_DATA
                res = self._create_result_object(CpoSolveResult, data)
                self.solver._notify_callback_event(event, res)

            else:
                self.end()
                raise CpoSolverException(
                    "Unknown event received from local solver: " + str(evt))
 def _add_callback_processing(self):
     """ Add the processing of solver callback.
     """
     # Check angel version
     aver = self.version_info.get('AngelVersion', 0)
     if aver < 8:
         raise CpoSolverException(
             "This version of the CPO solver angel ({}) does not support solver callbacks."
             .format(aver))
     self._write_message(CMD_ADD_CALLBACK)
     self._wait_event(EVT_SUCCESS)
    def _read_frame(self, nbb):
        """ Read a byte frame from input stream
        Args:
            nbb:  Number of bytes to read
        Returns:
            Byte array
        """
        # Read data
        data = self.pin.read(nbb)
        if len(data) != nbb:
            if len(data) == 0:
                # Check if first read of data
                if self.process_infos.get(
                        CpoProcessInfos.TOTAL_DATA_RECEIVE_SIZE, 0) == 0:
                    if IS_WINDOWS:
                        raise CpoSolverException(
                            "Nothing to read from local solver process. Possibly not started because cplex dll is not accessible."
                        )
                    else:
                        raise CpoSolverException(
                            "Nothing to read from local solver process. Check its availability."
                        )
                else:
                    try:
                        self.process.wait()
                        rc = self.process.returncode
                    except:
                        rc = "unknown"
                    raise CpoSolverException(
                        "Nothing to read from local solver process. Process seems to have been stopped (rc={})."
                        .format(rc))
            else:
                raise CpoSolverException(
                    "Read only {} bytes when {} was expected.".format(
                        len(data), nbb))

        # Return
        if IS_PYTHON_2:
            data = bytearray(data)
        return data
 def _load_lib(self, libf):
     """ Attempt to load a particular library
     Returns:
         Library handler
     Raises:
         CpoSolverException or other Exception if library is not found
     """
     # Search for library file
     if not os.path.isfile(libf):
         lf = find_library(libf)
         if lf is None:
             raise CpoSolverException(
                 "Can not find library '{}'".format(libf))
         libf = lf
     # Check library is executable
     if not is_exe_file(libf):
         raise CpoSolverException(
             "Library file '{}' is not executable".format(libf))
     # Load library
     try:
         return ctypes.CDLL(libf)
     except Exception as e:
         raise CpoSolverException("Can not load library '{}': {}".format(
             libf, e))
    def _write_message(self, cid, data=None):
        """ Write a message to the solver process
        Args:
            cid:   Command name
            data:  Data to write, already encoded in UTF8 if required
        """
        # Encode elements
        stime = time.time()
        cid = cid.encode('utf-8')
        if is_string(data):
            data = data.encode('utf-8')
        nstime = time.time()
        self.process_infos.incr(CpoProcessInfos.TOTAL_UTF8_ENCODE_TIME,
                                nstime - stime)

        # Build header
        tlen = len(cid)
        if data is not None:
            tlen += len(data) + 1
        if tlen > 0xffffffff:
            raise CpoSolverException(
                "Try to send a message with length {}, greater than {}.".
                format(tlen, 0xffffffff))
        frame = bytearray(6)
        frame[0] = 0xCA
        frame[1] = 0xFE
        encode_integer_big_endian_4(tlen, frame, 2)

        # Log message to send
        self.context.log(5, "Send message: cmd=", cid, ", tsize=", tlen)

        # Add data if any
        if data is None:
            frame = frame + cid
        else:
            frame = frame + cid + bytearray(1) + data

        # Write message frame
        with self.out_lock:
            self.pout.write(frame)
            self.pout.flush()

        # Update statistics
        self.process_infos.incr(CpoProcessInfos.TOTAL_DATA_SEND_TIME,
                                time.time() - nstime)
        self.process_infos.incr(CpoProcessInfos.TOTAL_DATA_SEND_SIZE,
                                len(frame))
    def _read_message(self):
        """ Read a message from the solver process
        Returns:
            Tuple (evt, data)
        """
        # Read message header
        frame = self._read_frame(6)
        if (frame[0] != 0xCA) or (frame[1] != 0xFE):
            erline = frame + self._read_error_message()
            erline = erline.decode()
            self.end()
            raise CpoSolverException(
                "Invalid message header. Possible error generated by solver: "
                + erline)

        # Read message data
        tsize = decode_integer_big_endian_4(frame, 2)
        data = self._read_frame(tsize)

        # Split name and data
        ename = 0
        while (ename < tsize) and (data[ename] != 0):
            ename += 1

        # Decode name and data
        stime = time.time()
        if ename == tsize:
            # Command only, no data
            evt = data.decode('utf-8')
            data = None
        else:
            # Split command and data
            evt = data[0:ename].decode('utf-8')
            data = data[ename + 1:].decode('utf-8')

        # Update statistics
        self.process_infos.incr(CpoProcessInfos.TOTAL_UTF8_DECODE_TIME,
                                time.time() - stime)
        self.process_infos.incr(CpoProcessInfos.TOTAL_DATA_RECEIVE_SIZE,
                                tsize + 6)

        # Log received message
        self.context.log(5, "Read message: ", evt, ", data: '", data, "'")

        return evt, data
Example #9
0
    def __init__(self, solver, params, context):
        """ Create a new solver using shared library.

        Args:
            solver:   Parent solver
            params:   Solving parameters
            context:  Solver agent context
        Raises:
            CpoSolverException if library is not found
        """
        # Call super
        super(CpoSolverLib, self).__init__(solver, params, context)

        # Initialize attributes
        self.first_error_line = None
        self.lib_handler = None  # (to not block end() in case of init failure)
        self.last_conflict_cpo = None

        # Connect to library
        self.lib_handler = self._get_lib_handler()
        self.context.log(2, "Solving library: '{}'".format(self.context.lib))

        # Create session
        # CAUTION: storing callback prototype is mandatory. Otherwise, it is garbaged and the callback fails.
        self.notify_event_proto = _EVENT_NOTIF_PROTOTYPE(self._notify_event)
        self.session = self.lib_handler.createSession(self.notify_event_proto)
        self.context.log(5, "Solve session: {}".format(self.session))

        # Transfer infos in process info
        for x in ('ProxyVersion', 'LibVersion', 'SourceDate', 'SolverVersion'):
            self.process_infos[x] = self.version_info.get(x)

        # Check solver version if any
        sver = self.version_info.get('SolverVersion')
        mver = solver.get_model_format_version()
        if sver and mver and compare_natural(mver, sver) > 0:
            raise CpoSolverException(
                "Solver version {} is lower than model format version {}.".
                format(sver, mver))

        # Initialize settings indicators
        self.callback_proto = None
    def __init__(self, solver, params, context):
        """ Create a new solver that solves locally with CP Optimizer Interactive.

        Args:
            solver:  Parent solver
            params:  Solving parameters
            context: Solver context
        Raises:
            CpoException if proxy executable does not exists
        """
        # Call super
        self.process = None
        self.active = True
        self.timeout_kill = False
        super(CpoSolverLocal, self).__init__(solver, params, context)

        # Check if executable file exists
        xfile = context.execfile
        if xfile is None or not is_string(xfile):
            raise CpoException(
                "Executable file should be given in 'execfile' context attribute."
            )
        if not os.path.isfile(xfile):
            raise CpoException(
                "Executable file '{}' does not exists".format(xfile))
        if not is_exe_file(xfile):
            raise CpoException(
                "Executable file '{}' is not executable".format(xfile))

        # Create solving process
        cmd = [context.execfile]
        if context.parameters is not None:
            cmd.extend(context.parameters)
        context.log(2, "Solver exec command: '", ' '.join(cmd), "'")
        try:
            self.process = subprocess.Popen(cmd,
                                            stdin=subprocess.PIPE,
                                            stderr=subprocess.STDOUT,
                                            stdout=subprocess.PIPE,
                                            universal_newlines=False)
        except:
            raise CpoException(
                "Can not execute command '{}'. Please check availability of required executable file."
                .format(' '.join(cmd)))
        self.pout = self.process.stdin
        self.pin = self.process.stdout
        self.out_lock = threading.Lock()

        # Read initial version info from process
        self.version_info = None
        timeout = context.process_start_timeout
        timer = threading.Timer(timeout, self._process_start_timeout)
        timer.start()
        try:
            evt, data = self._read_message()
        except Exception as e:
            if self.timeout_kill:
                raise CpoSolverException(
                    "Solver process was too long to start and respond ({} seconds). Process has been killed."
                    .format(timeout))
            raise CpoSolverException(
                "Solver sub-process start failure: {}".format(e))
        timer.cancel()

        # Check received message
        if evt != EVT_VERSION_INFO:
            raise CpoSolverException(
                "Unexpected event {} received instead of version info event {}."
                .format(evt, EVT_VERSION_INFO))
        self.version_info = verinf = json.loads(data)
        self.available_commands = self.version_info['AvailableCommands']
        # Normalize information
        verinf['AgentModule'] = __name__

        context.log(3, "Local solver info: '", verinf, "'")

        # Transfer infos in process info
        for x in ('ProxyVersion', 'AngelVersion', 'SourceDate',
                  'SolverVersion'):
            self.process_infos[x] = self.version_info.get(x)

        # Check solver version if any
        sver = self.version_info.get('SolverVersion')
        mver = solver.get_model_format_version()
        if sver and mver and compare_natural(mver, sver) > 0:
            raise CpoSolverException(
                "Solver version {} is lower than model format version {}.".
                format(sver, mver))