Fix osdf code after upgrading to py38
[optf/osdf.git] / runtime / solvers / mzn / mzn_solver.py
1 # -------------------------------------------------------------------------
2 #   Copyright (c) 2020 AT&T Intellectual Property
3 #
4 #   Licensed under the Apache License, Version 2.0 (the "License");
5 #   you may not use this file except in compliance with the License.
6 #   You may obtain a copy of the License at
7 #
8 #       http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #   Unless required by applicable law or agreed to in writing, software
11 #   distributed under the License is distributed on an "AS IS" BASIS,
12 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 #   See the License for the specific language governing permissions and
14 #   limitations under the License.
15 #
16 # -------------------------------------------------------------------------
17 #
18
19 from datetime import datetime
20 import json
21
22 from pymzn import cbc
23 from pymzn import chuffed
24 from pymzn import gecode
25 from pymzn import minizinc
26 from pymzn import or_tools
27 from pymzn import Status
28
29 from osdf.utils.file_utils import delete_file_folder
30
31 error_status_map = {
32     Status.INCOMPLETE: "incomplete",
33     Status.COMPLETE: "complete",
34     Status.UNSATISFIABLE: "unsatisfiable",
35     Status.UNKNOWN: "unknown",
36     Status.UNBOUNDED: "unbounded",
37     Status.UNSATorUNBOUNDED: "unsat_or_unbounded",
38     Status.ERROR: "error"
39 }
40
41 solver_dict = {
42     'cbc': cbc,
43     'geocode': gecode,
44     'chuffed': chuffed,
45     'cp': chuffed,
46     'or_tools': or_tools
47 }
48
49
50 def map_status(status):
51     return error_status_map.get(status, "failed")
52
53
54 def solve(request_json, mzn_content):
55     """Given the request and minizinc content.  Translates the json request to the format minizinc understands
56
57     return: returns the optimized solution.
58     """
59     req_info = request_json['requestInfo']
60     opt_info = request_json['optimInfo']
61     try:
62         mzn_solution = mzn_solver(mzn_content, opt_info)
63
64         response = {
65             'transactionId': req_info['transactionId'],
66             'requestID': req_info['requestID'],
67             'requestStatus': 'done',
68             'statusMessage': map_status(mzn_solution.status),
69             'solutions': mzn_solution[0] if mzn_solution else {}
70         }
71         return 200, json.dumps(response)
72     except Exception as e:
73         response = {
74             'transactionId': req_info['transactionId'],
75             'requestID': req_info['requestID'],
76             'requestStatus': 'failed',
77             'statusMessage': 'Failed due to {}'.format(e)
78         }
79         return 400, json.dumps(response)
80
81
82 def mzn_solver(mzn_content, opt_info):
83     """Calls the minizinc optimizer.
84
85     """
86     args = opt_info['solverArgs']
87     solver = get_mzn_solver(args.pop('solver'))
88     mzn_opts = dict()
89
90     try:
91         file_name = persist_opt_data(opt_info)
92         mzn_opts.update(args)
93         return minizinc(mzn_content, file_name, **mzn_opts, solver=solver)
94
95     finally:
96         delete_file_folder(file_name)
97
98
99 def persist_opt_data(opt_info):
100     """Persist the opt data, if included as part of the request.
101
102     return: file_name path of the optim_data
103             returns None if no optData is part of the request
104     """
105     file_name = None
106     if 'optData' in opt_info:
107         if opt_info['optData'].get('json'):
108             data_content = json.dumps(opt_info['optData']['json'])
109             file_name = '/tmp/optim_engine_{}.json'.format(datetime.timestamp(datetime.now()))
110             persist_data(data_content, file_name)
111         elif opt_info['optData'].get('text'):
112             data_content = opt_info['optData']['text']
113             file_name = '/tmp/optim_engine_{}.dzn'.format(datetime.timestamp(datetime.now()))
114             persist_data(data_content, file_name)
115     return file_name
116
117
118 def persist_data(data_content, file_name):
119     """Save the dzn data into a file
120
121     """
122     with open(file_name, "wt") as data:
123         data.write(data_content)
124
125
126 def get_mzn_solver(solver):
127     """Returns a solver type object for minizinc optimizers
128
129     solver: solver that is part of the request
130     return: solver mapped object
131     """
132     return solver_dict.get(solver)