7.2 Asynchronous Problem Submission¶
This tutorial demonstrates most features of the OptServer API, that is submitting a problem, polling for solution, retrieving the solution, breaking the solver and retrieving the log output.
For the purpose of the tutorial we assume that the problem to be solved is read from a file, and the solutions will be saved to a file, i.e. we don’t go into the logic which sets up the problem and interprets the solution. See file formats for specifications of file formats.
Starting the solver
If using authentication, the access token will always be passed in the X-Mosek-Access-Token
header. Assuming that an HTTP/HTTPS connection to the OptServer was established, we first submit a problem using submit. We can provide a name for easier identification of the problem in the Web GUI. The file format is passed in the Content-Type
header.
# POST problem data
con.request('POST', '/api/submit?jobname=' + jobname,
probdata,
headers = dict(headers, **{"Content-Type": "application/x-mosek-{}".format(intype)}))
resp = con.getresponse()
check_status(resp)
# Recover a token identifying the job
token = resp.read().decode('ascii')
The response contains a token used to identify the job in future requests. Note that this operation is identical to the synchronuous case. If no errors have occurred, we use solve-background to initiate solving the problem identified by the token:
con.request("GET","/api/solve-background?token=" + token, headers = headers)
resp = con.getresponse()
The calling program regains control immediately.
Waiting for and retrieving the solution
We can now periodically start polling for the solution via solution. We set the Accept
header to indicate expected solution format. If the response is empty then the solution is not yet available:
pollCount += 1
print("GET /api/solution")
con.request("GET", "/api/solution?token=" + token,
headers = dict(headers, **{"Accept": outtype}))
resp = con.getresponse()
check_status(resp)
# Is the solution available?
if resp.status == http.client.NO_CONTENT:
print("Solution not available in poll %d, continuing" % pollCount)
time.sleep(1.0)
When the response becomes non-empty we can retrieve the solution:
elif resp.status == http.client.OK:
solved = True
res = resp.getheader('X-Mosek-Res-Code',None)
trm = resp.getheader('X-Mosek-Trm-Code',None)
print("\tMOSEK response: %s" % res)
print("\t trm resp: %s" % trm)
print("Solution (as plain text):")
print(resp.read().decode('ascii', errors = 'ignore'))
Stopping the solver
At some point we can decide that the optimization should be stopped. That can be done with break.
if not solved and pollCount >= maxPolls:
con = openConnection(host, port, useHttps)
print("GET /api/break")
con.request("GET","/api/break?token=" + token, headers = headers)
resp = con.getresponse()
check_status(resp)
con.close()
Note that the solver need not break immediately, in particular it can enter a few more loops of checking for solution. The MOSEK termination code in this case will be MSK_RES_TRM_USER_CALLBACK
.
Retrieving the log
The log output from the solver can be retrieved gradually in each polling loop. The caller needs to keep track of how much of the log was already read and provide it as an offset in a call to log.
con = openConnection(host, port, useHttps)
print("GET /api/log")
con.request("GET","/api/log?token={0}&offset={1}".format(token, logOffset), headers = headers)
resp = con.getresponse()
check_status(resp)
# Show the latest log entries
lastLog = resp.read().decode('ascii', errors = 'ignore')
print(lastLog)
# Update the log offset by the size received
logOffset += len(lastLog)
con.close()
Complete code
The full example is shown below.
import http.client
import sys, time
try:
import ssl
except:
pass
# A debug method which prints out the HTTP(S) response
# and exits in case of error
def check_status(resp):
print('\tHTTPResponse: %s / %s' % (resp.status,resp.reason))
for k,v in resp.getheaders():
print('\t%s: %s' % (k,v))
if resp.status not in [http.client.OK, http.client.NO_CONTENT]:
raise Exception('An error in connection')
# A helper method which opens a connection
def openConnection(host, port, useHttps):
if useHttps:
return http.client.HTTPSConnection(host, port, context=ssl._create_unverified_context())
else:
return http.client.HTTPConnection(host, port)
if __name__ == '__main__':
try:
# OptServer address
host, port = sys.argv[1], int(sys.argv[2])
# Protocol (HTTP/HTTPS)
useHttps = (sys.argv[3] == "HTTPS")
# Name of file with input data
probfile = sys.argv[4]
# Input and output file type
intype, outtype = sys.argv[5], sys.argv[6]
# Number of solution polls
maxPolls = int(sys.argv[7])
# Jobname (for demonstration)
jobname = sys.argv[8]
# Authentication token
headers = {}
if len(sys.argv) == 10:
headers = {"X-Mosek-Access-Token": sys.argv[9]}
except:
print("Usage : python3 test_async.py host port protocol probfile intype outtype maxPolls jobname [accestoken]")
print("Example: python3 test_async.py solve.mosek.com 38000 HTTPS lo1.mps mps text/plain 5 SimpleTask ...")
sys.exit(1)
token = ""
# Create a connection for problem submission
con = openConnection(host, port, useHttps)
try:
with open(probfile,'rb') as probdata:
## Submit job
print('POST /api/submit')
# POST problem data
con.request('POST', '/api/submit?jobname=' + jobname,
probdata,
headers = dict(headers, **{"Content-Type": "application/x-mosek-{}".format(intype)}))
resp = con.getresponse()
check_status(resp)
# Recover a token identifying the job
token = resp.read().decode('ascii')
## Start solving end close connection
print("GET /api/solve-background")
con.request("GET","/api/solve-background?token=" + token, headers = headers)
resp = con.getresponse()
check_status(resp)
finally:
con.close()
print("Submit connection closed")
# Begin waiting for the solution
solved = False
pollCount = 0
logOffset = 0
while not solved:
con = openConnection(host, port, useHttps)
pollCount += 1
print("GET /api/solution")
con.request("GET", "/api/solution?token=" + token,
headers = dict(headers, **{"Accept": outtype}))
resp = con.getresponse()
check_status(resp)
# Is the solution available?
if resp.status == http.client.NO_CONTENT:
print("Solution not available in poll %d, continuing" % pollCount)
time.sleep(1.0)
elif resp.status == http.client.OK:
solved = True
res = resp.getheader('X-Mosek-Res-Code',None)
trm = resp.getheader('X-Mosek-Trm-Code',None)
print("\tMOSEK response: %s" % res)
print("\t trm resp: %s" % trm)
print("Solution (as plain text):")
print(resp.read().decode('ascii', errors = 'ignore'))
con.close()
# After too many tries we indicate the solver to stop
if not solved and pollCount >= maxPolls:
con = openConnection(host, port, useHttps)
print("GET /api/break")
con.request("GET","/api/break?token=" + token, headers = headers)
resp = con.getresponse()
check_status(resp)
con.close()
# Demonstrate how to retrieve the last part of solver log
con = openConnection(host, port, useHttps)
print("GET /api/log")
con.request("GET","/api/log?token={0}&offset={1}".format(token, logOffset), headers = headers)
resp = con.getresponse()
check_status(resp)
# Show the latest log entries
lastLog = resp.read().decode('ascii', errors = 'ignore')
print(lastLog)
# Update the log offset by the size received
logOffset += len(lastLog)
con.close()