From ed33a6354b7d8e3b45973ff9e6cb126f9aa458f7 Mon Sep 17 00:00:00 2001 From: Manas Date: Mon, 25 Jul 2022 17:31:20 +0530 Subject: [PATCH 1/6] writing knapsack in python --- CMakeLists.txt | 4 +-- configuration.conf | 2 +- sql/or_tools/CMakeLists.txt | 3 +- sql/or_tools/example.sql | 32 +++++++++++++++++++ sql/or_tools/or.py | 43 ++++++++++++++++++++++++++ sql/pg_controls/vrprouting.control | 1 + src/or_tools/CMakeLists.txt | 3 +- tools/testers/doc_queries_generator.pl | 3 +- tools/testers/setup_db.sql | 3 ++ 9 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 sql/or_tools/example.sql create mode 100644 sql/or_tools/or.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d9e4a8e62..cc4f19d2f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -331,8 +331,8 @@ endforeach() # Google OR tools #---------------------- -set(CMAKE_PREFIX_PATH "/home/manas/Codes/GSOC-2022/MyFork/ortools/") -find_package(ortools CONFIG REQUIRED) +# set(CMAKE_PREFIX_PATH "/home/manas/Codes/GSOC-2022/MyFork/ortools/") +# find_package(ortools CONFIG REQUIRED) #----------------------------------------------------------------------------- diff --git a/configuration.conf b/configuration.conf index cb1f091169..f1ddb638fa 100644 --- a/configuration.conf +++ b/configuration.conf @@ -28,4 +28,4 @@ simulation | N | Y | N optimizers | Y | N | N initialsol | Y | N | N vroom | Y | Y | Y -or_tools | Y | Y | N +or_tools | N | Y | N diff --git a/sql/or_tools/CMakeLists.txt b/sql/or_tools/CMakeLists.txt index 9d76c13cc9..2ecab9e6f5 100644 --- a/sql/or_tools/CMakeLists.txt +++ b/sql/or_tools/CMakeLists.txt @@ -1,6 +1,5 @@ SET(LOCAL_FILES - _knapsack.sql - knapsack.sql + example.sql ) foreach (f ${LOCAL_FILES}) diff --git a/sql/or_tools/example.sql b/sql/or_tools/example.sql new file mode 100644 index 0000000000..364c88afb6 --- /dev/null +++ b/sql/or_tools/example.sql @@ -0,0 +1,32 @@ +DROP FUNCTION IF EXISTS pymax; + +CREATE FUNCTION pymax (a integer, b integer) + RETURNS float +AS $$ + from ortools.linear_solver import pywraplp + solver = pywraplp.Solver.CreateSolver('GLOP') + x = solver.NumVar(0, 1, 'x') + y = solver.NumVar(0, 2, 'y') + + ct = solver.Constraint(0, 2, 'ct') + ct.SetCoefficient(x, 1) + ct.SetCoefficient(y, 1) + + objective = solver.Objective() + objective.SetCoefficient(x, 3) + objective.SetCoefficient(y, 1) + objective.SetMaximization() + + solver.Solve() + return x.solution_value() + 44.0 + +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION python_version() + RETURNS pg_catalog.text AS $BODY$ + + import sys + plpy.info(sys.version) + return 'finish' + $BODY$ +LANGUAGE plpython3u VOLATILE SECURITY DEFINER \ No newline at end of file diff --git a/sql/or_tools/or.py b/sql/or_tools/or.py new file mode 100644 index 0000000000..83ea69285a --- /dev/null +++ b/sql/or_tools/or.py @@ -0,0 +1,43 @@ +from ortools.linear_solver import pywraplp +from ortools.init import pywrapinit + + +def main(): + # Create the linear solver with the GLOP backend. + solver = pywraplp.Solver.CreateSolver('GLOP') + + # Create the variables x and y. + x = solver.NumVar(0, 1, 'x') + y = solver.NumVar(0, 2, 'y') + + print('Number of variables =', solver.NumVariables()) + + # Create a linear constraint, 0 <= x + y <= 2. + ct = solver.Constraint(0, 2, 'ct') + ct.SetCoefficient(x, 1) + ct.SetCoefficient(y, 1) + + print('Number of constraints =', solver.NumConstraints()) + + # Create the objective function, 3 * x + y. + objective = solver.Objective() + objective.SetCoefficient(x, 3) + objective.SetCoefficient(y, 1) + objective.SetMaximization() + + solver.Solve() + + print('Solution:') + print('Objective value =', objective.Value()) + print('x =', x.solution_value()) + print('y =', y.solution_value()) + + +if __name__ == '__main__': + pywrapinit.CppBridge.InitLogging('basic_example.py') + cpp_flags = pywrapinit.CppFlags() + cpp_flags.logtostderr = True + cpp_flags.log_prefix = False + pywrapinit.CppBridge.SetFlags(cpp_flags) + + main() \ No newline at end of file diff --git a/sql/pg_controls/vrprouting.control b/sql/pg_controls/vrprouting.control index ad9e4065dd..be82c526e6 100644 --- a/sql/pg_controls/vrprouting.control +++ b/sql/pg_controls/vrprouting.control @@ -6,3 +6,4 @@ relocatable = true requires = 'plpgsql' requires = 'postgis' requires = 'pgrouting' +requirs = 'plpython3u' diff --git a/src/or_tools/CMakeLists.txt b/src/or_tools/CMakeLists.txt index a23ff8c1cb..82e1541dd3 100644 --- a/src/or_tools/CMakeLists.txt +++ b/src/or_tools/CMakeLists.txt @@ -1,4 +1,3 @@ ADD_LIBRARY(or_tools OBJECT - knapsack.c - knapsack_driver.cpp + ) \ No newline at end of file diff --git a/tools/testers/doc_queries_generator.pl b/tools/testers/doc_queries_generator.pl index 2939067080..188d91c365 100755 --- a/tools/testers/doc_queries_generator.pl +++ b/tools/testers/doc_queries_generator.pl @@ -396,7 +396,8 @@ sub createTestDB { #TODO put as parameter my $encoding = "SET client_encoding TO 'UTF8';"; - + + mysystem("$psql $connopts -c \"$encoding CREATE EXTENSION IF NOT EXISTS plpython3u \" $DBNAME"); mysystem("$psql $connopts -c \"$encoding CREATE EXTENSION IF NOT EXISTS postgis $postgis_ver \" $DBNAME"); mysystem("$psql $connopts -c \"$encoding CREATE EXTENSION IF NOT EXISTS pgrouting $pgrouting_ver \" $DBNAME"); mysystem("$psql $connopts -c \"$encoding DROP EXTENSION IF EXISTS vrprouting CASCADE\" $DBNAME"); diff --git a/tools/testers/setup_db.sql b/tools/testers/setup_db.sql index 21b5d6a660..44bed8ecff 100644 --- a/tools/testers/setup_db.sql +++ b/tools/testers/setup_db.sql @@ -15,6 +15,9 @@ SET client_min_messages = WARNING; CREATE EXTENSION IF NOT EXISTS pgtap; CREATE EXTENSION IF NOT EXISTS vrprouting CASCADE; +CREATE EXTENSION IF NOT EXISTS plpython3u; +CREATE EXTENSION IF NOT EXISTS postgis; +CREATE EXTENSION IF NOT EXISTS pgrouting; BEGIN; From a33661f5ee8071e46a584df95d8129d405b9a835 Mon Sep 17 00:00:00 2001 From: Manas Date: Mon, 25 Jul 2022 19:22:41 +0530 Subject: [PATCH 2/6] exploring plpython3u --- sql/or_tools/_knapsack.sql | 46 --------------------- sql/or_tools/composite_types.sql | 58 +++++++++++++++++++++++++++ sql/or_tools/example.sql | 66 ++++++++++++++++++++----------- sql/or_tools/or_tools_example.sql | 33 ++++++++++++++++ sql/or_tools/plpython.md | 9 +++++ 5 files changed, 142 insertions(+), 70 deletions(-) delete mode 100644 sql/or_tools/_knapsack.sql create mode 100644 sql/or_tools/composite_types.sql create mode 100644 sql/or_tools/or_tools_example.sql create mode 100644 sql/or_tools/plpython.md diff --git a/sql/or_tools/_knapsack.sql b/sql/or_tools/_knapsack.sql deleted file mode 100644 index 7b49f8c730..0000000000 --- a/sql/or_tools/_knapsack.sql +++ /dev/null @@ -1,46 +0,0 @@ -/*PGR-GNU***************************************************************** -File: _knapsack.sql - -Copyright (c) 2021 pgRouting developers -Mail: project@pgrouting.org - -Function's developer: -Copyright (c) 2022 Manas Sivakumar - ------- - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - - ********************************************************************PGR-GNU*/ - - -CREATE OR REPLACE FUNCTION _vrp_knapsack( - TEXT, -- weights_cost SQL - - INTEGER, -- capacity - - OUT total_cost INTEGER, - OUT total_weight INTEGER, - OUT packed_costs INTEGER[], - OUT packed_weights INTEGER[] -) -RETURNS SETOF RECORD AS -'MODULE_PATHNAME' -LANGUAGE c VOLATILE STRICT; - --- COMMENTS - -COMMENT ON FUNCTION _vrp_knapsack(TEXT, INTEGER) -IS 'vrprouting internal function'; \ No newline at end of file diff --git a/sql/or_tools/composite_types.sql b/sql/or_tools/composite_types.sql new file mode 100644 index 0000000000..b20dcc9c91 --- /dev/null +++ b/sql/or_tools/composite_types.sql @@ -0,0 +1,58 @@ +DROP FUNCTION IF EXISTS overpaid; +DROP FUNCTION IF EXISTS make_pair; +DROP FUNCTION IF EXISTS make_pair_dict; +DROP FUNCTION IF EXISTS multiout_simple; +DROP TABLE IF EXISTS employee; +DROP TYPE IF EXISTS named_value; +DROP PROCEDURE IF EXISTS python_triple; + +CREATE TABLE employee ( + name text, + salary integer, + age integer +); + +CREATE TYPE named_value AS ( + name text, + value integer +); + +-- Input is a composite type +CREATE FUNCTION overpaid (e employee) + RETURNS boolean +AS $$ + if e["salary"] > 200000: + return True + if (e["age"] < 30) and (e["salary"] > 100000): + return True + return False +$$ LANGUAGE plpython3u; + +--output is a composite type +CREATE FUNCTION make_pair (name text, value integer) + RETURNS named_value[] +AS $$ + return (( name, value ), (name, value)) + # or alternatively, as list: return [ name, value ] +$$ LANGUAGE plpython3u; + +CREATE FUNCTION make_pair_dict (name text, value integer) + RETURNS named_value +AS $$ + return { "name": name, "value": value } +$$ LANGUAGE plpython3u; + +-- Returns the result in one line + +CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) +AS $$ + return (1, 2) +$$ LANGUAGE plpython3u; + + +CREATE PROCEDURE python_triple(INOUT a integer, INOUT b integer) +AS $$ + return (a * 3, b * 3) +$$ LANGUAGE plpython3u; + +-- CALL python_triple(5, 10); \ No newline at end of file diff --git a/sql/or_tools/example.sql b/sql/or_tools/example.sql index 364c88afb6..342706e2f1 100644 --- a/sql/or_tools/example.sql +++ b/sql/or_tools/example.sql @@ -1,32 +1,50 @@ -DROP FUNCTION IF EXISTS pymax; +DROP FUNCTION IF EXISTS pyadd; +DROP FUNCTION IF EXISTS pystrip; +DROP FUNCTION IF EXISTS return_arr; +DROP FUNCTION IF EXISTS md_array; +DROP FUNCTION IF EXISTS return_str_arr; +DROP FUNCTION IF EXISTS return_str; -CREATE FUNCTION pymax (a integer, b integer) - RETURNS float +-- function that adds two numbers +CREATE FUNCTION pyadd (a integer, b integer) + RETURNS integer AS $$ - from ortools.linear_solver import pywraplp - solver = pywraplp.Solver.CreateSolver('GLOP') - x = solver.NumVar(0, 1, 'x') - y = solver.NumVar(0, 2, 'y') - - ct = solver.Constraint(0, 2, 'ct') - ct.SetCoefficient(x, 1) - ct.SetCoefficient(y, 1) + if (a is None) or (b is None): + return None + x = a+b + return x +$$ LANGUAGE plpython3u; - objective = solver.Objective() - objective.SetCoefficient(x, 3) - objective.SetCoefficient(y, 1) - objective.SetMaximization() +-- The input parameters should be treated as read only +CREATE FUNCTION pystrip(x text) + RETURNS text +AS $$ + global x + x = x.strip() + return x +$$ LANGUAGE plpython3u; - solver.Solve() - return x.solution_value() + 44.0 +CREATE FUNCTION return_arr() + RETURNS int[] +AS $$ + return [1, 2, 3, 4, 5] +$$ LANGUAGE plpython3u; +CREATE FUNCTION md_array(x int4[]) + RETURNS int4[] +AS $$ + plpy.info(x, type(x)) + return x $$ LANGUAGE plpython3u; -CREATE OR REPLACE FUNCTION python_version() - RETURNS pg_catalog.text AS $BODY$ +CREATE FUNCTION return_str() + RETURNS text +AS $$ + return "hello" +$$ LANGUAGE plpython3u; - import sys - plpy.info(sys.version) - return 'finish' - $BODY$ -LANGUAGE plpython3u VOLATILE SECURITY DEFINER \ No newline at end of file +CREATE FUNCTION return_str_arr() + RETURNS varchar[] +AS $$ + return "hello" +$$ LANGUAGE plpython3u; \ No newline at end of file diff --git a/sql/or_tools/or_tools_example.sql b/sql/or_tools/or_tools_example.sql new file mode 100644 index 0000000000..a142a73dbf --- /dev/null +++ b/sql/or_tools/or_tools_example.sql @@ -0,0 +1,33 @@ +-- https://developers.google.com/optimization/introduction/python + +DROP FUNCTION IF EXISTS pymax; + +CREATE FUNCTION pymax (a integer, b integer) + RETURNS float +AS $$ + from ortools.linear_solver import pywraplp + solver = pywraplp.Solver.CreateSolver('GLOP') + x = solver.NumVar(0, 1, 'x') + y = solver.NumVar(0, 2, 'y') + + ct = solver.Constraint(0, 2, 'ct') + ct.SetCoefficient(x, 1) + ct.SetCoefficient(y, 1) + + objective = solver.Objective() + objective.SetCoefficient(x, 3) + objective.SetCoefficient(y, 1) + objective.SetMaximization() + + solver.Solve() + +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION python_version() + RETURNS pg_catalog.text AS $BODY$ + + import sys + plpy.info(sys.version) + return 'finish' + $BODY$ +LANGUAGE plpython3u VOLATILE SECURITY DEFINER \ No newline at end of file diff --git a/sql/or_tools/plpython.md b/sql/or_tools/plpython.md new file mode 100644 index 0000000000..223b0130d6 --- /dev/null +++ b/sql/or_tools/plpython.md @@ -0,0 +1,9 @@ + +### How to create a PL/Python function +``` +CREATE FUNCTION funcname (argument-list) + RETURNS return-type +AS $$ + # PL/Python function body +$$ LANGUAGE plpythonu; +``` \ No newline at end of file From 1e49dad9c7e189e4f8b6d2c92c5aea50a0b49b63 Mon Sep 17 00:00:00 2001 From: Manas Date: Mon, 25 Jul 2022 19:54:08 +0530 Subject: [PATCH 3/6] working with multiple rows --- sql/or_tools/mulitple_rows.sql | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 sql/or_tools/mulitple_rows.sql diff --git a/sql/or_tools/mulitple_rows.sql b/sql/or_tools/mulitple_rows.sql new file mode 100644 index 0000000000..f25d7ea2a7 --- /dev/null +++ b/sql/or_tools/mulitple_rows.sql @@ -0,0 +1,65 @@ +DROP FUNCTION IF EXISTS greet_same_cols CASCADE; +DROP FUNCTION IF EXISTS greet_separate_cols CASCADE; +DROP FUNCTION IF EXISTS greet CASCADE; +DROP FUNCTION IF EXISTS greet2 CASCADE; +DROP FUNCTION IF EXISTS multiout_simple_setof CASCADE; +DROP TYPE IF EXISTS greeting CASCADE; + +CREATE TYPE greeting AS ( + how text, + who text +); + +CREATE FUNCTION greet_separate_cols (how text) + RETURNS SETOF greeting +AS $$ + # return tuple containing lists as composite types + # all other combinations work also + return ( [ how, "World" ], [ how, "PostgreSQL" ], [ how, "PL/Python" ] ) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION greet_same_cols (how text) + RETURNS SETOF text +AS $$ + return ( [ how, "World" ], [ how, "PostgreSQL" ], [ how, "PL/Python" ] ) +$$ LANGUAGE plpython3u; + +--return variable number of outputs +CREATE FUNCTION greet (how text) + RETURNS SETOF greeting +AS $$ + for who in [ "World", "PostgreSQL", "PL/Python" ]: + yield ( how, who ) + for i in range(10): + yield (how, 'World') +$$ LANGUAGE plpython3u; + +-- Not working +CREATE FUNCTION greet2 (how text) + RETURNS SETOF greeting +AS $$ + class producer: + def __init__ (self, how, who): + self.how = how + self.who = who + self.ndx = -1 + + def __iter__ (self): + return self + + def next (self): + self.ndx += 1 + if self.ndx == len(self.who): + raise StopIteration + return ( self.how, self.who[self.ndx] ) + + return producer(how, [ "World", "PostgreSQL", "PL/Python" ]) +$$ LANGUAGE plpython3u; + +--returns set of records +--need to figure out how to change column name +CREATE FUNCTION multiout_simple_setof(n integer, OUT integer, OUT integer) + RETURNS SETOF record +AS $$ + return [(1, 2)] * n +$$ LANGUAGE plpython3u; From aba7357a38c3f553b792ab66511a651336c09ebd Mon Sep 17 00:00:00 2001 From: Manas Date: Tue, 26 Jul 2022 01:16:22 +0530 Subject: [PATCH 4/6] working implementation of ortools knapsack --- sql/or_tools/knapsack.sql | 103 ++++++++++++++++++++------------------ sql/or_tools/printing.sql | 23 +++++++++ 2 files changed, 78 insertions(+), 48 deletions(-) create mode 100644 sql/or_tools/printing.sql diff --git a/sql/or_tools/knapsack.sql b/sql/or_tools/knapsack.sql index fe12d261e3..7779f93a0f 100644 --- a/sql/or_tools/knapsack.sql +++ b/sql/or_tools/knapsack.sql @@ -1,50 +1,57 @@ -/*PGR-GNU***************************************************************** -File: knapsack_.sql - -Copyright (c) 2021 pgRouting developers -Mail: project@pgrouting.org - -Function's developer: -Copyright (c) 2022 Manas Sivakumar - - ------- - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - - ********************************************************************PGR-GNU*/ - - -CREATE OR REPLACE FUNCTION vrp_knapsack( - TEXT, -- weights_cost SQL +DROP FUNCTION IF EXISTS vrp_knapsack; +drop type if exists knapsack_items; +drop table if exists knapsack_data; + +CREATE TABLE knapsack_data( + weight INTEGER, + cost INTEGER); + +INSERT INTO knapsack_data (weight, cost) +VALUES +(12, 4), +(2, 2), +(1, 1), +(4, 10), +(1, 2); + +create type knapsack_items as( + index integer, + weight integer +); + +CREATE FUNCTION vrp_knapsack(inner_query text, capacity integer) + RETURNS SETOF knapsack_items +AS $$ + from ortools.algorithms import pywrapknapsack_solver - INTEGER, -- capacity - - OUT total_cost INTEGER, - OUT total_weight INTEGER, - OUT packed_cost INTEGER[], - OUT packed_weights INTEGER[] -) -RETURNS SETOF RECORD AS -$BODY$ - SELECT * - FROM _vrp_knapsack(_pgr_get_statement($1), $2); -$BODY$ -LANGUAGE SQL VOLATILE STRICT; - --- COMMENTS + solver = pywrapknapsack_solver.KnapsackSolver( + pywrapknapsack_solver.KnapsackSolver. + KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, 'KnapsackExample') + + capacities = [] + capacities.append(capacity) + inner_query_result = plpy.execute(inner_query, 5) + values = [] + weight1 = [] + weights =[] + for i in range(5): + values.append(inner_query_result[i]["cost"]) + weight1.append(inner_query_result[i]["weight"]) + weights.append(weight1) + + solver.Init(values, weights, capacities) + computed_value = solver.Solve() + + packed_items = [] + packed_weights = [] + total_weight = 0 + plpy.warning('Total value =', computed_value) + for i in range(len(values)): + if solver.BestSolutionContains(i): + packed_items.append(i) + packed_weights.append(weights[0][i]) + total_weight += weights[0][i] + yield (i, weights[0][i]) + plpy.warning('Total weight:', total_weight) +$$ LANGUAGE plpython3u; -COMMENT ON FUNCTION vrp_knapsack(TEXT, INTEGER) -IS 'vrp_knapsack'; \ No newline at end of file diff --git a/sql/or_tools/printing.sql b/sql/or_tools/printing.sql new file mode 100644 index 0000000000..be13cb6f64 --- /dev/null +++ b/sql/or_tools/printing.sql @@ -0,0 +1,23 @@ +DROP TABLE IF EXISTS knapsack_data; +CREATE TABLE knapsack_data( + weight INTEGER, + cost INTEGER); + +INSERT INTO knapsack_data (weight, cost) +VALUES +(12, 4), +(2, 2), +(1, 1), +(4, 10), +(1, 2); + +DROP FUNCTION IF EXISTS access_table; +CREATE FUNCTION access_table(inner_query text, capacity integer) + RETURNS integer +AS $$ + result = plpy.execute(inner_query, 5) + plpy.log("Hello World") + plpy.warning("Hello World") + plpy.warning(result[0]["cost"], result[0]["weight"]) + return capacity +$$ LANGUAGE plpython3u; \ No newline at end of file From 2206f5be8b0672dd86a8afcc4fefc102d5e58417 Mon Sep 17 00:00:00 2001 From: Manas Date: Sun, 31 Jul 2022 19:03:01 +0530 Subject: [PATCH 5/6] Corrected spelling --- sql/pg_controls/vrprouting.control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pg_controls/vrprouting.control b/sql/pg_controls/vrprouting.control index be82c526e6..b973f5ea72 100644 --- a/sql/pg_controls/vrprouting.control +++ b/sql/pg_controls/vrprouting.control @@ -6,4 +6,4 @@ relocatable = true requires = 'plpgsql' requires = 'postgis' requires = 'pgrouting' -requirs = 'plpython3u' +requires = 'plpython3u' From db4fdda7516d356faced5bdc6247e2ec9abd2da7 Mon Sep 17 00:00:00 2001 From: Manas Date: Mon, 1 Aug 2022 00:28:57 +0530 Subject: [PATCH 6/6] [src/or_tools] multiple knapsack and bin packing implementation --- sql/or_tools/bin_packing.sql | 67 ++++++++++++++++++++ sql/or_tools/knapsack.sql | 8 ++- sql/or_tools/multiple_knapsack.sql | 98 ++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 sql/or_tools/bin_packing.sql create mode 100644 sql/or_tools/multiple_knapsack.sql diff --git a/sql/or_tools/bin_packing.sql b/sql/or_tools/bin_packing.sql new file mode 100644 index 0000000000..2dde5cf1ee --- /dev/null +++ b/sql/or_tools/bin_packing.sql @@ -0,0 +1,67 @@ +DROP FUNCTION IF EXISTS vrp_bin_packing CASCADE; +DROP TABLE IF EXISTS bin_packing_data CASCADE; + +CREATE TABLE bin_packing_data( + weight INTEGER); + +INSERT INTO bin_packing_data (weight) +VALUES +(48), (30), (19), (36), (36), (27), (42), (42), (36), (24), (30); + + +CREATE FUNCTION vrp_bin_packing(inner_query text, bin_capacity integer) + RETURNS integer +AS $$ + from ortools.linear_solver import pywraplp + inner_query_result = plpy.execute(inner_query, 11) + data = {} + weights = [] + for i in range(11): + weights.append(inner_query_result[i]["weight"]) + data['weights'] = weights + data['items'] = list(range(len(weights))) + data['bins'] = data['items'] + data['bin_capacity'] = bin_capacity + + solver = pywraplp.Solver.CreateSolver('SCIP') + x = {} + for i in data['items']: + for j in data['bins']: + x[(i, j)] = solver.IntVar(0, 1, 'x_%i_%i' % (i, j)) + + y = {} + for j in data['bins']: + y[j] = solver.IntVar(0, 1, 'y[%i]' % j) + + for i in data['items']: + solver.Add(sum(x[i, j] for j in data['bins']) == 1) + + for j in data['bins']: + solver.Add(sum(x[(i, j)] * data['weights'][i] for i in data['items']) <= y[j] * data['bin_capacity']) + + solver.Minimize(solver.Sum([y[j] for j in data['bins']])) + + status = solver.Solve() + + if status == pywraplp.Solver.OPTIMAL: + num_bins = 0. + for j in data['bins']: + if y[j].solution_value() == 1: + bin_items = [] + bin_weight = 0 + for i in data['items']: + if x[i, j].solution_value() > 0: + bin_items.append(i) + bin_weight += data['weights'][i] + if bin_weight > 0: + num_bins += 1 + plpy.warning('Bin number', j) + plpy.warning(' Items packed', bin_items) + plpy.warning(' Total weight', bin_weight) + plpy.warning('Number of bins used', num_bins) + else: + plpy.error('The problem does not have an optimal solution') + return 1 +$$ LANGUAGE plpython3u; + +SELECT * from vrp_bin_packing('SELECT * from bin_packing_data', 100); \ No newline at end of file diff --git a/sql/or_tools/knapsack.sql b/sql/or_tools/knapsack.sql index 7779f93a0f..3fab196ced 100644 --- a/sql/or_tools/knapsack.sql +++ b/sql/or_tools/knapsack.sql @@ -16,7 +16,8 @@ VALUES create type knapsack_items as( index integer, - weight integer + weight integer, + cost integer ); CREATE FUNCTION vrp_knapsack(inner_query text, capacity integer) @@ -44,14 +45,17 @@ AS $$ packed_items = [] packed_weights = [] + packed_values = [] total_weight = 0 plpy.warning('Total value =', computed_value) for i in range(len(values)): if solver.BestSolutionContains(i): packed_items.append(i) packed_weights.append(weights[0][i]) + packed_values.append(values[i]) total_weight += weights[0][i] - yield (i, weights[0][i]) + yield (i, weights[0][i], values[i]) plpy.warning('Total weight:', total_weight) $$ LANGUAGE plpython3u; +SELECT * from vrp_knapsack('SELECT * from knapsack_data' , 15); \ No newline at end of file diff --git a/sql/or_tools/multiple_knapsack.sql b/sql/or_tools/multiple_knapsack.sql new file mode 100644 index 0000000000..1924327fde --- /dev/null +++ b/sql/or_tools/multiple_knapsack.sql @@ -0,0 +1,98 @@ +DROP FUNCTION IF EXISTS vrp_multiple_knapsack CASCADE; +DROP TYPE IF EXISTS multiple_knapsack_items CASCADE; +DROP TABLE IF EXISTS multiple_knapsack_data CASCADE; + +CREATE TABLE multiple_knapsack_data( + weight INTEGER, + cost INTEGER); + +INSERT INTO multiple_knapsack_data (weight, cost) +VALUES +(48, 10), +(30, 30), +(42, 25), +(36, 50), +(36, 35), +(48, 30), +(42, 15), +(42, 40), +(36, 30), +(24, 35), +(30, 45), +(30, 10), +(42, 20), +(36, 30), +(36, 25); + +create type multiple_knapsack_items as( + index integer, + weight integer, + cost integer +); + +CREATE FUNCTION vrp_multiple_knapsack(inner_query text, capacities integer[]) + RETURNS SETOF multiple_knapsack_items +AS $$ + from ortools.linear_solver import pywraplp + + data = {} + data['values'] = [] + data['weights'] = [] + inner_query_result = plpy.execute(inner_query, 15) + for i in range(15): + data['values'].append(inner_query_result[i]["cost"]) + data['weights'].append(inner_query_result[i]["weight"]) + + data['num_items'] = len(data['weights']) + data['all_items'] = range(data['num_items']) + + data['bin_capacities'] = capacities + data['num_bins'] = len(data['bin_capacities']) + data['all_bins'] = range(data['num_bins']) + + solver = pywraplp.Solver.CreateSolver('SCIP') + if solver is None: + plpy.error('SCIP solver unavailable.') + + x = {} + for i in data['all_items']: + for b in data['all_bins']: + x[i, b] = solver.BoolVar(f'x_{i}_{b}') + + for i in data['all_items']: + solver.Add(sum(x[i, b] for b in data['all_bins']) <= 1) + + for b in data['all_bins']: + solver.Add(sum(x[i, b] * data['weights'][i] for i in data['all_items']) <= data['bin_capacities'][b]) + + objective = solver.Objective() + for i in data['all_items']: + for b in data['all_bins']: + objective.SetCoefficient(x[i, b], data['values'][i]) + objective.SetMaximization() + + status = solver.Solve() + + if status == pywraplp.Solver.OPTIMAL: + plpy.warning('Total value =', objective.Value()) + total_weight = 0 + for b in data['all_bins']: + plpy.warning('Bin :', b) + bin_weight = 0 + bin_value = 0 + for i in data['all_items']: + if x[i, b].solution_value() > 0: + yield(i, data['weights'][i], data['values'][i]) + bin_weight += data['weights'][i] + bin_value += data['values'][i] + plpy.warning('Packed bin weight', bin_weight) + plpy.warning('Packed bin value', bin_value) + total_weight += bin_weight + plpy.warning('Total packed weight', total_weight) + else: + plpy.error('The problem does not have an optimal solution.') +$$ LANGUAGE plpython3u; + +SELECT * from vrp_multiple_knapsack('SELECT * from multiple_knapsack_data', ARRAY[100,100,100,100,100]); + +--Have to learn how to leave space \ No newline at end of file