Merge "Adding the generic solver code"
[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 import json
20 from datetime import datetime
21
22 from pymzn import Status, minizinc, cbc, gecode, chuffed, or_tools
23
24 from osdf.utils.file_utils import delete_file_folder
25
26 error_status_map = {
27     Status.INCOMPLETE: "incomplete",
28     Status.COMPLETE: "complete",
29     Status.UNSATISFIABLE: "unsatisfiable",
30     Status.UNKNOWN: "unknown",
31     Status.UNBOUNDED: "unbounded",
32     Status.UNSATorUNBOUNDED: "unsat_or_unbounded",
33     Status.ERROR: "error"
34 }
35
36 solver_dict = {
37     'cbc': cbc,
38     'geocode': gecode,
39     'chuffed': chuffed,
40     'cp': chuffed,
41     'or_tools': or_tools
42 }
43
44
45 def map_status(status):
46     return error_status_map.get(status, "failed")
47
48
49 def solve(request_json, mzn_content):
50     req_info = request_json['requestInfo']
51     opt_info = request_json['optimInfo']
52     try:
53         mzn_solution = mzn_solver(mzn_content, opt_info)
54
55         response = {
56             'transactionId': req_info['transactionId'],
57             'requestID': req_info['requestID'],
58             'requestStatus': 'done',
59             'statusMessage': map_status(mzn_solution.status),
60             'solutions': mzn_solution[0] if mzn_solution else {}
61         }
62         return 200, json.dumps(response)
63     except Exception as e:
64         response = {
65             'transactionId': req_info['transactionId'],
66             'requestID': req_info['requestID'],
67             'requestStatus': 'failed',
68             'statusMessage': 'Failed due to {}'.format(e)
69         }
70         return 400, json.dumps(response)
71
72
73 def mzn_solver(mzn_content, opt_info):
74     args = opt_info['solverArgs']
75     solver = get_mzn_solver(args.pop('solver'))
76     mzn_opts = dict()
77
78     try:
79         file_name = persist_opt_data(opt_info)
80         mzn_opts.update(args)
81         return minizinc(mzn_content, file_name, **mzn_opts, solver=solver)
82
83     finally:
84         delete_file_folder(file_name)
85
86
87 def persist_opt_data(opt_info):
88
89     if opt_info['optData'].get('json'):
90         data_content = json.dumps(opt_info['optData']['json'])
91         file_name = '/tmp/optim_engine_{}.json'.format(datetime.timestamp(datetime.now()))
92     elif opt_info['optData'].get('text'):
93         data_content = opt_info['optData']['text']
94         file_name = '/tmp/optim_engine_{}.dzn'.format(datetime.timestamp(datetime.now()))
95
96     with open(file_name, "wt") as data:
97         data.write(data_content)
98     return file_name
99
100
101 def get_mzn_solver(solver):
102     return solver_dict.get(solver)