Using the same solver_manager_create for different input parameters
bilics opened this issue · comments
Hello,
I have a question regarding the solver_manager_create
- I would to like to be able to call an endpoint with specific routing parameters (different domain values for customers, demands, vehicles, etc) and receive a SINGLETON_ID
to check upon statuses.
def solver_manager_create(solver_config: '_SolverConfig') -> '_SolverManager':
"""Creates a new SolverManager, which can be used to solve problems asynchronously (ex: Web requests).
So is it possible to update solver_config
for multiple "tenants" using the same instance of _SolverManager
(and update/get statuses using the SINGLETON_ID
parameter)?
Or is it necessary to use something like Celery, to create independent/separate instances of _SolverManager
for each call, with different parameters?)
Thanks!
That depends: by input parameters, are you talking about different problems or different SolverConfig?
If it for different problems (with the same SolverConfig), then that the purpose of SolverManager
; you can use it to schedule solvers for asynchronous requests. There is one thing to be careful of though: do not use web frameworks that start a new process to handle a request. That will cause the Solver
to be started in that process, but because that process only lives as long as the request, it will die (and additionally, the SolverManager
in the main process will have no knowledge of the SolverManager
in the subprocess).
If it for different problems with different SolverConfig
(ex: different termination time), then you currently need to create seperate SolverFactory
/SolverManager
instance to handle each request. The related OptaPlanner issue is https://issues.redhat.com/browse/PLANNER-2663 ; Once that feature been implemented in OptaPlanner
, it will be ported to OptaPy
.
I imagine it is the same SolverConfig if I just to change the number of number of customers, their locations - and demands for each location will be different for each call.
In this case I would need to simply update the VehicleRoutingSolution
with the appropriate values, correct?
There is one thing to be careful of though: do not use web frameworks that start a new process to handle a request. That will cause the Solver to be started in that process, but because that process only lives as long as the request, it will die (and additionally, the SolverManager in the main process will have no knowledge of the SolverManager in the subprocess).
Got it - thanks.
I imagine it is the same SolverConfig if I just to change the number of number of customers, their locations - and demands for each location will be different for each call.
In this case I would need to simply update the
VehicleRoutingSolution
with the appropriate values, correct?
Correct; just pass in a VehicleRoutingSolution
instance with appropriate values for the tenant.
Thanks @Christopher-Chianelli - got it. Closing this.
Hello, re-opening this because I don't think I understood completely how to setup/schedule the solver for multiple subsequent calls. Please find below the current code to test this scenario - where each call so SolveAndListen
will act on a different set of config parameters.
vehicle_routing_solution = []
id_sequence = [1]
def generate_id():
out = id_sequence[0]
id_sequence[0] = out + 1
return out
@app.route('/vrp/solve', methods=['POST'])
def solve():
params = request.json
customers = params['customers']
vehicles = params['customers']
global vehicle_routing_solution
uid = generate_id()
solution = DataBuilder.builder().
.set_vehicles(vehicles)\
.set_customers(customers)\
.build()
solution.set_score(HardSoftScore.ZERO)
vehicle_routing_solution.append(solution)
solver_manager.solveAndListen(uid, find_by_id, save, error_handler)
return dict({ "status": uid})
def find_by_id(schedule_id):
global vehicle_routing_solution
return vehicle_routing_solution[schedule_id]
def save(solution):
global vehicle_routing_solution
# Not sure how to implement this function, since the UID is not available
def error_handler(problem_id, exception):
print(f'an exception occurred solving {problem_id}: {exception.getMessage()}')
exception.printStackTrace()
The above code yields an error that I can not identify the problem:
Traceback (most recent call last):
File "/opt/homebrew/Cellar/python@3.10/3.10.6_2/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
self.run()
File "/opt/homebrew/Cellar/python@3.10/3.10.6_2/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 953, in run
self._target(*self._args, **self._kwargs)
File "/opt/homebrew/lib/python3.10/site-packages/optapy/optaplanner_api_wrappers.py", line 29, in await_best_solution_from_solver_job
raise e
File "/opt/homebrew/lib/python3.10/site-packages/optapy/optaplanner_api_wrappers.py", line 26, in await_best_solution_from_solver_job
solver_job.getFinalBestSolution()
java.util.concurrent.java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: org.jpype.PyExceptionProxy
Thanks in advance @Christopher-Chianelli - if you have to point me in the right direction.
Sadly, exception info is lost when setting a Python exception as the cause for a Java exception. Where is request
defined (it is not a parameter for solve
)? solve
look correct; the issue is either in your constraints or domain model, but I would need to see them to say where.
Hi @Christopher-Chianelli - sorry about the delay - I have created a minimal example here:
https://github.com/bilics/optapy-test
The idea is to expose the solver via a REST API call:
curl --request POST \
--url http://127.0.0.1:5002/vrp/solve \
--header 'Content-Type: application/json' \
--data '{
"vehicles":
[
{ "capacity": 25, "kind": 1, "depot":0},
{ "capacity": 50, "kind": 2, "depot":0}
],
"depots":
[
{ "id": 1, "latitude": -19.3, "longitude": -42.1}
],
"customers":
[
{ "id": 1, "latitude": -23.1, "longitude": -46.243, "demand": 8},
{ "id": 1, "latitude": -23.12, "longitude": -46.01264, "demand": 8},
{ "id": 1, "latitude": -23.14, "longitude": -46.1987, "demand": 8},
{ "id": 1, "latitude": -22.167, "longitude": -45.341, "demand": 8},
{ "id": 1, "latitude": -21.1, "longitude": -46.843, "demand": 8},
{ "id": 1, "latitude": -25.12, "longitude": -45.264, "demand": 8},
{ "id": 1, "latitude": -23.74, "longitude": -46.6987, "demand": 8},
{ "id": 1, "latitude": -22.467, "longitude": -45.241, "demand": 8},
{ "id": 1, "latitude": -23.8, "longitude": -46.943, "demand": 8},
{ "id": 1, "latitude": -23.9712, "longitude": -45.264, "demand": 8},
{ "id": 1, "latitude": -23.154, "longitude": -44.2987, "demand": 8},
{ "id": 1, "latitude": -21.167, "longitude": -46.141, "demand": 8}
]
}'
Thanks!