optapy / optapy

OptaPy is an AI constraint solver for Python to optimize planning and scheduling problems.

Home Page:https://www.optapy.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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!