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
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))