From 7fc4c48e2845c192358e0f79bf3a99564ef1b520 Mon Sep 17 00:00:00 2001 From: "nikolaj.van.omme@gmail.com" Date: Tue, 10 Feb 2015 19:24:05 +0000 Subject: [PATCH] Doc automatic update --- documentation/documentation_hub.html | 28 +- .../tutorials/cplusplus/chap10/A-n32-k5.vrp | 76 ++ .../tutorials/cplusplus/chap10/Makefile | 84 ++ .../cplusplus/chap10/check_cvrp_solution.cc | 59 ++ .../cplusplus/chap10/check_vrp_solution.cc | 62 ++ .../tutorials/cplusplus/chap10/cvrp_basic.cc | 154 +++ .../tutorials/cplusplus/chap10/cvrp_data.h | 202 ++++ .../cplusplus/chap10/cvrp_data_generator.cc | 48 + .../cplusplus/chap10/cvrp_data_generator.h | 145 +++ .../cplusplus/chap10/cvrp_epix_data.h | 132 +++ .../cplusplus/chap10/cvrp_solution.h | 281 ++++++ .../cplusplus/chap10/cvrp_solution_to_epix.cc | 61 ++ .../cplusplus/chap10/rl_auxiliary_graph.cc | 87 ++ .../tutorials/cplusplus/chap10/vrp.cc | 129 +++ .../cplusplus/chap10/vrp_partial_routes.cc | 97 ++ .../cplusplus/chap10/vrp_solution_to_epix.cc | 61 ++ .../tutorials/cplusplus/chap6/Makefile | 14 +- .../tutorials/cplusplus/chap7/Makefile | 72 ++ documentation/tutorials/cplusplus/chap7/abz9 | 27 + .../tutorials/cplusplus/chap7/dummy_lns.cc | 131 +++ .../cplusplus/chap7/golomb_default_search1.cc | 105 +++ .../cplusplus/chap7/golomb_default_search2.cc | 138 +++ .../tutorials/cplusplus/chap7/jobshop.h | 266 ++++++ .../cplusplus/chap7/jobshop_heuristic.cc | 349 +++++++ .../tutorials/cplusplus/chap7/jobshop_lns.cc | 250 +++++ .../tutorials/cplusplus/chap7/jobshop_lns.h | 91 ++ .../tutorials/cplusplus/chap7/jobshop_ls.h | 184 ++++ .../tutorials/cplusplus/chap7/jobshop_sa1.cc | 273 ++++++ .../tutorials/cplusplus/chap7/jobshop_sa2.cc | 352 +++++++ .../tutorials/cplusplus/chap7/jobshop_ts1.cc | 278 ++++++ .../tutorials/cplusplus/chap7/jobshop_ts2.cc | 361 ++++++++ .../tutorials/cplusplus/chap7/limits.h | 230 +++++ .../tutorials/cplusplus/common/IO_helpers.h | 98 ++ .../tutorials/cplusplus/common/common_flags.h | 30 + .../tutorials/cplusplus/common/constants.h | 27 + .../tutorials/cplusplus/common/limits.h | 233 +++++ .../tutorials/cplusplus/common/random.h | 39 + .../cplusplus/routing_common/routing_common.h | 233 +++++ .../routing_common/routing_common_flags.h | 58 ++ .../cplusplus/routing_common/routing_data.h | 228 +++++ .../routing_common/routing_data_generator.h | 80 ++ .../routing_common/routing_distance.h | 76 ++ .../routing_common/routing_epix_helper.h | 124 +++ .../cplusplus/routing_common/routing_random.h | 73 ++ .../routing_common/routing_solution.h | 51 + .../cplusplus/routing_common/tsplib.h | 414 +++++++++ .../cplusplus/routing_common/tsplib_reader.h | 618 +++++++++++++ .../_images/LNS_basic_pseudo_code.png | Bin 0 -> 18837 bytes .../user_manual/_images/SA_pseudo_code.png | Bin 0 -> 48971 bytes .../_images/local_search_basic.png | Bin 20555 -> 9239 bytes .../_images/local_search_basic1.png | Bin 0 -> 7209 bytes ...1510e1a32cb0a43379c30067d242d3e8ca6839.png | Bin 0 -> 718 bytes ...bfbd937b9f2a0291991e5207aaf77a2fc3188d.png | Bin 0 -> 554 bytes ...4eaa0da05ee92eb6ddc7199719650597a7e8b3.png | Bin 0 -> 897 bytes ...9d51d87e0861f4c5a32cab44a96601bb80f190.png | Bin 0 -> 763 bytes ...b8fbd46ad8a2a3c2b814e9218bd7b74c10e0db.png | Bin 0 -> 599 bytes ...93277d05fadf0c14f8c26c3cbf3afb3fd7f680.png | Bin 0 -> 679 bytes ...8aa82efdd03747feb4ddd348d0625e67eff5ea.png | Bin 0 -> 719 bytes ...10829d67b6b936efdbeea243f679c4ec3c8db6.png | Bin 0 -> 402 bytes ...c81928c3120c88c86d6921521b0c7b30ba4fcc.png | Bin 0 -> 820 bytes ...2c91b7bf7514e1e910f722cd4138d1a9980ede.png | Bin 0 -> 380 bytes ...10515ceeb0b173969e370cc46acb837b622b17.png | Bin 0 -> 428 bytes ...cc1ce4f5e733f6bcd0099f5a944f316e143c51.png | Bin 0 -> 1210 bytes ...1cabda3a9b09f0dde217303ca9d1cd9201dcf6.png | Bin 0 -> 258 bytes ...f8872436b1107ed9d7826131afcac9e5dc3311.png | Bin 0 -> 395 bytes ...bc7b3a267c87204236fefbe603eb79e49f6f3d.png | Bin 0 -> 428 bytes ...a212babe1948e16074ec200305e87a5a0fcab7.png | Bin 0 -> 520 bytes ...a388683d02039ebaedec336f4018ed9c3f440a.png | Bin 0 -> 904 bytes ...70dc00d93b45bbdac94c3f2c6ffa76a63f4353.png | Bin 0 -> 402 bytes ...dc65f10d4ad510c21139adc18e2b2f5f769591.png | Bin 0 -> 633 bytes ...e72f28a9fe0a456173c9287d7a195c663e171b.png | Bin 0 -> 317 bytes ...9e91f1fa979b0c78fcc1782e59bffd2a5cbd6e.png | Bin 0 -> 477 bytes ...9a6a2610357101d77dbae8eb9bf0b3100c08fd.png | Bin 0 -> 381 bytes ...162eb98dd00c45b4508034c18a16d1a1c1b005.png | Bin 0 -> 421 bytes ...efd4ce7260acbbb9e4e190abfff4eb58ee8b28.png | Bin 0 -> 1664 bytes ...1f4a3906d0bcfa952c51fd13df36999c67807a.png | Bin 0 -> 708 bytes ...af5e311e32ebc237f7beacbfddb6441b567293.png | Bin 0 -> 317 bytes ...ef88777c45e887e58044375905b793db021201.png | Bin 0 -> 392 bytes ...a9f44bc51ca08a5320765c38c0bc2f888e5c03.png | Bin 0 -> 529 bytes ...039f9efc024403b48c35fbad53021426a96dd4.png | Bin 0 -> 564 bytes ...1fb80920b214cd9f8ccb4af360b4891402c0e3.png | Bin 0 -> 264 bytes ...72e879f0b05d2802c5deeb8a7dbd466fd5db8e.png | Bin 0 -> 629 bytes ...6dbdbff63bda725faec7bfeec618885320dbb7.png | Bin 0 -> 493 bytes ...bd49c9ca0568bc2d7eb7de1c6dbbe837fbe77e.png | Bin 0 -> 999 bytes ...c8f1f7113f6cef68a960258d62da05fa5a431e.png | Bin 0 -> 601 bytes ...85ef5682ae63b4fbcd9828e8fef32fda75744f.png | Bin 0 -> 448 bytes ...a96d47fc75f521c80193b9c030c717f8be9c67.png | Bin 0 -> 365 bytes ...f25bcaf968c85f66afd01b11fe4feb67c125da.png | Bin 0 -> 383 bytes ...5f6f309a39d7624db63c54efded0c203d0a3bc.png | Bin 0 -> 407 bytes ...741b899afcd7fe00da0afec6c45b63a0028329.png | Bin 0 -> 338 bytes ...925497ae709bf610fdb1956943a7af0cc7918d.png | Bin 0 -> 641 bytes ...3153ca777e70a3b7f675b3a8f4fa620aab16c6.png | Bin 0 -> 323 bytes ...1c19d517b8ce8720b4bdc36551378c4ceb8d54.png | Bin 0 -> 1041 bytes ...db303f8421b8383c8ac4ac559259c60ac85205.png | Bin 0 -> 373 bytes ...fef4246fe548d6fcb1f8734af167930e5e5d4b.png | Bin 0 -> 373 bytes ...0b3b32d353682f471e1b2cc26eaa4edc29b19a.png | Bin 0 -> 429 bytes ...d068bd0fdbb1e76139d9d423ce5800e6812949.png | Bin 0 -> 758 bytes ...c7c996f057bd3698f1b0cff82bbc3a1714ba3b.png | Bin 0 -> 375 bytes ...3f6c55e39366ad7956d6cd4d84583e894b661a.png | Bin 0 -> 288 bytes ...ca2c46f98ac4992c543dcd5aab7c897d2c9f8a.png | Bin 0 -> 301 bytes ...4588fd900d02afcbd260bc07f54cce49a7dc4a.png | Bin 0 -> 248 bytes ...bd61285bfa72545a81de040c02c01d2b6994a2.png | Bin 0 -> 403 bytes ...8115215e9ea837e8fd35830c27a3a6b53823c8.png | Bin 0 -> 319 bytes ...8a2d6368b051ed1915a58813bec0cb785c0577.png | Bin 0 -> 404 bytes ...d1ad74677466d8ab0a46eb34e1e710d5be06d6.png | Bin 0 -> 354 bytes ...644f5aef88a86a47a52798705d58eb6ce1c6e0.png | Bin 0 -> 1468 bytes ...3c2367082d26bd66ac95de4aa8118daf060a6a.png | Bin 0 -> 472 bytes ...9c73a2e6e1b46d6d98a076aaf2cd9a0da3ab2a.png | Bin 0 -> 589 bytes ...d9ce9060e61f4b7c20694ba20f8430ab71cf51.png | Bin 0 -> 401 bytes ...e54ded8ebbe64b98214f34ab44d7f3ad54d337.png | Bin 0 -> 385 bytes ...2f1a28628c14e247dd11ec0b00d9d8b2361acb.png | Bin 0 -> 399 bytes ...92693f4651f9955977b4130f938061c901d2e3.png | Bin 0 -> 1540 bytes ...12ea8eec45b790e08bc47803bab83a155b7d52.png | Bin 0 -> 574 bytes ...13547684600daec60944e8aadad3c60063047f.png | Bin 0 -> 514 bytes ...4e531e3d2a6da0bcd0bc3940d5925f2e48ced5.png | Bin 0 -> 316 bytes .../_images/metaheuristics_hierarchy.png | Bin 0 -> 3493 bytes .../user_manual/_images/sequence_lns.png | Bin 0 -> 33737 bytes documentation/user_manual/genindex.html | 2 +- documentation/user_manual/index.html | 2 +- documentation/user_manual/manual/LS.html | 4 +- documentation/user_manual/manual/TSP.html | 10 +- documentation/user_manual/manual/VRP.html | 2 +- .../manual/custom_constraints.html | 63 +- .../all_different_except_zero_definition.html | 204 ++++ .../all_different_except_zero_model.html | 177 ++++ ...rent.html => alldifferent_constraint.html} | 32 +- ... alldifferent_except_zero_constraint.html} | 45 +- .../basic_constraint_example.html | 232 +++++ .../basic_working_constraints.html | 72 +- .../custom_constraints/consistency.html | 30 +- .../manual/custom_constraints/summary.html | 20 +- .../user_manual/manual/first_steps.html | 2 +- .../manual/first_steps/anatomy.html | 2 +- .../manual/first_steps/cryptarithmetic.html | 2 +- .../manual/first_steps/getting_started.html | 2 +- .../manual/first_steps/monitors.html | 2 +- .../manual/first_steps/parameters.html | 4 +- .../manual/first_steps/summary.html | 10 +- .../first_steps/supported_languages.html | 2 +- .../user_manual/manual/introduction.html | 2 +- .../manual/introduction/4queens.html | 2 +- .../manual/introduction/manual_content.html | 4 +- .../manual/introduction/or_tools.html | 2 +- .../manual/introduction/real_examples.html | 4 +- .../manual/introduction/summary.html | 2 +- .../manual/introduction/theory.html | 2 +- .../manual/introduction/three_stages.html | 2 +- .../manual/introduction/tradeoffs.html | 2 +- .../manual/introduction/what_is_cp.html | 2 +- .../manual/ls/basic_working_local_search.html | 24 +- .../manual/ls/jobshop_def_data.html | 2 +- .../manual/ls/jobshop_implementation.html | 2 +- .../user_manual/manual/ls/jobshop_ls.html | 10 +- .../user_manual/manual/ls/local_search.html | 2 +- .../user_manual/manual/ls/ls_filtering.html | 2 +- .../user_manual/manual/ls/ls_operators.html | 8 +- .../user_manual/manual/ls/ls_summary.html | 2 +- .../manual/ls/scheduling_or_tools.html | 4 +- .../user_manual/manual/metaheuristics.html | 180 +++- .../manual/metaheuristics/GLS.html | 874 +++++++++++++++++- .../user_manual/manual/metaheuristics/SA.html | 336 ++++++- .../manual/metaheuristics/default_search.html | 333 ++++++- .../manual/metaheuristics/jobshop_lns.html | 462 ++++++++- .../manual/metaheuristics/metaheuristics.html | 266 +++++- .../metaheuristics_summary.html | 47 +- .../manual/metaheuristics/restart.html | 166 +++- .../manual/metaheuristics/search_limits.html | 158 +++- .../manual/metaheuristics/tabu.html | 619 ++++++++++++- .../user_manual/manual/modeling_tricks.html | 54 +- .../customized_search_primitives.html | 34 +- .../dynamic_improvements.html} | 57 +- .../manual/modeling_tricks/efficiency.html | 34 +- .../manual/modeling_tricks/false_friends.html | 50 +- .../modeling_tricks/local_search_tricks.html | 178 ++++ .../manual/modeling_tricks/parallelizing.html | 166 ++++ .../modeling_tricks/solving_options.html | 64 +- .../manual/modeling_tricks/variables.html | 192 ++++ .../user_manual/manual/objectives.html | 8 +- .../manual/objectives/content_model.html | 10 +- .../manual/objectives/data_search.html | 10 +- .../objectives/first_implementation.html | 10 +- .../manual/objectives/golomb_first_model.html | 10 +- .../objectives/objective_functions.html | 18 +- .../manual/objectives/optimization_how.html | 10 +- .../objectives/second_implementation.html | 10 +- .../manual/objectives/summary.html | 10 +- .../objectives/third_implementation.html | 10 +- .../manual/objectives/tighten_model.html | 10 +- .../user_manual/manual/reification.html | 2 +- .../manual/reification/reification.html | 2 +- .../user_manual/manual/search_primitives.html | 2 +- .../basic_model_implementation.html | 2 +- .../basic_working_phases.html | 2 +- .../basic_working_search_algorithm.html | 2 +- .../search_primitives/breaking_symmetry.html | 2 +- .../manual/search_primitives/cpviz.html | 2 +- .../customized_search_primitives.html | 2 +- .../manual/search_primitives/nqueens.html | 2 +- .../out_of_the_box_search_primitives.html | 2 +- .../manual/search_primitives/summary.html | 2 +- .../manual/tsp/first_tsp_implementation.html | 10 +- .../tsp/first_tsptw_implementation.html | 2 +- .../manual/tsp/model_behind_scenes.html | 10 +- .../tsp/model_behind_scenes_overview.html | 10 +- .../manual/tsp/routing_library.html | 2 +- documentation/user_manual/manual/tsp/tsp.html | 2 +- .../user_manual/manual/tsp/tsptw.html | 2 +- .../user_manual/manual/tsp/tsptw_summary.html | 2 +- .../manual/tsp/two_phases_approaches.html | 2 +- .../manual/tsp/zoo_routing_problems.html | 2 +- .../user_manual/manual/under_the_hood.html | 8 +- .../manual/under_the_hood/assignment.html | 2 +- .../manual/under_the_hood/classes.html | 2 +- .../manual/under_the_hood/conventions.html | 6 +- .../manual/under_the_hood/files.html | 2 +- .../user_manual/manual/under_the_hood/ls.html | 2 +- .../manual/under_the_hood/metaheuristics.html | 2 +- .../manual/under_the_hood/queue.html | 2 +- .../user_manual/manual/under_the_hood/rl.html | 4 +- .../under_the_hood/search_monitors.html | 2 +- .../manual/under_the_hood/summary.html | 2 +- .../manual/under_the_hood/trail.html | 2 +- .../user_manual/manual/utilities.html | 3 +- .../manual/utilities/asserting.html | 62 +- .../manual/utilities/debugging.html | 37 +- .../manual/utilities/flatzinc.html | 2 +- .../user_manual/manual/utilities/logging.html | 84 +- .../manual/utilities/profiling.html | 40 +- .../manual/utilities/randomness.html | 2 +- .../manual/utilities/serializing.html | 2 +- .../user_manual/manual/utilities/timing.html | 2 +- .../manual/utilities/visualizing.html | 37 +- .../user_manual/manual/vrp/cvrp.html | 2 +- .../user_manual/manual/vrp/cvrp_summary.html | 2 +- .../manual/vrp/first_cvrp_implementation.html | 2 +- .../manual/vrp/first_vrp_implementation.html | 2 +- .../user_manual/manual/vrp/multi_depots.html | 2 +- .../manual/vrp/partial_routes.html | 2 +- documentation/user_manual/manual/vrp/vrp.html | 2 +- 239 files changed, 12191 insertions(+), 794 deletions(-) create mode 100644 documentation/tutorials/cplusplus/chap10/A-n32-k5.vrp create mode 100644 documentation/tutorials/cplusplus/chap10/Makefile create mode 100644 documentation/tutorials/cplusplus/chap10/check_cvrp_solution.cc create mode 100644 documentation/tutorials/cplusplus/chap10/check_vrp_solution.cc create mode 100644 documentation/tutorials/cplusplus/chap10/cvrp_basic.cc create mode 100644 documentation/tutorials/cplusplus/chap10/cvrp_data.h create mode 100644 documentation/tutorials/cplusplus/chap10/cvrp_data_generator.cc create mode 100644 documentation/tutorials/cplusplus/chap10/cvrp_data_generator.h create mode 100644 documentation/tutorials/cplusplus/chap10/cvrp_epix_data.h create mode 100644 documentation/tutorials/cplusplus/chap10/cvrp_solution.h create mode 100644 documentation/tutorials/cplusplus/chap10/cvrp_solution_to_epix.cc create mode 100644 documentation/tutorials/cplusplus/chap10/rl_auxiliary_graph.cc create mode 100644 documentation/tutorials/cplusplus/chap10/vrp.cc create mode 100644 documentation/tutorials/cplusplus/chap10/vrp_partial_routes.cc create mode 100644 documentation/tutorials/cplusplus/chap10/vrp_solution_to_epix.cc create mode 100644 documentation/tutorials/cplusplus/chap7/Makefile create mode 100644 documentation/tutorials/cplusplus/chap7/abz9 create mode 100644 documentation/tutorials/cplusplus/chap7/dummy_lns.cc create mode 100644 documentation/tutorials/cplusplus/chap7/golomb_default_search1.cc create mode 100644 documentation/tutorials/cplusplus/chap7/golomb_default_search2.cc create mode 100644 documentation/tutorials/cplusplus/chap7/jobshop.h create mode 100644 documentation/tutorials/cplusplus/chap7/jobshop_heuristic.cc create mode 100644 documentation/tutorials/cplusplus/chap7/jobshop_lns.cc create mode 100644 documentation/tutorials/cplusplus/chap7/jobshop_lns.h create mode 100644 documentation/tutorials/cplusplus/chap7/jobshop_ls.h create mode 100644 documentation/tutorials/cplusplus/chap7/jobshop_sa1.cc create mode 100644 documentation/tutorials/cplusplus/chap7/jobshop_sa2.cc create mode 100644 documentation/tutorials/cplusplus/chap7/jobshop_ts1.cc create mode 100644 documentation/tutorials/cplusplus/chap7/jobshop_ts2.cc create mode 100644 documentation/tutorials/cplusplus/chap7/limits.h create mode 100644 documentation/tutorials/cplusplus/common/IO_helpers.h create mode 100644 documentation/tutorials/cplusplus/common/common_flags.h create mode 100644 documentation/tutorials/cplusplus/common/constants.h create mode 100644 documentation/tutorials/cplusplus/common/limits.h create mode 100644 documentation/tutorials/cplusplus/common/random.h create mode 100644 documentation/tutorials/cplusplus/routing_common/routing_common.h create mode 100644 documentation/tutorials/cplusplus/routing_common/routing_common_flags.h create mode 100644 documentation/tutorials/cplusplus/routing_common/routing_data.h create mode 100644 documentation/tutorials/cplusplus/routing_common/routing_data_generator.h create mode 100644 documentation/tutorials/cplusplus/routing_common/routing_distance.h create mode 100644 documentation/tutorials/cplusplus/routing_common/routing_epix_helper.h create mode 100644 documentation/tutorials/cplusplus/routing_common/routing_random.h create mode 100644 documentation/tutorials/cplusplus/routing_common/routing_solution.h create mode 100644 documentation/tutorials/cplusplus/routing_common/tsplib.h create mode 100644 documentation/tutorials/cplusplus/routing_common/tsplib_reader.h create mode 100644 documentation/user_manual/_images/LNS_basic_pseudo_code.png create mode 100644 documentation/user_manual/_images/SA_pseudo_code.png create mode 100644 documentation/user_manual/_images/local_search_basic1.png create mode 100644 documentation/user_manual/_images/math/051510e1a32cb0a43379c30067d242d3e8ca6839.png create mode 100644 documentation/user_manual/_images/math/07bfbd937b9f2a0291991e5207aaf77a2fc3188d.png create mode 100644 documentation/user_manual/_images/math/0b4eaa0da05ee92eb6ddc7199719650597a7e8b3.png create mode 100644 documentation/user_manual/_images/math/169d51d87e0861f4c5a32cab44a96601bb80f190.png create mode 100644 documentation/user_manual/_images/math/16b8fbd46ad8a2a3c2b814e9218bd7b74c10e0db.png create mode 100644 documentation/user_manual/_images/math/1893277d05fadf0c14f8c26c3cbf3afb3fd7f680.png create mode 100644 documentation/user_manual/_images/math/1e8aa82efdd03747feb4ddd348d0625e67eff5ea.png create mode 100644 documentation/user_manual/_images/math/2110829d67b6b936efdbeea243f679c4ec3c8db6.png create mode 100644 documentation/user_manual/_images/math/21c81928c3120c88c86d6921521b0c7b30ba4fcc.png create mode 100644 documentation/user_manual/_images/math/2b2c91b7bf7514e1e910f722cd4138d1a9980ede.png create mode 100644 documentation/user_manual/_images/math/3010515ceeb0b173969e370cc46acb837b622b17.png create mode 100644 documentation/user_manual/_images/math/30cc1ce4f5e733f6bcd0099f5a944f316e143c51.png create mode 100644 documentation/user_manual/_images/math/311cabda3a9b09f0dde217303ca9d1cd9201dcf6.png create mode 100644 documentation/user_manual/_images/math/34f8872436b1107ed9d7826131afcac9e5dc3311.png create mode 100644 documentation/user_manual/_images/math/35bc7b3a267c87204236fefbe603eb79e49f6f3d.png create mode 100644 documentation/user_manual/_images/math/38a212babe1948e16074ec200305e87a5a0fcab7.png create mode 100644 documentation/user_manual/_images/math/3aa388683d02039ebaedec336f4018ed9c3f440a.png create mode 100644 documentation/user_manual/_images/math/4270dc00d93b45bbdac94c3f2c6ffa76a63f4353.png create mode 100644 documentation/user_manual/_images/math/42dc65f10d4ad510c21139adc18e2b2f5f769591.png create mode 100644 documentation/user_manual/_images/math/4fe72f28a9fe0a456173c9287d7a195c663e171b.png create mode 100644 documentation/user_manual/_images/math/599e91f1fa979b0c78fcc1782e59bffd2a5cbd6e.png create mode 100644 documentation/user_manual/_images/math/659a6a2610357101d77dbae8eb9bf0b3100c08fd.png create mode 100644 documentation/user_manual/_images/math/67162eb98dd00c45b4508034c18a16d1a1c1b005.png create mode 100644 documentation/user_manual/_images/math/67efd4ce7260acbbb9e4e190abfff4eb58ee8b28.png create mode 100644 documentation/user_manual/_images/math/681f4a3906d0bcfa952c51fd13df36999c67807a.png create mode 100644 documentation/user_manual/_images/math/7aaf5e311e32ebc237f7beacbfddb6441b567293.png create mode 100644 documentation/user_manual/_images/math/7cef88777c45e887e58044375905b793db021201.png create mode 100644 documentation/user_manual/_images/math/82a9f44bc51ca08a5320765c38c0bc2f888e5c03.png create mode 100644 documentation/user_manual/_images/math/84039f9efc024403b48c35fbad53021426a96dd4.png create mode 100644 documentation/user_manual/_images/math/841fb80920b214cd9f8ccb4af360b4891402c0e3.png create mode 100644 documentation/user_manual/_images/math/8772e879f0b05d2802c5deeb8a7dbd466fd5db8e.png create mode 100644 documentation/user_manual/_images/math/896dbdbff63bda725faec7bfeec618885320dbb7.png create mode 100644 documentation/user_manual/_images/math/89bd49c9ca0568bc2d7eb7de1c6dbbe837fbe77e.png create mode 100644 documentation/user_manual/_images/math/91c8f1f7113f6cef68a960258d62da05fa5a431e.png create mode 100644 documentation/user_manual/_images/math/9685ef5682ae63b4fbcd9828e8fef32fda75744f.png create mode 100644 documentation/user_manual/_images/math/98a96d47fc75f521c80193b9c030c717f8be9c67.png create mode 100644 documentation/user_manual/_images/math/98f25bcaf968c85f66afd01b11fe4feb67c125da.png create mode 100644 documentation/user_manual/_images/math/995f6f309a39d7624db63c54efded0c203d0a3bc.png create mode 100644 documentation/user_manual/_images/math/9a741b899afcd7fe00da0afec6c45b63a0028329.png create mode 100644 documentation/user_manual/_images/math/9e925497ae709bf610fdb1956943a7af0cc7918d.png create mode 100644 documentation/user_manual/_images/math/a83153ca777e70a3b7f675b3a8f4fa620aab16c6.png create mode 100644 documentation/user_manual/_images/math/a91c19d517b8ce8720b4bdc36551378c4ceb8d54.png create mode 100644 documentation/user_manual/_images/math/abdb303f8421b8383c8ac4ac559259c60ac85205.png create mode 100644 documentation/user_manual/_images/math/aefef4246fe548d6fcb1f8734af167930e5e5d4b.png create mode 100644 documentation/user_manual/_images/math/af0b3b32d353682f471e1b2cc26eaa4edc29b19a.png create mode 100644 documentation/user_manual/_images/math/b0d068bd0fdbb1e76139d9d423ce5800e6812949.png create mode 100644 documentation/user_manual/_images/math/b7c7c996f057bd3698f1b0cff82bbc3a1714ba3b.png create mode 100644 documentation/user_manual/_images/math/be3f6c55e39366ad7956d6cd4d84583e894b661a.png create mode 100644 documentation/user_manual/_images/math/ccca2c46f98ac4992c543dcd5aab7c897d2c9f8a.png create mode 100644 documentation/user_manual/_images/math/ce4588fd900d02afcbd260bc07f54cce49a7dc4a.png create mode 100644 documentation/user_manual/_images/math/d7bd61285bfa72545a81de040c02c01d2b6994a2.png create mode 100644 documentation/user_manual/_images/math/da8115215e9ea837e8fd35830c27a3a6b53823c8.png create mode 100644 documentation/user_manual/_images/math/da8a2d6368b051ed1915a58813bec0cb785c0577.png create mode 100644 documentation/user_manual/_images/math/e0d1ad74677466d8ab0a46eb34e1e710d5be06d6.png create mode 100644 documentation/user_manual/_images/math/e2644f5aef88a86a47a52798705d58eb6ce1c6e0.png create mode 100644 documentation/user_manual/_images/math/e53c2367082d26bd66ac95de4aa8118daf060a6a.png create mode 100644 documentation/user_manual/_images/math/ed9c73a2e6e1b46d6d98a076aaf2cd9a0da3ab2a.png create mode 100644 documentation/user_manual/_images/math/efd9ce9060e61f4b7c20694ba20f8430ab71cf51.png create mode 100644 documentation/user_manual/_images/math/efe54ded8ebbe64b98214f34ab44d7f3ad54d337.png create mode 100644 documentation/user_manual/_images/math/f62f1a28628c14e247dd11ec0b00d9d8b2361acb.png create mode 100644 documentation/user_manual/_images/math/f792693f4651f9955977b4130f938061c901d2e3.png create mode 100644 documentation/user_manual/_images/math/fa12ea8eec45b790e08bc47803bab83a155b7d52.png create mode 100644 documentation/user_manual/_images/math/fb13547684600daec60944e8aadad3c60063047f.png create mode 100644 documentation/user_manual/_images/math/fc4e531e3d2a6da0bcd0bc3940d5925f2e48ced5.png create mode 100644 documentation/user_manual/_images/metaheuristics_hierarchy.png create mode 100644 documentation/user_manual/_images/sequence_lns.png create mode 100644 documentation/user_manual/manual/custom_constraints/all_different_except_zero_definition.html create mode 100644 documentation/user_manual/manual/custom_constraints/all_different_except_zero_model.html rename documentation/user_manual/manual/custom_constraints/{alldifferent.html => alldifferent_constraint.html} (75%) rename documentation/user_manual/manual/custom_constraints/{dynamic_improvements.html => alldifferent_except_zero_constraint.html} (74%) create mode 100644 documentation/user_manual/manual/custom_constraints/basic_constraint_example.html rename documentation/user_manual/manual/{metaheuristics/VNS.html => modeling_tricks/dynamic_improvements.html} (69%) create mode 100644 documentation/user_manual/manual/modeling_tricks/local_search_tricks.html create mode 100644 documentation/user_manual/manual/modeling_tricks/parallelizing.html create mode 100644 documentation/user_manual/manual/modeling_tricks/variables.html diff --git a/documentation/documentation_hub.html b/documentation/documentation_hub.html index fc9918b588..74c2b7b85b 100644 --- a/documentation/documentation_hub.html +++ b/documentation/documentation_hub.html @@ -193,8 +193,8 @@ The following percentages show you the completion status of the manual. Note tha Chap7: Meta-heuristics
-
- 5% +
+ 100%
@@ -203,8 +203,8 @@ The following percentages show you the completion status of the manual. Note tha Chap8: Custom constraints
-
- 1% +
+ 31%
@@ -235,8 +235,8 @@ The following percentages show you the completion status of the manual. Note tha Chap11: Utilities
-
- 31% +
+ 56%
@@ -344,19 +344,21 @@ gflag:

Last but not least, you might wonder why we don't use (or rather minimize the use of) streams in our examples. This is an internal requirement.

+ +

You can download all files at once here.

+

Files

-You can download all files at once here. - +

- + - + @@ -371,7 +373,7 @@ You can download all files at once chap6 @@ -392,10 +394,10 @@ You can download all files at once NOT YET +
Chap2:First steps with or-tools: cryptarithmetic puzzlesFirst steps with or-tools: Cryptarithmetic Puzzles chap2
Chap3:Using objectives in constraint programming: the Golomb ruler problemUsing objectives in constraint programming: the Golomb Ruler Problem chap3
NOT TESTED
- +


Python tutorials

diff --git a/documentation/tutorials/cplusplus/chap10/A-n32-k5.vrp b/documentation/tutorials/cplusplus/chap10/A-n32-k5.vrp new file mode 100644 index 0000000000..9cb5cd540b --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/A-n32-k5.vrp @@ -0,0 +1,76 @@ +NAME : A-n32-k5 +COMMENT : (Augerat et al, Min no of trucks: 5, Optimal value: 784) +TYPE : CVRP +DIMENSION : 32 +EDGE_WEIGHT_TYPE : EUC_2D +CAPACITY : 100 +NODE_COORD_SECTION + 1 82 76 + 2 96 44 + 3 50 5 + 4 49 8 + 5 13 7 + 6 29 89 + 7 58 30 + 8 84 39 + 9 14 24 + 10 2 39 + 11 3 82 + 12 5 10 + 13 98 52 + 14 84 25 + 15 61 59 + 16 1 65 + 17 88 51 + 18 91 2 + 19 19 32 + 20 93 3 + 21 50 93 + 22 98 14 + 23 5 42 + 24 42 9 + 25 61 62 + 26 9 97 + 27 80 55 + 28 57 69 + 29 23 15 + 30 20 70 + 31 85 60 + 32 98 5 +DEMAND_SECTION +1 0 +2 19 +3 21 +4 6 +5 19 +6 7 +7 12 +8 16 +9 6 +10 16 +11 8 +12 14 +13 21 +14 16 +15 3 +16 22 +17 18 +18 19 +19 1 +20 24 +21 8 +22 12 +23 4 +24 8 +25 24 +26 24 +27 2 +28 20 +29 15 +30 2 +31 14 +32 9 +DEPOT_SECTION + 1 + -1 +EOF diff --git a/documentation/tutorials/cplusplus/chap10/Makefile b/documentation/tutorials/cplusplus/chap10/Makefile new file mode 100644 index 0000000000..e427797736 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/Makefile @@ -0,0 +1,84 @@ +OR_TOOLS_TOP= +OR_TOOLS_SOURCES=$(OR_TOOLS_TOP)/src + +TUTORIAL= + +SOURCES= vrp_partial_routes.cc rl_auxiliary_graph.cc cvrp_data_generator.cc check_vrp_solution.cc check_cvrp_solution.cc \ + cvrp_solution_to_epix.cc vrp.cc vrp_solution_to_epix.cc cvrp_basic.cc + +OBJECTS=$(SOURCES:.cc=.$O) + +EXE=$(SOURCES:.cc=) + +include $(OR_TOOLS_TOP)/Makefile + +.PHONY: all tutorials local_clean + +tutorials: $(EXE) + +vrp_partial_routes.o: vrp_partial_routes.cc $(OR_TOOLS_SOURCES)/constraint_solver/routing.h + $(CCC) $(CFLAGS) -c vrp_partial_routes.cc -o vrp_partial_routes.o + +vrp_partial_routes: $(ROUTING_DEPS) vrp_partial_routes.o + $(CCC) $(CFLAGS) vrp_partial_routes.o $(DYNAMIC_ROUTING_LNK) $(DYNAMIC_LD_FLAGS) -o vrp_partial_routes + +rl_auxiliary_graph.o: rl_auxiliary_graph.cc $(OR_TOOLS_SOURCES)/constraint_solver/routing.h + $(CCC) $(CFLAGS) -c rl_auxiliary_graph.cc -o rl_auxiliary_graph.o + +rl_auxiliary_graph: $(ROUTING_DEPS) rl_auxiliary_graph.o + $(CCC) $(CFLAGS) rl_auxiliary_graph.o $(DYNAMIC_ROUTING_LNK) $(DYNAMIC_LD_FLAGS) -o rl_auxiliary_graph + +cvrp_data_generator.o: cvrp_data_generator.cc $(OR_TOOLS_SOURCES)/constraint_solver/routing.h \ + $(TUTORIAL)/routing_common/routing_common.h $(TUTORIAL)/routing_common/routing_data_generator.h cvrp_data_generator.h + $(CCC) $(CFLAGS) -I $(TUTORIAL) -c cvrp_data_generator.cc -o cvrp_data_generator.o + +cvrp_data_generator: $(ROUTING_DEPS) cvrp_data_generator.o + $(CCC) $(CFLAGS) cvrp_data_generator.o $(DYNAMIC_ROUTING_LNK) $(DYNAMIC_LD_FLAGS) -o cvrp_data_generator + +check_vrp_solution.o: check_vrp_solution.cc $(OR_TOOLS_SOURCES)/constraint_solver/routing.h \ + $(TUTORIAL)/routing_common/routing_common.h $(TUTORIAL)/routing_common/routing_data_generator.h cvrp_solution.h + $(CCC) $(CFLAGS) -I $(TUTORIAL) -c check_vrp_solution.cc -o check_vrp_solution.o + +check_vrp_solution: $(ROUTING_DEPS) check_vrp_solution.o + $(CCC) $(CFLAGS) check_vrp_solution.o $(DYNAMIC_ROUTING_LNK) $(DYNAMIC_LD_FLAGS) -o check_vrp_solution + +check_cvrp_solution.o: check_cvrp_solution.cc $(OR_TOOLS_SOURCES)/constraint_solver/routing.h \ + $(TUTORIAL)/routing_common/routing_common.h $(TUTORIAL)/routing_common/routing_data_generator.h cvrp_solution.h + $(CCC) $(CFLAGS) -I $(TUTORIAL) -c check_cvrp_solution.cc -o check_cvrp_solution.o + +check_cvrp_solution: $(ROUTING_DEPS) check_cvrp_solution.o + $(CCC) $(CFLAGS) check_cvrp_solution.o $(DYNAMIC_ROUTING_LNK) $(DYNAMIC_LD_FLAGS) -o check_cvrp_solution + +cvrp_solution_to_epix.o: cvrp_solution_to_epix.cc $(TUTORIAL)/routing_common/routing_common.h $(TUTORIAL)/routing_common/routing_data.h \ + $(TUTORIAL)/routing_common/routing_solution.h $(TUTORIAL)/routing_common/routing_epix_helper.h cvrp_data.h cvrp_solution.h \ + cvrp_epix_data.h + $(CCC) $(CFLAGS) -I $(TUTORIAL) -c cvrp_solution_to_epix.cc -o cvrp_solution_to_epix.o + +cvrp_solution_to_epix: $(ROUTING_DEPS) cvrp_solution_to_epix.o + $(CCC) $(CFLAGS) cvrp_solution_to_epix.o $(DYNAMIC_ROUTING_LNK) $(DYNAMIC_LD_FLAGS) -o cvrp_solution_to_epix + +vrp.o: vrp.cc $(TUTORIAL)/routing_common/routing_common.h $(TUTORIAL)/routing_common/routing_data.h \ + $(TUTORIAL)/routing_common/routing_solution.h $(TUTORIAL)/routing_common/routing_epix_helper.h cvrp_data.h cvrp_solution.h + $(CCC) $(CFLAGS) -I $(TUTORIAL) -c vrp.cc -o vrp.o + +vrp: $(ROUTING_DEPS) vrp.o + $(CCC) $(CFLAGS) vrp.o $(DYNAMIC_ROUTING_LNK) $(DYNAMIC_LD_FLAGS) -o vrp + +vrp_solution_to_epix.o: vrp_solution_to_epix.cc $(TUTORIAL)/routing_common/routing_common.h $(TUTORIAL)/routing_common/routing_data.h \ + $(TUTORIAL)/routing_common/routing_solution.h $(TUTORIAL)/routing_common/routing_epix_helper.h cvrp_data.h cvrp_solution.h \ + cvrp_epix_data.h + $(CCC) $(CFLAGS) -I $(TUTORIAL) -c vrp_solution_to_epix.cc -o vrp_solution_to_epix.o + +vrp_solution_to_epix: $(ROUTING_DEPS) vrp_solution_to_epix.o + $(CCC) $(CFLAGS) vrp_solution_to_epix.o $(DYNAMIC_ROUTING_LNK) $(DYNAMIC_LD_FLAGS) -o vrp_solution_to_epix + +cvrp_basic.o: cvrp_basic.cc $(OR_TOOLS_SOURCES)/constraint_solver/routing.h \ + $(TUTORIAL)/routing_common/tsplib_reader.h + $(CCC) $(CFLAGS) -I $(TUTORIAL) -c cvrp_basic.cc -o cvrp_basic.o + +cvrp_basic: $(ROUTING_DEPS) cvrp_basic.o + $(CCC) $(CFLAGS) cvrp_basic.o $(DYNAMIC_ROUTING_LNK) $(DYNAMIC_LD_FLAGS) -o cvrp_basic + +local_clean: + rm $(OBJECTS) $(EXE) + diff --git a/documentation/tutorials/cplusplus/chap10/check_cvrp_solution.cc b/documentation/tutorials/cplusplus/chap10/check_cvrp_solution.cc new file mode 100644 index 0000000000..81d49995c3 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/check_cvrp_solution.cc @@ -0,0 +1,59 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Simple program to test a CVRP solution. +#include + +#include "base/commandlineflags.h" + +#include "cvrp_data.h" +#include "cvrp_solution.h" +#include "routing_common/tsplib_reader.h" + +//DECLARE_int32(width_size); + +//DEFINE_string(instance_file, "", "TSPLIB instance file."); +//DEFINE_string(solution_file, "", "CVRP solution file."); + +//DEFINE_string(distance_file, "", "Matrix distance file."); + +int main(int argc, char **argv) { + std::string usage("Checks the feasibility of a CVRP solution.\n" + "See Google or-tools tutorials\n" + "Sample usage:\n\n"); + usage += argv[0]; + usage += " -instance_file= -solution_file=<(C)VRP solution>\n"; + + google::SetUsageMessage(usage); + google::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_instance_file != "" && FLAGS_solution_file != "") { + operations_research::TSPLIBReader tsp_data_reader(FLAGS_instance_file); + operations_research::CVRPData cvrp_data(tsp_data_reader); + operations_research::CVRPSolution cvrp_sol(cvrp_data, FLAGS_solution_file); + if (FLAGS_distance_file != "") { + cvrp_data.WriteDistanceMatrix(FLAGS_distance_file); + } + if (cvrp_sol.IsFeasibleSolution()) { + LG << "Solution is feasible!"; + LG << "Obj value = " << cvrp_sol.ComputeObjectiveValue(); + } else { + LG << "Solution is NOT feasible..."; + } + } else { + std::cout << google::ProgramUsage(); + exit(-1); + } + return 0; +} \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/check_vrp_solution.cc b/documentation/tutorials/cplusplus/chap10/check_vrp_solution.cc new file mode 100644 index 0000000000..6a87680409 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/check_vrp_solution.cc @@ -0,0 +1,62 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Simple program to test a VRP solution. +#include + +#include "base/commandlineflags.h" + +#include "cvrp_data.h" +#include "cvrp_solution.h" +#include "routing_common/tsplib_reader.h" + +DECLARE_int32(width_size); + +//DEFINE_string(instance_file, "", "TSPLIB instance file."); +//DEFINE_string(solution_file, "", "(C)VRP solution file."); + +//DEFINE_string(distance_file, "", "Matrix distance file."); + +int main(int argc, char **argv) { + std::string usage("Checks the feasibility of a VRP solution.\n" + "See Google or-tools tutorials\n" + "Sample usage:\n\n"); + usage += argv[0]; + usage += " -instance_file= -solution_file=<(C)VRP solution>\n"; + + google::SetUsageMessage(usage); + google::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_instance_file != "" && FLAGS_solution_file != "") { + operations_research::TSPLIBReader tsp_data_reader(FLAGS_instance_file); + operations_research::CVRPData cvrp_data(tsp_data_reader); + operations_research::CVRPSolution cvrp_sol(cvrp_data, FLAGS_solution_file); + if (FLAGS_distance_file != "") { + cvrp_data.WriteDistanceMatrix(FLAGS_distance_file); + } + if (cvrp_sol.IsSolution()) { + LG << "Solution is feasible!"; + LG << "Obj value = " << cvrp_sol.ComputeObjectiveValue(); + if (cvrp_sol.IsFeasibleSolution()) { + LG << "Solution if even CVRP feasible!!!"; + } + } else { + LG << "Solution is NOT feasible..."; + } + } else { + std::cout << google::ProgramUsage(); + exit(-1); + } + return 0; +} \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/cvrp_basic.cc b/documentation/tutorials/cplusplus/chap10/cvrp_basic.cc new file mode 100644 index 0000000000..7df94d2c76 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/cvrp_basic.cc @@ -0,0 +1,154 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Simple program to solve the CVRP with Local Search in or-tools. + +#include +#include + +#include "base/commandlineflags.h" +#include "constraint_solver/routing.h" +#include "base/join.h" +#include "base/timer.h" + +#include "constraint_solver/routing.h" + +#include "common/limits.h" + +#include "cvrp_data.h" +#include "cvrp_solution.h" +#include "routing_common/tsplib_reader.h" + +DEFINE_int32(depot, 1, "The starting node of the tour."); +//DEFINE_string(instance_file, "", "Input file with TSPLIB data."); +DEFINE_string(initial_solution_file, "", "Input file with a valid feasible solution."); +//DEFINE_string(solution_file, "", "Output file with generated solution in (C)VRP format."); +//DECLARE_int32(number_vehicles);// Upper bound on the number of vehicles. +DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms. 0 means no limit."); +DEFINE_int32(no_solution_improvement_limit, 200, "Number of allowed solutions without improving the objective value."); + +namespace operations_research { + +void CVRPBasicSolver (const CVRPData & data) { + + const int size = data.Size(); + const int64 capacity = data.Capacity(); + + CHECK_GT(FLAGS_number_vehicles, 0) << "We need at least one vehicle!"; + // Little check to see if we have enough vehicles + CHECK_GT(capacity, data.TotalDemand()/FLAGS_number_vehicles) << "No enough vehicles to cover all the demands"; + RoutingModel routing(size, FLAGS_number_vehicles); + routing.SetCost(NewPermanentCallback(&data, &CVRPData::Distance)); + + if (FLAGS_distance_file != "") { + data.WriteDistanceMatrix(FLAGS_distance_file); + } + + // Disabling Large Neighborhood Search, comment out to activate it. + //routing.SetCommandLineOption("routing_no_lns", "true"); + + if (FLAGS_time_limit_in_ms > 0) { + routing.UpdateTimeLimit(FLAGS_time_limit_in_ms); + } + + // Setting depot + CHECK_GT(FLAGS_depot, 0) << " Because we use the" << " TSPLIB convention, the depot id must be > 0"; + RoutingModel::NodeIndex depot(FLAGS_depot -1); + routing.SetDepot(depot); + + // add capacities constraints + std::vector demands(size); + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < size; ++i) { + demands[i.value()] = data.Demand(i); + } + routing.AddVectorDimension(&demands[0], capacity, true, "Demand"); + + routing.CloseModel(); + + // Use initial solution if provided + Assignment * initial_sol = NULL;// = routing.solver()->MakeAssignment(); + if (FLAGS_initial_solution_file != "") { + initial_sol = routing.solver()->MakeAssignment();//needed by RoutesToAssignment but actually doesn't do much... to detail + CVRPSolution cvrp_init_sol(data, FLAGS_initial_solution_file); + + routing.RoutesToAssignment(cvrp_init_sol.Routes(), true, true, initial_sol);//as the solution is complete, we don't care about true true + + if (routing.solver()->CheckAssignment(initial_sol)) {// just in case and to fill the complementary variables + + CVRPSolution temp_sol(data, &routing, initial_sol); + LG << "Initial solution provided is feasible with obj = " << temp_sol.ComputeObjectiveValue(); + } else { + LG << "Initial solution provided is NOT feasible... exit!"; + return; + } + } + +NoImprovementLimit * const no_improvement_limit = MakeNoImprovementLimit(routing.solver(), routing.CostVar(), FLAGS_no_solution_improvement_limit, true); + //routing.AddSearchMonitor(no_improvement_limit); + +SearchLimit * const ctrl_break = MakeCatchCTRLBreakLimit(routing.solver()); +routing.AddSearchMonitor(ctrl_break); + //routing.solver()-> + + routing.CloseModel(); + + const Assignment* solution = routing.Solve(initial_sol);// if initial_sol == NULL, solves from scratch + + // INSPECT SOLUTION + if (solution != NULL) { + CVRPSolution cvrp_sol(data, &routing, solution); + cvrp_sol.SetName(StrCat("Solution for instance ", data.Name(), " computed by cvrp.cc")); + // test solution + if (!cvrp_sol.IsFeasibleSolution()) { + LOG(ERROR) << "Solution is NOT feasible!"; + } else { + LG << "Solution is feasible and has an obj value of " << cvrp_sol.ComputeObjectiveValue(); + // SAVE SOLUTION IN CVRP FORMAT + if (FLAGS_solution_file != "") { + cvrp_sol.Write(FLAGS_solution_file); + } else { + cvrp_sol.Print(std::cout); + } + } + + } else { + LG << "No solution found."; + } + +} // void VRPSolver (CVRPData & data) + + +} // namespace operations_research + + +int main(int argc, char **argv) { + std::string usage("Computes a simple CVRP.\n" + "See Google or-tools tutorials\n" + "Sample usage:\n\n"); + usage += argv[0]; + usage += " -instance_file=\n"; + + google::SetUsageMessage(usage); + google::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_instance_file == "") { + std::cout << google::ProgramUsage(); + exit(-1); + } + operations_research::TSPLIBReader tsplib_reader(FLAGS_instance_file); + operations_research::CVRPData cvrp_data(tsplib_reader); + operations_research::CVRPBasicSolver(cvrp_data); + + return 0; +} // main diff --git a/documentation/tutorials/cplusplus/chap10/cvrp_data.h b/documentation/tutorials/cplusplus/chap10/cvrp_data.h new file mode 100644 index 0000000000..effff8915f --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/cvrp_data.h @@ -0,0 +1,202 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common base for (c)vrp data (instance) classes. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_VRP_DATA_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_VRP_DATA_H + +#include +#include + +#include "base/bitmap.h" +#include "base/logging.h" +#include "base/file.h" +#include "base/split.h" +#include "base/filelinereader.h" +#include "base/join.h" +#include "base/strtoint.h" + +#include "constraint_solver/routing.h" + +#include "routing_common/routing_common.h" +#include "routing_common/routing_data.h" +#include "routing_common/tsplib_reader.h" +#include "routing_common/tsplib.h" +#include "cvrp_data_generator.h" + +DECLARE_int32(width_size); + +namespace operations_research { + +class CVRPData : public RoutingData { +public: + explicit CVRPData(CVRPDataGenerator & g) : RoutingData(g), depot_(g.Depot()), capacity_(g.Capacity()), + demands_(Size()), + node_coord_type_(g.NodeCoordinateType()), + display_data_type_(g.DisplayDataType()), + two_dimension_(g.HasDimensionTwo()), + total_demand_(0){ + + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + demands_[i.value()] = g.Demand(i); + total_demand_ += g.Demand(i); + } + } + explicit CVRPData(const TSPLIBReader & reader) : + RoutingData(reader), + depot_(reader.Depot()), + capacity_(reader.Capacity()), + demands_(Size()), + node_coord_type_(reader.NodeCoordinateType()), + display_data_type_(reader.DisplayDataType()), + two_dimension_(reader.HasDimensionTwo()), + total_demand_(0) { + CHECK(reader.TSPLIBType() == CVRP); + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + demands_[i.value()] = reader.Demand(i); + total_demand_ += reader.Demand(i); + } + if (node_coord_type_ == TWOD_COORDS || node_coord_type_ == THREED_COORDS) { + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + Coordinate(i) = reader.Coordinate(i); + } + SetHasCoordinates(); + } + if (display_data_type_ == TWOD_DISPLAY) { + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + DisplayCoordinate(i) = reader.DisplayCoordinate(i); + } + SetHasDisplayCoordinates(); + } + SetRoutingDataInstanciated(); + } + + void SetDepot(RoutingModel::NodeIndex d) { + CheckNodeIsValid(d); + depot_ = d; + } + + RoutingModel::NodeIndex Depot() const { + return depot_; + } + + void SetDemand(const RoutingModel::NodeIndex i, int64 demand) { + CheckNodeIsValid(i); + demands_[i.value()] = demand; + } + + int64 Demand(const RoutingModel::NodeIndex i) const { + CheckNodeIsValid(i); + return demands_[i.value()]; + } + +int64 TotalDemand() const { + return total_demand_; +} + + void PrintTSPLIBInstance(std::ostream & out) const; + + void WriteTSPLIBInstance(const std::string & filename) const { + WriteToFile writer(this, filename); + writer.SetMember(&operations_research::CVRPData::PrintTSPLIBInstance); + writer.Run(); + } + +void SetCapacity(int64 capacity) { + capacity_ = capacity; +} + +int64 Capacity() const { + return capacity_; +} + + // Helper function + int64& SetDistance(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j) { + return distances_.Cost(i, j); + } + +private: + + RoutingModel::NodeIndex depot_; + std::vector demands_; + int64 total_demand_; + TSPLIB_NODE_COORD_TYPE_TYPES_enum node_coord_type_; + TSPLIB_DISPLAY_DATA_TYPE_TYPES_enum display_data_type_; + bool two_dimension_; + int64 capacity_; + +}; + +void CVRPData::PrintTSPLIBInstance(std::ostream& out) const { + out << TSPLIB_STATES_KEYWORDS[NAME] << " : " << Name() << std::endl; + out << TSPLIB_STATES_KEYWORDS[COMMENT] << " : " << Comment() << std::endl; + out << TSPLIB_STATES_KEYWORDS[TYPE] << " : CVRP" << std::endl; + out << TSPLIB_STATES_KEYWORDS[DIMENSION] << " : " << Size() << std::endl; + out << TSPLIB_STATES_KEYWORDS[EDGE_WEIGHT_TYPE] << " : " << "EXPLICIT" << std::endl; + out << TSPLIB_STATES_KEYWORDS[EDGE_WEIGHT_FORMAT] << " : " << "FULL_MATRIX" << std::endl; + if (HasCoordinates()) { + out << TSPLIB_STATES_KEYWORDS[NODE_COORD_TYPE] << " : " << TSPLIB_NODE_COORD_TYPE_TYPES_KEYWORDS[node_coord_type_] << std::endl; + } + if (HasDisplayCoordinates()) { + out << TSPLIB_STATES_KEYWORDS[DISPLAY_DATA_TYPE] << " : " << TSPLIB_DISPLAY_DATA_TYPE_TYPES_KEYWORDS[display_data_type_] << std::endl; + } + if (Depot() != RoutingModel::kFirstNode) { + out << TSPLIB_STATES_KEYWORDS[DEPOT_SECTION] << std::endl; + out << Depot().value() + 1 << std::endl; + out << kTSPLIBDelimiter << std::endl; + } + out << TSPLIB_STATES_KEYWORDS[EDGE_WEIGHT_SECTION] << std::endl; + distances_.Print(out); + if (HasCoordinates()) { + out << TSPLIB_STATES_KEYWORDS[NODE_COORD_SECTION] << std::endl; + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + out.width(FLAGS_width_size); + out << std::right << i.value() + 1; + out.width(FLAGS_width_size); + out << std::right << Coordinate(i).x; + out.width(FLAGS_width_size); + out << std::right << Coordinate(i).y; + if (!two_dimension_) { // 3D + out.width(FLAGS_width_size); + out << std::right << Coordinate(i).z; + } + out << std::endl; + } + } + if (HasDisplayCoordinates()) { + out << TSPLIB_STATES_KEYWORDS[DISPLAY_DATA_SECTION] << std::endl; + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + out.width(FLAGS_width_size); + out << std::right << i.value() + 1; + out.width(FLAGS_width_size + 4); + out << std::setprecision(2) << std::fixed << std::right << DisplayCoordinate(i).x; + out.width(FLAGS_width_size + 4); + out << std::right << DisplayCoordinate(i).y << std::endl; + } + } + out << TSPLIB_STATES_KEYWORDS[DEMAND_SECTION] << std::endl; + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + out.width(FLAGS_width_size); + out << std::right << i.value() + 1; + out.width(FLAGS_width_size); + out << std::right << Demand(i) << std::endl; + } + out << kTSPLIBEndFileDelimiter << std::endl; +} + +} // namespace operations_research + + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_VRP_DATA_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/cvrp_data_generator.cc b/documentation/tutorials/cplusplus/chap10/cvrp_data_generator.cc new file mode 100644 index 0000000000..bfa0a23790 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/cvrp_data_generator.cc @@ -0,0 +1,48 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Simple CVRP instance generator. + +#include "base/commandlineflags.h" + +#include "cvrp_data_generator.h" +#include "cvrp_data.h" + +DEFINE_int32(depot, 1, "Depot of the CVRP instance. Must be greater of equal to 1."); + +int main(int argc, char **argv) { + + google::ParseCommandLineFlags(&argc, &argv, true); + google::SetUsageMessage(operations_research::GeneratorUsage(argv[0], "CVRP")); + + if (FLAGS_instance_name != "" && FLAGS_instance_size > 2) { + operations_research::CVRPDataGenerator cvrp_data_generator(FLAGS_instance_name, FLAGS_instance_size); + CHECK_GE(FLAGS_depot, 1) << "Because we use the TSPLIB format, the depot must be greater or equal to 1."; + CHECK_LE(FLAGS_depot, FLAGS_instance_size) << "The depot must be in range 1-" << FLAGS_instance_size << "."; + cvrp_data_generator.SetDepot(operations_research::RoutingModel::NodeIndex(FLAGS_depot - 1)); + operations_research::CVRPData cvrp_data(cvrp_data_generator); + if (FLAGS_distance_file != "") { + cvrp_data.WriteDistanceMatrix(FLAGS_distance_file); + } + if (FLAGS_instance_file == "") { + cvrp_data.PrintTSPLIBInstance(std::cout); + } else { + cvrp_data.WriteTSPLIBInstance(FLAGS_instance_file); + } + } else { + std::cout << google::ProgramUsage(); + exit(-1); + } + return 0; +} \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/cvrp_data_generator.h b/documentation/tutorials/cplusplus/chap10/cvrp_data_generator.h new file mode 100644 index 0000000000..692a922868 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/cvrp_data_generator.h @@ -0,0 +1,145 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Very basic CVRPDataGenerator class. +// +// The demands are created by constructing a feasible solution. +// The capacity if FULLY used on each route. i.e. the sum of all demands +// along a route is equal to FLAGS_capacity. +// A node can have a zero capacity. If you want to force each node to +// have a capacity of at least 1, set FLAGS_allow_zero_capacity to false. +// +// We don't consider min and max capacities. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_CVRP_GENERATOR_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_CVRP_GENERATOR_H + +#include "base/commandlineflags.h" +#include "routing_common/routing_data_generator.h" +#include "routing_common/tsplib.h" + +DEFINE_int32(number_vehicles, 2, "Number of vehicles."); +DEFINE_int64(capacity, 100, "Capacity of all vehicles."); + +DEFINE_bool(allow_zero_capacity, true, "Allow node with zero capacity?"); + +namespace operations_research { + +class CVRPDataGenerator : public RoutingDataGenerator { +public: + CVRPDataGenerator(std::string instance_name, int32 size, std::string problem_name = "CVRP") : RoutingDataGenerator(problem_name, instance_name, size), + comment_("Generated by VRPDataGenerator."), capacity_corrector_(FLAGS_allow_zero_capacity ? 0 : 1), capacity_(FLAGS_capacity) { + CreateFeasibleSolution(); + } + + std::string Comment() const { + return comment_; + } + + void SetComment(const std::string comment) { + comment_ = comment; + } + + RoutingModel::NodeIndex Depot() const { + return depot_; + } + + void SetDepot(const RoutingModel::NodeIndex d) { + depot_ = d; + } + +int64 Capacity() const { + return capacity_; +} + + int64 Demand(const RoutingModel::NodeIndex i) const { + return demands_[i.value()]; + } + + TSPLIB_NODE_COORD_TYPE_TYPES_enum NodeCoordinateType() const { + return TWOD_COORDS; + } + + TSPLIB_DISPLAY_DATA_TYPE_TYPES_enum DisplayDataType() const { + return COORD_DISPLAY; + } + + bool HasDimensionTwo() const { + return true; + } +private: + void CreateFeasibleSolution() { + // first: shuffle nodes + std::vector nodes(Size()); + for (RoutingModel::NodeIndex i(0); i < Size(); ++i) { + nodes[i.value()] = i; + } + // 0 must be the depot + std::random_shuffle(nodes.begin() + 1, nodes.end(), randomizer_); + + // second: distribute nodes into routes of random length + sol_.resize(Size()); + int number_of_nodes = 0; + int total_number_of_used_nodes = 0; + for (int i = 0; i < FLAGS_number_vehicles; ++i) { + if (i == FLAGS_number_vehicles - 1) { // add the rest + number_of_nodes = Size() - 1 - total_number_of_used_nodes; + } else { // add random number of nodes + number_of_nodes = randomizer_.Uniform(Size() - 1 - total_number_of_used_nodes - FLAGS_number_vehicles + i) + 1; + } + sol_[i].resize(number_of_nodes); + for (int j = 0; j < number_of_nodes; ++j) { + sol_[i][j] = nodes[total_number_of_used_nodes + j + 1]; + std::cout << sol_[i][j] << " - "; + } + std::cout << std::endl; + total_number_of_used_nodes += number_of_nodes; + } + + // third: allocate the capacity for each route + demands_.resize(Size()); + demands_[0] = 0; + int64 total_capacity_used = 0; + int64 total_nodes_with_capacity = 0; + int64 capacity = 0; + for (int i = 0; i < FLAGS_number_vehicles; ++i) { + + number_of_nodes = sol_[i].size(); + total_capacity_used = 0; + for (int j = 0; j < number_of_nodes; ++j) { + if (j == number_of_nodes - 1) { // add the rest + ++total_nodes_with_capacity; + capacity = FLAGS_capacity - total_capacity_used; + } else { // add random capacity + ++total_nodes_with_capacity; + capacity = randomizer_.Uniform(FLAGS_capacity - total_capacity_used - number_of_nodes + j - capacity_corrector_ + - capacity_corrector_ * (Size() - total_nodes_with_capacity)) + capacity_corrector_; + } + demands_[sol_[i][j].value()] = capacity; + total_capacity_used += capacity; + } + } + } + + std::string comment_; + RoutingModel::NodeIndex depot_; + std::vector > sol_; + std::vector demands_; + const int64 capacity_; + const int capacity_corrector_; +}; + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_CVRP_GENERATOR_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/cvrp_epix_data.h b/documentation/tutorials/cplusplus/chap10/cvrp_epix_data.h new file mode 100644 index 0000000000..5360fe7bda --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/cvrp_epix_data.h @@ -0,0 +1,132 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common base to use the epix library to visualize +// CVRP data (instance) and solutions. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_CVRP_EPIX_DATA_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_CVRP_EPIX_DATA_H + +#include + +#include "routing_common/routing_common.h" +#include "cvrp_data.h" +#include "cvrp_solution.h" +#include "routing_common/routing_epix_helper.h" + +namespace operations_research { + +class CVRPEpixData { +public: + explicit CVRPEpixData(const CVRPData & data): data_(data) {} + void PrintInstance(std::ostream & out) const; + void WriteInstance(const std::string & filename) const; + void PrintSolution(std::ostream & out, const CVRPSolution & sol) const; + void WriteSolution(const std::string & filename, const CVRPSolution & sol) const; +private: + const CVRPData & data_; +}; + +void CVRPEpixData::PrintInstance(std::ostream& out) const { + CHECK(data_.HasCoordinates()); + PrintEpixBeginFile(out); + PrintEpixPreamble(out); + PrintEpixBoundingBox(out, data_.RawBoundingBox()); + + PrintEpixNewLine(out); + PrintEpixComment(out, "Points:"); + + for (RoutingModel::NodeIndex i(0); i < data_.Size(); ++i) { + Point p = data_.Coordinate(i); + PrintEpixPoint(out, p, i); + } + + PrintEpixBeginFigure(out); + PrintEpixDrawMultiplePoints(out, data_.Size()); + PrintEpixDepot(out, data_.Depot()); + + PrintEpixEndFigure(out); + + PrintEpixEndFile(out); +} + +void CVRPEpixData::WriteInstance(const std::string& filename) const { + WriteToFile writer(this, filename); + writer.SetMember(&operations_research::CVRPEpixData::PrintInstance); + writer.Run(); +} + +void CVRPEpixData::PrintSolution(std::ostream& out, const operations_research::CVRPSolution& sol) const { + CHECK(data_.HasCoordinates()); + PrintEpixBeginFile(out); + PrintEpixPreamble(out); + PrintEpixBoundingBox(out, data_.RawBoundingBox()); + + PrintEpixNewLine(out); + PrintEpixComment(out, "Points:"); + + for (RoutingModel::NodeIndex i(0); i < data_.Size(); ++i) { + Point p = data_.Coordinate(i); + PrintEpixPoint(out, p, i); + } + + + PrintEpixComment(out, "Edges:"); + + RoutingModel::NodeIndex from_node, to_node; + RoutingModel::NodeIndex depot = data_.Depot(); + int segment_nbr = 0; + + for (CVRPSolution::const_vehicle_iterator v_iter = sol.vehicle_begin(); v_iter != sol.vehicle_end(); ++v_iter) { + from_node = depot; + for (CVRPSolution::const_node_iterator n_iter = sol.node_begin(v_iter); n_iter != sol.node_end(v_iter); ++n_iter ) { + to_node = *n_iter; + PrintEpixSegment(out, segment_nbr, from_node, to_node); + from_node = to_node; + ++segment_nbr; + } + // Last arc + PrintEpixSegment(out, segment_nbr, from_node, depot); + ++segment_nbr; + } + + + PrintEpixNewLine(out); + + PrintEpixBeginFigure(out); + + +PrintEpixDrawMultipleSegments(out, segment_nbr); + + + PrintEpixRaw(out, " fill(White());"); + PrintEpixDrawMultiplePoints(out, data_.Size()); + PrintEpixDepot(out, data_.Depot()); + PrintEpixEndFigure(out); + + PrintEpixEndFile(out); +} + +void CVRPEpixData::WriteSolution(const std::string& filename, const operations_research::CVRPSolution& sol) const { + + WriteToFileP1 writer(this, filename); + writer.SetMember(&operations_research::CVRPEpixData::PrintSolution); + writer.Run(sol); + +} + + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_CVRP_EPIX_DATA_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/cvrp_solution.h b/documentation/tutorials/cplusplus/chap10/cvrp_solution.h new file mode 100644 index 0000000000..8683b777ae --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/cvrp_solution.h @@ -0,0 +1,281 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common base for TSPTW solutions. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_CVRP_SOLUTION_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_CVRP_SOLUTION_H + +#include + +#include "constraint_solver/routing.h" +#include "base/split.h" +#include "base/filelinereader.h" +#include "base/join.h" +#include "base/bitmap.h" + +#include "routing_common/routing_solution.h" +#include "cvrp_data.h" + +DEFINE_bool(numbering_solution_nodes_from_zero, true, "Number the nodes in the solution starting from 0?"); + +namespace operations_research { + + class CVRPSolution : public RoutingSolution { + public: + typedef std::vector >::iterator vehicle_iterator; + typedef std::vector >::const_iterator const_vehicle_iterator; + typedef std::vector::iterator node_iterator; + typedef std::vector::const_iterator const_node_iterator; + //explicit CVRPSolution(const CVRPData & data) : RoutingSolution(data), data_(data), depot_(data.Depot()) {} + CVRPSolution(const CVRPData & data, std::string filename): RoutingSolution(data), data_(data), depot_(data.Depot()), + loaded_solution_obj_(-1) { + LoadInstance(filename); + } + // We could have used RoutingModel::AssignmentToRoutes() + CVRPSolution(const CVRPData & data, const RoutingModel * const routing, const Assignment * const sol): RoutingSolution(data), data_(data), + depot_(data.Depot()), loaded_solution_obj_(-1) { + CHECK_NOTNULL(routing); + CHECK_NOTNULL(sol); + depot_ = routing->IndexToNode(routing->GetDepot()); + for (int32 vehicle = 0; vehicle < routing->vehicles(); ++vehicle) { + int64 start_node = routing->Start(vehicle); + // first node after depot + int64 node = sol->Value(routing->NextVar(start_node)); + for (; !routing->IsEnd(node); + node = sol->Value(routing->NextVar(node))) { + RoutingModel::NodeIndex node_id = routing->IndexToNode(node); +// LG << "Add (node_id,vehicle): (" << node_id << "," << vehicle << ")"; + Add(node_id,vehicle); + } + } + } + + virtual ~CVRPSolution() {} + + RoutingModel::NodeIndex Depot() const { + return depot_; + } + + std::string Name() const { + return name_; + } + + void SetName(const std::string & name) { + name_ = name; + } + + // We only consider complete solutions. + virtual void LoadInstance(std::string filename); + virtual bool IsSolution() const; + virtual bool IsFeasibleSolution() const; + virtual int64 ComputeObjectiveValue() const; + int NumberOfVehicles() const { + return number_of_vehicles_; + } + virtual bool Add(RoutingModel::NodeIndex i, int route_number) { + if (sol_.size() == route_number) { + std::vector v; + sol_.push_back(v); + } + sol_[route_number].push_back(i); + return true; + } + + void WriteAssignment(const RoutingModel * routing, Assignment * const sol) { + CHECK_NOTNULL(routing); + CHECK_NOTNULL(sol); + routing->RoutesToAssignment(sol_, + true, + true, + sol); + } + +std::vector > const & Routes() const { + return sol_; +} + + //iterators + vehicle_iterator vehicle_begin() { return sol_.begin(); } + const_vehicle_iterator vehicle_begin() const { return sol_.begin(); } + vehicle_iterator vehicle_end() { return sol_.end(); } + const_vehicle_iterator vehicle_end() const { return sol_.end(); } + + node_iterator node_begin(vehicle_iterator v_iter) {return v_iter->begin();} + const_node_iterator node_begin(const_vehicle_iterator v_iter) const {return v_iter->begin();} + node_iterator node_end(vehicle_iterator v_iter) {return v_iter->end();} + const_node_iterator node_end(const_vehicle_iterator v_iter) const {return v_iter->end();} + + virtual void Print(std::ostream & out) const; + virtual void Write(const std::string & filename) const ; + protected: + std::vector > sol_; + std::vector capacities_; + private: + const CVRPData & data_; + RoutingModel::NodeIndex depot_; + int line_number_; + void ProcessNewLine(char* const line); + void InitLoadInstance() { + line_number_ = 0; + number_of_vehicles_ = 0; + sol_.clear(); + name_ = ""; + comment_ = ""; + } + + std::string name_; + std::string comment_; + int64 loaded_solution_obj_; + int number_of_vehicles_; + }; + + void CVRPSolution::LoadInstance(std::string filename) { + InitLoadInstance(); + FileLineReader reader(filename.c_str()); + reader.set_line_callback(NewPermanentCallback( + this, + &CVRPSolution::ProcessNewLine)); + reader.Reload(); + if (!reader.loaded_successfully()) { + LOG(FATAL) << "Could not open TSPTW solution file: " << filename; + } + } + + // Test if all nodes are serviced once and only once + bool CVRPSolution::IsSolution() const { + // Test if same number of nodes + if (data_.Size() != Size()) { + return false; + } + + // Test if all nodes are used + Bitmap used(Size()); + + for (int i = 0; i < sol_.size(); ++i) { + for (int j = 0; j < sol_[i].size(); ++j) { + int index = sol_[i][j].value(); + if (used.Get(index)) { + LG << "already used index = " << index; + return false; + } else { + used.Set(index,true); + } + } + } + + // Test if depot was not used in the solution + return !used.Get(depot_.value()); + } + + // Test if capacities are respected. + bool CVRPSolution::IsFeasibleSolution() const { + if (!IsSolution()) { + return false; + } + + const int64 vehicle_capacity = data_.Capacity(); + RoutingModel::NodeIndex node; + int64 route_capacity_left = vehicle_capacity; + int vehicle_index = 1; + + for (const_vehicle_iterator v_iter = vehicle_begin(); v_iter != vehicle_end(); ++v_iter) { + route_capacity_left = vehicle_capacity; + VLOG(1) << "Route " << vehicle_index << " with capacity " << route_capacity_left; + for (const_node_iterator n_iter = node_begin(v_iter); n_iter != node_end(v_iter); ++n_iter ) { + node = *n_iter; + + route_capacity_left -= data_.Demand(node); + VLOG(1) << "Servicing node " << node.value() + 1 << " with demand " << data_.Demand(node) << " (capacity left: " << route_capacity_left << ")"; + if (route_capacity_left < 0) { + return false; + } + } + ++vehicle_index; + } + + return true; + } + + + + int64 CVRPSolution::ComputeObjectiveValue() const { + int64 obj = 0; + RoutingModel::NodeIndex from_node, to_node; + + for (const_vehicle_iterator v_iter = vehicle_begin(); v_iter != vehicle_end(); ++v_iter) { + from_node = depot_; + for (const_node_iterator n_iter = node_begin(v_iter); n_iter != node_end(v_iter); ++n_iter ) { + to_node = *n_iter; + obj += data_.Distance(from_node, to_node); + from_node = to_node; + } + // Last arc + obj += data_.Distance(to_node, depot_); + } + + return obj; + } + + void CVRPSolution::Print(std::ostream& out) const { + int32 vehicle_index = 0; + for (const_vehicle_iterator v_iter = vehicle_begin(); v_iter != vehicle_end(); ++v_iter) { + out << "Route " << StrCat("#", vehicle_index + 1, ":"); + for (const_node_iterator n_iter = node_begin(v_iter); n_iter != node_end(v_iter); ++n_iter ) { + out << " " << (*n_iter).value() + (FLAGS_numbering_solution_nodes_from_zero? 0 : 1); + } + out << std::endl; + ++vehicle_index; + } + out << "cost " << ComputeObjectiveValue(); + + } + + void CVRPSolution::Write(const std::string & filename) const { + WriteToFile writer(this, filename); + writer.SetMember(&operations_research::CVRPSolution::Print); + writer.Run(); + } + +void CVRPSolution::ProcessNewLine(char*const line) { + ++line_number_; + static const char kWordDelimiters[] = " #:"; + std::vector words; + words = strings::Split(line, kWordDelimiters, strings::SkipEmpty()); + + if (words[0] == "Route") { + const int number_of_served_nodes = words.size() - 2; + CHECK_GE(number_of_served_nodes, 1); + for (int node = 0; node < number_of_served_nodes; ++node) { + int32 node_id = atoi32(words[node + 2]) + (FLAGS_numbering_solution_nodes_from_zero? 0 : -1); + CHECK_LE(node_id, size_) << "Node " << node_id << " is greater than size " << size_ << " of solution."; + Add(RoutingModel::NodeIndex(node_id ), number_of_vehicles_); + } + ++number_of_vehicles_; + + return; + } + + if (words[0] == "cost") { + CHECK_EQ(words.size(), 2) << "Only objective value allowed on cost line of CVRP solution file at line " << line_number_; + loaded_solution_obj_ = atoi64(words[1]); + return; + } + LOG(FATAL) << "Unrecognized line in CVRP solution file at line: " << line_number_; +} // void ProcessNewLine(char* const line) + + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_CVRP_SOLUTION_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/cvrp_solution_to_epix.cc b/documentation/tutorials/cplusplus/chap10/cvrp_solution_to_epix.cc new file mode 100644 index 0000000000..bab9cff9c6 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/cvrp_solution_to_epix.cc @@ -0,0 +1,61 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Simple program to visualize a CVRP solution. + +#include + +#include "base/commandlineflags.h" + +#include "cvrp_data.h" +#include "cvrp_solution.h" +#include "routing_common/tsplib_reader.h" +#include "cvrp_epix_data.h" + +//DECLARE_int32(width_size); + +//DEFINE_string(instance_file, "", "TSPLIB instance file."); +//DEFINE_string(solution_file, "", "TSPLIB solution file."); + +int main(int argc, char **argv) { + std::string usage("Prints a CVRP solution in ePiX format.\n" + "See Google or-tools tutorials\n" + "Sample usage:\n\n"); + usage += argv[0]; + usage += " -instance_file= -solution_file= > epix_file.xp\n\n"; + usage += " ./elaps -pdf epix_file.xp\n"; + + google::SetUsageMessage(usage); + google::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_instance_file != "" && FLAGS_solution_file != "") { + operations_research::TSPLIBReader tsplib_reader(FLAGS_instance_file); + operations_research::CVRPData cvrp_data(tsplib_reader); + operations_research::CVRPSolution cvrp_sol(cvrp_data, FLAGS_solution_file); + if (cvrp_sol.IsFeasibleSolution()) { + if (cvrp_data.IsVisualizable()) { + operations_research::CVRPEpixData epix_data(cvrp_data); + epix_data.PrintSolution(std::cout, cvrp_sol); + } else { + LG << "Solution is not visualizable!"; + } + } else { + LG << "Solution is NOT feasible..."; + } + } else { + std::cout << google::ProgramUsage(); + exit(-1); + } + return 0; +} \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/rl_auxiliary_graph.cc b/documentation/tutorials/cplusplus/chap10/rl_auxiliary_graph.cc new file mode 100644 index 0000000000..490d6f27b7 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/rl_auxiliary_graph.cc @@ -0,0 +1,87 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include +#include + +#include "base/commandlineflags.h" +#include "constraint_solver/routing.h" +#include "base/join.h" + +namespace operations_research { + +// Not very interesting +int64 MyCost(RoutingModel::NodeIndex from, RoutingModel::NodeIndex to) { + return (from + to).value(); +} + + +void VRP_Partial_Routes(void) { + // create multi depots + std::vector > depots(4); + depots[0] = std::make_pair(1,4); + depots[1] = std::make_pair(3,4); + depots[2] = std::make_pair(3,7); + depots[3] = std::make_pair(4,7); + + RoutingModel VRP(9, 4, depots); + + VRP.SetCost(NewPermanentCallback(MyCost)); + + // Constructing routes + std::vector > routes(4); + // Constructing route 0 : 1 - 0 - 2 - 4 + routes[0].push_back(RoutingModel::NodeIndex(0)); + routes[0].push_back(RoutingModel::NodeIndex(2)); + // Constructing route 1 : 3 - 5 - 4 + routes[1].push_back(RoutingModel::NodeIndex(5)); + // Constructing route 2 : 3 - 6 - 7 + routes[2].push_back(RoutingModel::NodeIndex(6)); + // Constructing route 3 : 4 - 8 - 7 + routes[3].push_back(RoutingModel::NodeIndex(8)); + + VRP.CloseModel(); + + if (VRP.ApplyLocksToAllVehicles(routes, true)) { + LG << "Routes successfully locked"; + } else { + LG << "Routes not successfully locked"; + } + + const Assignment* Solution = VRP.Solve(); + + for (int p = 0; p < VRP.vehicles(); ++p) { + LG << "Route: " << p; + std::string route; + std::string index_route; + for (int64 index = VRP.Start(p); !VRP.IsEnd(index); index = Solution->Value(VRP.NextVar(index))) { + route = StrCat(route, StrCat(VRP.IndexToNode(index).value(), " -> ")); + index_route = StrCat(index_route, StrCat(index, " -> ")); + } + route = StrCat(route, VRP.IndexToNode(VRP.End(p)).value()); + index_route = StrCat(index_route, VRP.End(p)); + LG << route; + LG << index_route; + } + +} // void VRP_Partial_Routes(void) + +} // namespace operations_research + +int main(int argc, char **argv) { + + google::ParseCommandLineFlags(&argc, &argv, true); + operations_research::VRP_Partial_Routes(); + + return 0; +} // main \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/vrp.cc b/documentation/tutorials/cplusplus/chap10/vrp.cc new file mode 100644 index 0000000000..d029507ae1 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/vrp.cc @@ -0,0 +1,129 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Simple program to solve the VRP with Local Search in or-tools. + +#include +#include + +#include "base/commandlineflags.h" +#include "constraint_solver/routing.h" +#include "base/join.h" +#include "base/timer.h" + +#include "cvrp_data.h" +#include "cvrp_solution.h" +#include "routing_common/tsplib_reader.h" + +DEFINE_int32(depot, 1, "The starting node of the tour."); +//DEFINE_string(instance_file, "", "Input file with TSPLIB data."); +//DEFINE_string(solution_file, "", "Output file with generated solution in (C)VRP format."); +//DECLARE_int32(number_vehicles); +DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms."); + +namespace operations_research { + +void VRPSolver (const CVRPData & data) { + + const int size = data.Size(); + + RoutingModel routing(size, FLAGS_number_vehicles); + routing.SetCost(NewPermanentCallback(&data, &CVRPData::Distance)); + + // Disabling Large Neighborhood Search, comment out to activate it. + //routing.SetCommandLineOption("routing_no_lns", "true"); + + if (FLAGS_time_limit_in_ms > 0) { + routing.UpdateTimeLimit(FLAGS_time_limit_in_ms); + } + + // Setting depot + CHECK_GT(FLAGS_depot, 0) << " Because we use the" << " TSPLIB convention, the depot id must be > 0"; + RoutingModel::NodeIndex depot(FLAGS_depot -1); + routing.SetDepot(depot); + + routing.CloseModel(); + + // Forbidding empty routes + for (int vehicle_nbr = 0; vehicle_nbr < FLAGS_number_vehicles; ++vehicle_nbr) { + IntVar* const start_var = routing.NextVar(routing.Start(vehicle_nbr)); + for (int64 node_index = routing.Size(); node_index < routing.Size() + routing.vehicles(); ++node_index) { + start_var->RemoveValue(node_index); + } + } + + + + // SOLVE + const Assignment* solution = routing.Solve(); + + // INSPECT SOLUTION + if (solution != NULL) { + CVRPSolution cvrp_sol(data, &routing, solution); + cvrp_sol.SetName(StrCat("Solution for instance ", data.Name(), " computed by vrp.cc")); + // test solution + if (!cvrp_sol.IsSolution()) { + LOG(ERROR) << "Solution is NOT feasible!"; + } else { + LG << "Solution is feasible and has an obj value of " << cvrp_sol.ComputeObjectiveValue(); + // SAVE SOLUTION IN CVRP FORMAT + if (FLAGS_solution_file != "") { + cvrp_sol.Write(FLAGS_solution_file); + } + } + + // Solution cost. + LG << "Obj value: " << solution->ObjectiveValue(); + // Inspect solution. + std::string route; + for (int vehicle_nbr = 0; vehicle_nbr < FLAGS_number_vehicles; ++vehicle_nbr) { + route = ""; + for (int64 node = routing.Start(vehicle_nbr); !routing.IsEnd(node); + node = solution->Value(routing.NextVar(node))) { + route = StrCat(route, StrCat(routing.IndexToNode(node).value() + 1 , " -> ")); + } + route = StrCat(route, routing.IndexToNode(routing.End(vehicle_nbr)).value() + 1 ); + LG << "Route #" << vehicle_nbr + 1 << std::endl << route << std::endl; + } + + } else { + LG << "No solution found."; + } + +} // void VRPSolver (CVRPData & data) + + +} // namespace operations_research + + +int main(int argc, char **argv) { + std::string usage("Computes a simple VRP.\n" + "See Google or-tools tutorials\n" + "Sample usage:\n\n"); + usage += argv[0]; + usage += " -instance_file="; + + google::SetUsageMessage(usage); + google::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_instance_file == "") { + std::cout << google::ProgramUsage(); + exit(-1); + } + operations_research::TSPLIBReader tsplib_reader(FLAGS_instance_file); + operations_research::CVRPData cvrp_data(tsplib_reader); + operations_research::VRPSolver(cvrp_data); + + return 0; +} // main \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/vrp_partial_routes.cc b/documentation/tutorials/cplusplus/chap10/vrp_partial_routes.cc new file mode 100644 index 0000000000..2047ecb01d --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/vrp_partial_routes.cc @@ -0,0 +1,97 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include +#include + +#include "base/commandlineflags.h" +#include "constraint_solver/routing.h" +#include "base/join.h" + +namespace operations_research { + +// Not very interesting +int64 MyCost(RoutingModel::NodeIndex from, RoutingModel::NodeIndex to) { + return (from + to).value(); +} + +void VRP_Partial_Routes(void) { + // create multi depots + std::vector > depots(4); + depots[0] = std::make_pair(1,4); + depots[1] = std::make_pair(3,4); + depots[2] = std::make_pair(3,7); + depots[3] = std::make_pair(4,7); + + RoutingModel VRP(9, 4, depots); + + VRP.SetCost(NewPermanentCallback(MyCost)); + + // Constructing routes + std::vector > routes(4); + // Constructing route 0 : 7 - 0 - 2 - 4 - 5 - 6 - 7 + routes[0].push_back(RoutingModel::NodeIndex(0)); + routes[0].push_back(RoutingModel::NodeIndex(2)); + //routes[0].push_back(RoutingModel::NodeIndex(4)); + //routes[0].push_back(RoutingModel::NodeIndex(5)); + //routes[0].push_back(RoutingModel::NodeIndex(6)); + //routes[0].push_back(RoutingModel::NodeIndex(4)); + // Constructing route 1 : 6 - 1 - 8 - 3 - 7 + routes[1].push_back(RoutingModel::NodeIndex(5)); + + routes[2].push_back(RoutingModel::NodeIndex(6)); + routes[3].push_back(RoutingModel::NodeIndex(8)); + + VRP.CloseModel(); + + LG << "vehicle 0: Start: " << VRP.Start(0) << " End: " << VRP.End(0); + LG << "vehicle 1: Start: " << VRP.Start(1) << " End: " << VRP.End(1); + LG << "Size() = " << VRP.Size(); + LG << "Depot 5 to int64 index: " << VRP.NodeToIndex(RoutingModel::NodeIndex(5)); + LG << "Depot 1 to int64 index: " << VRP.NodeToIndex(RoutingModel::NodeIndex(1)); + + if (VRP.ApplyLocksToAllVehicles(routes, true)) { + LG << "Routes successfully locked"; + } else { + LG << "Routes not successfully locked"; + } + + const Assignment* Solution = VRP.Solve(); + + //LG << "Crash: " << Solution->Value(VRP.NextVar(VRP.End(0))); + int route_number = Solution->Value(VRP.VehicleVar(4)); + + for (int p = 0; p < VRP.vehicles(); ++p) { + LG << "Route: " << p; + std::string route; + std::string index_route; + for (int64 index = VRP.Start(p); !VRP.IsEnd(index); index = Solution->Value(VRP.NextVar(index))) { + route = StrCat(route, StrCat(VRP.IndexToNode(index).value(), " -> ")); + index_route = StrCat(index_route, StrCat(index, " -> ")); + } + route = StrCat(route, VRP.IndexToNode(VRP.End(p)).value()); + index_route = StrCat(index_route, VRP.End(p)); + LG << route; + LG << index_route; + } + +} // void VRP_Partial_Routes(void) + +} // namespace operations_research + +int main(int argc, char **argv) { + + google::ParseCommandLineFlags(&argc, &argv, true); + operations_research::VRP_Partial_Routes(); + return 0; +} // main \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap10/vrp_solution_to_epix.cc b/documentation/tutorials/cplusplus/chap10/vrp_solution_to_epix.cc new file mode 100644 index 0000000000..043dcc8666 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap10/vrp_solution_to_epix.cc @@ -0,0 +1,61 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Simple program to visualize a VRP solution. + +#include + +#include "base/commandlineflags.h" + +#include "cvrp_data.h" +#include "cvrp_solution.h" +#include "routing_common/tsplib_reader.h" +#include "cvrp_epix_data.h" + +//DECLARE_int32(width_size); + +//DEFINE_string(instance_file, "", "TSPLIB instance file."); +//DEFINE_string(solution_file, "", "TSPLIB solution file."); + +int main(int argc, char **argv) { + std::string usage("Prints a CVRP solution in ePiX format.\n" + "See Google or-tools tutorials\n" + "Sample usage:\n\n"); + usage += argv[0]; + usage += " -instance_file= -solution_file= > epix_file.xp\n\n"; + usage += " ./elaps -pdf epix_file.xp\n"; + + google::SetUsageMessage(usage); + google::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_instance_file != "" && FLAGS_solution_file != "") { + operations_research::TSPLIBReader tsplib_reader(FLAGS_instance_file); + operations_research::CVRPData cvrp_data(tsplib_reader); + operations_research::CVRPSolution cvrp_sol(cvrp_data, FLAGS_solution_file); + if (cvrp_sol.IsSolution()) { + if (cvrp_data.IsVisualizable()) { + operations_research::CVRPEpixData epix_data(cvrp_data); + epix_data.PrintSolution(std::cout, cvrp_sol); + } else { + LG << "Solution is not visualizable!"; + } + } else { + LG << "Solution is NOT feasible..."; + } + } else { + std::cout << google::ProgramUsage(); + exit(-1); + } + return 0; +} \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap6/Makefile b/documentation/tutorials/cplusplus/chap6/Makefile index ac8795eabd..72e7ae29ad 100644 --- a/documentation/tutorials/cplusplus/chap6/Makefile +++ b/documentation/tutorials/cplusplus/chap6/Makefile @@ -13,43 +13,43 @@ include $(OR_TOOLS_TOP)/Makefile tutorials: $(EXE) -report_jobshopdata.$O: report_jobshopdata.cc jobshop.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h +report_jobshopdata.$O: report_jobshopdata.cc jobshop.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solveri.h $(CCC) $(CFLAGS) -c report_jobshopdata.cc -o report_jobshopdata.$O report_jobshopdata: $(CP_DEPS) report_jobshopdata.$O $(CCC) $(CFLAGS) report_jobshopdata.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o report_jobshopdata -jobshop.$O: jobshop.cc jobshop.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h +jobshop.$O: jobshop.cc jobshop.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solveri.h $(CCC) $(CFLAGS) -c jobshop.cc -o jobshop.$O jobshop: $(CP_DEPS) jobshop.$O $(CCC) $(CFLAGS) jobshop.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop -dummy_ls.$O: dummy_ls.cc $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h +dummy_ls.$O: dummy_ls.cc $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solveri.h $(CCC) $(CFLAGS) -c dummy_ls.cc -o dummy_ls.$O dummy_ls: $(CP_DEPS) dummy_ls.$O $(CCC) $(CFLAGS) dummy_ls.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o dummy_ls -jobshop_ls1.$O: jobshop_ls1.cc jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h +jobshop_ls1.$O: jobshop_ls1.cc jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solveri.h $(CCC) $(CFLAGS) -c jobshop_ls1.cc -o jobshop_ls1.$O jobshop_ls1: $(CP_DEPS) jobshop_ls1.$O $(CCC) $(CFLAGS) jobshop_ls1.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop_ls1 -jobshop_ls2.$O: jobshop_ls2.cc jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h +jobshop_ls2.$O: jobshop_ls2.cc jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solveri.h $(CCC) $(CFLAGS) -c jobshop_ls2.cc -o jobshop_ls2.$O jobshop_ls2: $(CP_DEPS) jobshop_ls2.$O $(CCC) $(CFLAGS) jobshop_ls2.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop_ls2 -jobshop_ls3.$O: jobshop_ls3.cc jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h +jobshop_ls3.$O: jobshop_ls3.cc jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solveri.h $(CCC) $(CFLAGS) -c jobshop_ls3.cc -o jobshop_ls3.$O jobshop_ls3: $(CP_DEPS) jobshop_ls3.$O $(CCC) $(CFLAGS) jobshop_ls3.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop_ls3 -dummy_ls_filtering.$O: dummy_ls_filtering.cc $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h +dummy_ls_filtering.$O: dummy_ls_filtering.cc $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solveri.h $(CCC) $(CFLAGS) -c dummy_ls_filtering.cc -o dummy_ls_filtering.$O dummy_ls_filtering: $(CP_DEPS) dummy_ls_filtering.$O diff --git a/documentation/tutorials/cplusplus/chap7/Makefile b/documentation/tutorials/cplusplus/chap7/Makefile new file mode 100644 index 0000000000..ebe3169d4d --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/Makefile @@ -0,0 +1,72 @@ +OR_TOOLS_TOP= +OR_TOOLS_SOURCES=$(OR_TOOLS_TOP)/src + +SOURCES= jobshop_ts1.cc jobshop_ts2.cc jobshop_sa1.cc jobshop_sa2.cc dummy_lns.cc jobshop_lns.cc jobshop_heuristic.cc golomb_default_search1.cc golomb_default_search2.cc + +OBJECTS=$(SOURCES:.cc=.$O) + +EXE=$(SOURCES:.cc=) + +include $(OR_TOOLS_TOP)/Makefile + +.PHONY: all tutorials local_clean + +tutorials: $(EXE) + +jobshop_ts1.$O: jobshop_ts1.cc limits.h jobshop.h jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h + $(CCC) $(CFLAGS) -c jobshop_ts1.cc -o jobshop_ts1.$O + +jobshop_ts1: $(CP_DEPS) jobshop_ts1.$O + $(CCC) $(CFLAGS) jobshop_ts1.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop_ts1 + +jobshop_ts2.$O: jobshop_ts2.cc limits.h jobshop.h jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h + $(CCC) $(CFLAGS) -c jobshop_ts2.cc -o jobshop_ts2.$O + +jobshop_ts2: $(CP_DEPS) jobshop_ts2.$O + $(CCC) $(CFLAGS) jobshop_ts2.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop_ts2 + +jobshop_sa1.$O: jobshop_sa1.cc limits.h jobshop.h jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h + $(CCC) $(CFLAGS) -c jobshop_sa1.cc -o jobshop_sa1.$O + +jobshop_sa1: $(CP_DEPS) jobshop_sa1.$O + $(CCC) $(CFLAGS) jobshop_sa1.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop_sa1 + +jobshop_sa2.$O: jobshop_sa2.cc limits.h jobshop.h jobshop_ls.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h + $(CCC) $(CFLAGS) -c jobshop_sa2.cc -o jobshop_sa2.$O + +jobshop_sa2: $(CP_DEPS) jobshop_sa2.$O + $(CCC) $(CFLAGS) jobshop_sa2.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop_sa2 + +dummy_lns.$O: dummy_lns.cc $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solveri.h + $(CCC) $(CFLAGS) -c dummy_lns.cc -o dummy_lns.$O + +dummy_lns: $(CP_DEPS) dummy_lns.$O + $(CCC) $(CFLAGS) dummy_lns.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o dummy_lns + +jobshop_lns.$O: jobshop_lns.cc limits.h jobshop.h jobshop_ls.h jobshop_lns.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h + $(CCC) $(CFLAGS) -c jobshop_lns.cc -o jobshop_lns.$O + +jobshop_lns: $(CP_DEPS) jobshop_lns.$O + $(CCC) $(CFLAGS) jobshop_lns.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop_lns + +jobshop_heuristic.$O: jobshop_heuristic.cc limits.h jobshop.h jobshop_ls.h jobshop_lns.h $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h + $(CCC) $(CFLAGS) -c jobshop_heuristic.cc -o jobshop_heuristic.$O + +jobshop_heuristic: $(CP_DEPS) jobshop_heuristic.$O + $(CCC) $(CFLAGS) jobshop_heuristic.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o jobshop_heuristic + +golomb_default_search1.$O: golomb_default_search1.cc $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h + $(CCC) $(CFLAGS) -c golomb_default_search1.cc -o golomb_default_search1.$O + +golomb_default_search1: $(CP_DEPS) golomb_default_search1.$O + $(CCC) $(CFLAGS) golomb_default_search1.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o golomb_default_search1 + +golomb_default_search2.$O: golomb_default_search2.cc $(OR_TOOLS_SOURCES)/constraint_solver/constraint_solver.h + $(CCC) $(CFLAGS) -c golomb_default_search2.cc -o golomb_default_search2.$O + +golomb_default_search2: $(CP_DEPS) golomb_default_search2.$O + $(CCC) $(CFLAGS) golomb_default_search2.$O $(DYNAMIC_CP_LNK) $(DYNAMIC_LD_FLAGS) -o golomb_default_search2 + +local_clean: + rm $(OBJECTS) $(EXE) + diff --git a/documentation/tutorials/cplusplus/chap7/abz9 b/documentation/tutorials/cplusplus/chap7/abz9 new file mode 100644 index 0000000000..a7359d2bcb --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/abz9 @@ -0,0 +1,27 @@ + +++++++++++++++++++++++++++++ + + instance abz9 + + +++++++++++++++++++++++++++++ + Adams, Balas, and Zawack 15 x 20 instance (Table 1, instance 9) + 20 15 + 6 14 5 21 8 13 4 11 1 11 14 35 13 20 11 17 10 18 12 11 2 23 3 13 0 15 7 11 9 35 + 1 35 5 31 0 13 3 26 6 14 9 17 7 38 12 20 10 19 13 12 8 16 4 34 11 15 14 12 2 14 + 0 30 4 35 2 40 10 35 6 30 14 23 8 29 13 37 7 38 3 40 9 26 12 11 1 40 11 36 5 17 + 7 40 5 18 4 12 8 23 0 23 9 14 13 16 12 14 10 23 3 12 6 16 14 32 1 40 11 25 2 29 + 2 35 3 15 12 31 11 28 6 32 4 30 10 27 7 29 0 38 13 11 1 23 14 17 5 27 9 37 8 29 + 5 33 3 33 6 19 12 40 10 19 0 33 13 26 2 31 11 28 7 36 4 38 1 21 14 25 9 40 8 35 + 13 25 0 32 11 33 12 18 4 32 6 28 5 15 3 35 9 14 2 34 7 23 10 32 1 17 14 26 8 19 + 2 16 12 33 9 34 11 30 13 40 8 12 14 26 5 26 6 15 3 21 1 40 4 32 0 14 7 30 10 35 + 2 17 10 16 14 20 6 24 8 26 3 36 12 22 0 14 13 11 9 20 7 23 1 29 11 23 4 15 5 40 + 4 27 9 37 3 40 11 14 13 25 7 30 0 34 2 11 5 15 12 32 1 36 10 12 14 28 8 31 6 23 + 13 25 0 22 3 27 8 14 5 25 6 20 14 18 7 14 1 19 2 17 4 27 9 22 12 22 11 27 10 21 + 14 34 10 15 0 22 3 29 13 34 6 40 7 17 2 32 12 20 5 39 4 31 11 16 1 37 8 33 9 13 + 6 12 12 27 4 17 2 24 8 11 5 19 14 11 3 17 9 25 1 11 11 31 13 33 7 31 10 12 0 22 + 5 22 14 15 0 16 8 32 7 20 4 22 9 11 13 19 1 30 12 33 6 29 11 18 3 34 10 32 2 18 + 5 27 3 26 10 28 6 37 4 18 12 12 11 11 13 26 7 27 9 40 14 19 1 24 2 18 0 12 8 34 + 8 15 5 28 9 25 6 32 1 13 7 38 11 11 2 34 4 25 0 20 10 32 3 23 12 14 14 16 13 20 + 1 15 4 13 8 37 3 14 10 22 5 24 12 26 7 22 9 34 14 22 11 19 13 32 0 29 2 13 6 35 + 7 36 5 33 13 28 9 20 10 30 4 33 14 29 0 34 3 22 11 12 6 30 8 12 1 35 2 13 12 35 + 14 26 11 31 5 35 2 38 13 19 10 35 4 27 8 29 3 39 9 13 6 14 7 26 0 17 1 22 12 15 + 1 36 7 34 11 33 8 17 14 38 6 39 5 16 3 27 13 29 2 16 0 16 4 19 9 40 12 35 10 39 \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap7/dummy_lns.cc b/documentation/tutorials/cplusplus/chap7/dummy_lns.cc new file mode 100644 index 0000000000..9c0e4d426e --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/dummy_lns.cc @@ -0,0 +1,131 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Dummy Local Search to understand the behavior of LSN Operators. + +#include "constraint_solver/constraint_solveri.h" +#include "constraint_solver/constraint_solver.h" + + +DEFINE_int64(n, 4, "Size of the problem"); + +DEFINE_int64(ls_time_limit, 10000, "LS time limit (in ms)"); +DEFINE_int64(ls_branches_limit, 10000, "LS branches limit"); +DEFINE_int64(ls_failures_limit, 10000, "LS failures limit"); +DEFINE_int64(ls_solutions_limit, 1, "LS solutions limit"); +DEFINE_bool(print_intermediate_solutions, true, + "Add a search log for the objective?"); + +namespace operations_research { + +class OneVarLns : public BaseLNS { + public: + OneVarLns(const std::vector& vars) + : BaseLNS(vars), + index_(0) {} + + ~OneVarLns() {} + + virtual void InitFragments() { index_ = 0; } + + virtual bool NextFragment(std::vector* fragment) { + const int size = Size(); + if (index_ < size) { + fragment->push_back(index_); + ++index_; + return true; + } else { + return false; + } + } + + private: + int index_; +}; + +void DummyLNS(const int64 n) { + CHECK_GE(n, 2) << "size of problem (n) must be greater or equal than 2"; + LOG(INFO) << "Simple Large Neighborhood Search with initial solution"; + + Solver s("Dummy LNS"); + + // Model + vector vars; + s.MakeIntVarArray(n, 0, n-1, &vars); + IntVar* const sum_var = s.MakeSum(vars)->Var(); + OptimizeVar* const obj = s.MakeMinimize(sum_var, 1); + + // unique constraint x_0 >= 1 + s.AddConstraint(s.MakeGreaterOrEqual(vars[0], 1)); + + // initial solution + Assignment * const initial_solution = s.MakeAssignment(); + initial_solution->Add(vars); + for (int i = 0; i < n; ++i) { + if (i % 2 == 0) { + initial_solution->SetValue(vars[i], n - 1); + } else { + initial_solution->SetValue(vars[i], n - 2); + } + } + + // complementary phase builder + Assignment * const optimal_candidate_solution = s.MakeAssignment(); + optimal_candidate_solution->Add(vars); + optimal_candidate_solution->AddObjective(sum_var); + DecisionBuilder * optimal_complementary_db = s.MakeNestedOptimize( + s.MakePhase(vars, + Solver::CHOOSE_FIRST_UNBOUND, + Solver::ASSIGN_MAX_VALUE), + optimal_candidate_solution, + false, + 1); + + // BaseLNS + OneVarLns one_var_lns(vars); + + SearchLimit * const limit = s.MakeLimit(FLAGS_ls_time_limit, + FLAGS_ls_branches_limit, + FLAGS_ls_failures_limit, + FLAGS_ls_solutions_limit); + + LocalSearchPhaseParameters* ls_params + = s.MakeLocalSearchPhaseParameters(&one_var_lns, optimal_complementary_db, limit); + + DecisionBuilder* ls = s.MakeLocalSearchPhase(initial_solution, ls_params); + + SolutionCollector* const collector = s.MakeLastSolutionCollector(); + collector->Add(vars); + collector->AddObjective(sum_var); + + SearchMonitor* log = NULL; + if (FLAGS_print_intermediate_solutions) { + log = s.MakeSearchLog(1000, obj); + } + + if (s.Solve(ls, collector, obj, log)) { + LOG(INFO) << "Objective value = " << collector->objective_value(0); + } else { + LG << "No solution..."; + } +} + +} // namespace operations_research + +int main(int argc, char** argv) { + google::ParseCommandLineFlags(&argc, &argv, true); + operations_research::DummyLNS(FLAGS_n); + return 0; +} + diff --git a/documentation/tutorials/cplusplus/chap7/golomb_default_search1.cc b/documentation/tutorials/cplusplus/chap7/golomb_default_search1.cc new file mode 100644 index 0000000000..640bc84176 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/golomb_default_search1.cc @@ -0,0 +1,105 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Simple use of DefaultPhase to solve the Golomb Rule Problem. + +#include +#include + +#include "base/commandlineflags.h" +#include "base/logging.h" +#include "constraint_solver/constraint_solver.h" + +DEFINE_int32(n, 0, "Number of marks. If 0 will test different values of n."); +DEFINE_bool(print, false, "Print solution or not?"); + +// kG[n] = G(n). +static const int kG[] = { + -1, 0, 1, 3, 6, 11, 17, 25, 34, 44, 55, 72, 85, + 106, 127, 151, 177, 199, 216, 246 +}; + +static const int kKnownSolutions = 19; + +namespace operations_research { + +void GolombRuler(int n) { + CHECK_GE(n, 1); + + Solver s("golomb"); + + // Upper bound on G(n), only valid for n <= 65 000 + CHECK_LE(n, 65000); + const int64 max = n * n - 1; + + // Variables + std::vector X(n + 1); + X[0] = s.MakeIntConst(-1); // The solver doesn't allow NULL pointers + X[1] = s.MakeIntConst(0); // X(1) = 0 + + for (int i = 2; i <= n; ++i) { + X[i] = s.MakeIntVar(1, max, StringPrintf("X%03d", i)); + } + + std::vector Y; + for (int i = 1; i <= n; ++i) { + for (int j = i + 1; j <= n; ++j) { + IntVar* const diff = s.MakeDifference(X[j], X[i])->Var(); + Y.push_back(diff); + diff->SetMin(1); + } + } + + s.AddConstraint(s.MakeAllDifferent(Y)); + + OptimizeVar* const length = s.MakeMinimize(X[n], 1); + + SolutionCollector* const collector = s.MakeLastSolutionCollector(); + collector->Add(X); + DecisionBuilder* const db = s.MakeDefaultPhase(X); + + s.Solve(db, collector, length); // go! + + CHECK_EQ(collector->solution_count(), 1); + const int64 result = collector->Value(0, X[n]); + LOG(INFO) << "G(" << n << ") = " << result; + LOG(INFO) << "Time: " << s.wall_time()/1000.0 << " s"; + + if (FLAGS_print) { + std::ostringstream solution_str; + solution_str << "Solution: "; + for (int i = 1; i <= n; ++i) { + solution_str << collector->Value(0, X[i]) << " "; + } + LOG(INFO) << solution_str.str(); + } + + if (n < kKnownSolutions) { + CHECK_EQ(result, kG[n]); + } +} + +} // namespace operations_research + +int main(int argc, char **argv) { + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_n != 0) { + operations_research::GolombRuler(FLAGS_n); + } else { + for (int n = 4; n < 11; ++n) { + operations_research::GolombRuler(n); + } + } + return 0; +} diff --git a/documentation/tutorials/cplusplus/chap7/golomb_default_search2.cc b/documentation/tutorials/cplusplus/chap7/golomb_default_search2.cc new file mode 100644 index 0000000000..cd63a46916 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/golomb_default_search2.cc @@ -0,0 +1,138 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Simple use of DefaultPhase with customized parameters to solve the Golomb Rule Problem. + +#include +#include + +#include "base/commandlineflags.h" +#include "base/logging.h" +#include "constraint_solver/constraint_solver.h" + +DEFINE_int32(n, 0, "Number of marks. If 0 will test different values of n."); +DEFINE_bool(print, false, "Print solution or not?"); + +// kG[n] = G(n). +static const int kG[] = { + -1, 0, 1, 3, 6, 11, 17, 25, 34, 44, 55, 72, 85, + 106, 127, 151, 177, 199, 216, 246 +}; + +static const int kKnowSolutions = 19; + +namespace operations_research { + +Constraint * AllDifferent(Solver* s, + const std::vector > & vars) { + std::vector vars_flat; + for (int i = 0; i < vars.size(); ++i) { + for (int j = 0; j < vars[i].size(); ++j) { + if (vars[i][j] != NULL) { + vars_flat.push_back(vars[i][j]); + } + } + } + return s->MakeAllDifferent(vars_flat); +} + +void GolombRuler(const int n) { + CHECK_GE(n, 1); + + Solver s("golomb"); + + CHECK_LE(n, 25); + const int64 max = kG[n]; + + // Variables + std::vector X(n + 1); + X[0] = s.MakeIntConst(-1); // The solver doesn't allow NULL pointers + X[1] = s.MakeIntConst(0); // X(1) = 0 + + for (int i = 2; i <= n; ++i) { + X[i] = s.MakeIntVar(1, max, StringPrintf("X%03d", i)); + } + + std::vector > Y(n + 1, std::vector(n + 1)); + for (int i = 1; i < n; ++i) { + for (int j = i + 1; j <= n; ++j) { + Y[i][j] = s.MakeDifference(X[j], X[i])->Var(); + if ((i > 1) || (j < n)) { + Y[i][j]->SetMin(kG[j-i +1]); + } else { + Y[i][j]->SetMin(kG[j-i] + 1); + } + } + } + + // Constraints + s.AddConstraint(s.MakeLess(s.MakeDifference(X[2], X[1])->Var(), + s.MakeDifference(X[n], X[n-1])->Var())); + + s.AddConstraint(AllDifferent(&s, Y)); + + for (int i = 1; i < n; ++i) { + for (int j = i + 1; j <= n; ++j) { + s.AddConstraint(s.MakeLessOrEqual(s.MakeDifference(Y[i][j], X[n])->Var(), + -(n - 1 - j + i)*(n - j + i)/2)); + } + } + + OptimizeVar* const length = s.MakeMinimize(X[n], 1); + + SolutionCollector* const collector = s.MakeLastSolutionCollector(); + collector->Add(X); + + DefaultPhaseParameters parameters; + parameters.var_selection_schema = DefaultPhaseParameters::CHOOSE_MAX_VALUE_IMPACT; + parameters.value_selection_schema = DefaultPhaseParameters::SELECT_MAX_IMPACT; + parameters.heuristic_period = -1; + parameters.restart_log_size = -5; + parameters.use_no_goods = false; + DecisionBuilder* const db = s.MakeDefaultPhase(X, parameters); + + s.Solve(db, collector, length); // go! + + CHECK_EQ(collector->solution_count(), 1); + const int64 result = collector->Value(0, X[n]); + LOG(INFO) << "G(" << n << ") = " << result; + LOG(INFO) << "Time: " << s.wall_time()/1000.0 << " s"; + + if (FLAGS_print) { + std::ostringstream solution_str; + solution_str << "Solution: "; + for (int i = 1; i <= n; ++i) { + solution_str << collector->Value(0, X[i]) << " "; + } + LOG(INFO) << solution_str.str(); + } + + if (n < kKnowSolutions) { + CHECK_EQ(result, kG[n]); + } +} + +} // namespace operations_research + +int main(int argc, char **argv) { + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_n != 0) { + operations_research::GolombRuler(FLAGS_n); + } else { + for (int n = 4; n < 11; ++n) { + operations_research::GolombRuler(n); + } + } + return 0; +} diff --git a/documentation/tutorials/cplusplus/chap7/jobshop.h b/documentation/tutorials/cplusplus/chap7/jobshop.h new file mode 100644 index 0000000000..68d1f7df0a --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/jobshop.h @@ -0,0 +1,266 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// The JobshopData class is a simple container for Job-Shop Problem instances. +// It can read the JSSP and professor Taillard's instances formats. +// +// Note that the format is only partially checked: bad inputs might +// cause undefined behavior. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_JOBSHOP_H_ +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_JOBSHOP_H_ + +#include +#include +#include +#include +#include + +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/strtoint.h" +#include "base/join.h" +#include "base/file.h" +#include "base/filelinereader.h" +#include "base/split.h" + +namespace operations_research { + +class JobShopData { + public: + struct Task { + Task(int j, int m, int d) : job_id(j), machine_id(m), duration(d) {} + int job_id; + int machine_id; + int duration; + }; + + enum ProblemType { + UNDEFINED, + JSSP, + TAILLARD + }; + + enum TaillardState { + START, + JOBS_READ, + MACHINES_READ, + SEED_READ, + JOB_ID_READ, + JOB_LENGTH_READ, + JOB_READ + }; + + explicit JobShopData(const std::string& filename) : + name_(""), + filename_(filename), + machine_count_(0), + job_count_(0), + horizon_(0), + current_job_index_(0), + current_line_nbr_(0), + problem_type_(UNDEFINED), + taillard_state_(START), + kWordDelimiters(" "), + problem_numbers_defined(false) { + FileLineReader reader(filename_.c_str()); + reader.set_line_callback(NewPermanentCallback( + this, + &JobShopData::ProcessNewLine)); + reader.Reload(); + if (!reader.loaded_successfully()) { + LOG(FATAL) << "Could not open job-shop file " << filename_; + } + } + + ~JobShopData() {} + + int machine_count() const { return machine_count_; } + + int job_count() const { return job_count_; } + + const std::string& name() const { return name_; } + + int horizon() const { return horizon_; } + + // Returns the tasks of a job, ordered by precedence. + const std::vector& TasksOfJob(int job_id) const { + return all_tasks_[job_id]; + } + + void Report(std::ostream & out) { + out << "Job-shop problem instance "; + if (!problem_numbers_defined) { + out << "not defined yet!" << std::endl; + return; + } + out << "in " << (problem_type_ == JSSP ? "JSSP" : "TAILLARD's") + << " format read from file " << filename_ << std::endl; + out << "Name: " << name() << std::endl; + out << "Jobs: " << job_count() << std::endl; + out << "Machines: " << machine_count() << std::endl; + } + + void ReportAll(std::ostream & out) { + Report(out); + + out << "==========================================" << std::endl; + for (int i = 0; i < all_tasks_.size(); ++i) { + out << "Job: " << i << std::endl; + for (int j = 0; j < all_tasks_[i].size(); ++j) { + Task t = all_tasks_[i][j]; + out << "(" << t.machine_id << "," << t.duration << ") "; + } + out << std::endl; + } + } + + + private: + void ProcessNewLine(char* const line) { + ++current_line_nbr_; + VLOG(3) << "Line number " << current_line_nbr_; + + std::vector words; + words = strings::Split(line, kWordDelimiters, strings::SkipEmpty()); + switch (problem_type_) { + case UNDEFINED: { + if (words.size() == 2 && words[0] == "instance") { + problem_type_ = JSSP; + VLOG(1) << "Reading jssp instance " << words[1]; + name_ = words[1]; + } else if (words.size() == 1 && atoi32(words[0]) > 0) { + problem_type_ = TAILLARD; + VLOG(1) << "Reading Taillard instance from file " << filename_; + name_ = StrCat("Taillard instance from file ", filename_); + taillard_state_ = JOBS_READ; + job_count_ = atoi32(words[0]); + CHECK_GT(job_count_, 0); + all_tasks_.resize(job_count_); + problem_numbers_defined = true; + } + break; + } + case JSSP: { + if (words.size() == 2 && !problem_numbers_defined) { + job_count_ = atoi32(words[0]); + machine_count_ = atoi32(words[1]); + CHECK_GT(machine_count_, 0) + << "Number of machines must be greater than 0 on line " + << current_line_nbr_; + CHECK_GT(job_count_, 0) + << "Number of jobs must be greater than 0 on line " + << current_line_nbr_; + VLOG(1) << machine_count_ << " machines and " + << job_count_ << " jobs"; + all_tasks_.resize(job_count_); + problem_numbers_defined = true; + break; + } + + if (words.size() >= 2 && problem_numbers_defined) { + CHECK_EQ(words.size() % 2, 0) + << "Odd number of token on line " + << current_line_nbr_; + VLOG(3) << "job index " << current_job_index_; + const int task_count = words.size() / 2; + for (int i = 0; i < task_count; ++i) { + VLOG(4) << "Task " << i; + const int machine_id = atoi32(words[2 * i]); + const int duration = atoi32(words[2 * i + 1]); + VLOG(4) << "Machine id " << machine_id + << ", duration " << duration; + AddTask(current_job_index_, machine_id, duration); + } + current_job_index_++; + break; + } + break; + } + case TAILLARD: { + switch (taillard_state_) { + case START: { + LOG(FATAL) << "Should not be here"; + break; + } + case JOBS_READ: { + CHECK_EQ(1, words.size()); + machine_count_ = atoi32(words[0]); + CHECK_GT(machine_count_, 0); + taillard_state_ = MACHINES_READ; + break; + } + case MACHINES_READ: { + CHECK_EQ(1, words.size()); + const int seed = atoi32(words[0]); + VLOG(1) << "Taillard instance with " << job_count_ + << " jobs, and " << machine_count_ + << " machines, generated with a seed of " << seed; + taillard_state_ = SEED_READ; + break; + } + case SEED_READ: + case JOB_READ: { + CHECK_EQ(1, words.size()) + << "Only one token allowed on line " << current_line_nbr_; + current_job_index_ = atoi32(words[0]); + VLOG(3) << "job index " << current_job_index_; + taillard_state_ = JOB_ID_READ; + break; + } + case JOB_ID_READ: { + CHECK_EQ(1, words.size()); + taillard_state_ = JOB_LENGTH_READ; + break; + } + case JOB_LENGTH_READ: { + CHECK_EQ(machine_count_, words.size()) + << "Number of tasks must be equal to number of machines on line " + << current_line_nbr_; + for (int i = 0; i < machine_count_; ++i) { + const int duration = atoi32(words[i]); + VLOG(4) << "Machine id " << i << ", duration " << duration; + AddTask(current_job_index_, i, duration); + } + taillard_state_ = JOB_READ; + break; + } + } + break; + } + } + } + + void AddTask(int job_id, int machine_id, int duration) { + all_tasks_[job_id].push_back(Task(job_id, machine_id, duration)); + horizon_ += duration; + } + + std::string name_; + std::string filename_; + int machine_count_; + int job_count_; + int horizon_; + std::vector > all_tasks_; + int current_job_index_; + int current_line_nbr_; + ProblemType problem_type_; + TaillardState taillard_state_; + const char* kWordDelimiters; + bool problem_numbers_defined; +}; +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_JOBSHOP_H_ diff --git a/documentation/tutorials/cplusplus/chap7/jobshop_heuristic.cc b/documentation/tutorials/cplusplus/chap7/jobshop_heuristic.cc new file mode 100644 index 0000000000..6befd3456b --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/jobshop_heuristic.cc @@ -0,0 +1,349 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// A simple program to solve the Job-Shop Problem with Local Search/Large Neighborhood Search. +// See jobshop_ls.h for the Local Search operators. +// +// We use the disjunctive model and specialized IntervalVars and SequenceVars. +// +// Use of two Local Search operators: +// - swap_operator: Exchanging two IntervalVars on a SequenceVar. +// - suffle_operator: Exchanging an arbitratry number of contiguous +// IntervalVars on a SequenceVar. +// +// We also use Local Search to find an initial solution. + + +#include +#include +#include + +#include "base/commandlineflags.h" +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "constraint_solver/constraint_solver.h" +#include "constraint_solver/constraint_solveri.h" +#include "util/string_array.h" +#include "jobshop.h" +#include "jobshop_ls.h" +#include "jobshop_lns.h" +#include "limits.h" + + +DEFINE_string( + data_file, + "", + "Input file with a description of the job-shop problem instance to solve " + "in JSSP or Taillard's format.\n"); + +DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit."); +DEFINE_int32(shuffle_length, 4, "Length of sub-sequences to shuffle LS."); +DEFINE_int64(initial_time_limit_in_ms, 20000, + "Time limit in ms to find the initial solution by LS."); +DEFINE_int32(solutions_nbr_tolerance, 1, + "initial_time_limit_in_ms is applied except if the number of solutions" + "produced since last check is greater of equal to solutions_nbr_tolerance."); +DEFINE_int32(sub_sequence_length, 4, + "Length of sub-sequences to relax in LNS."); +DEFINE_int32(lns_seed, 1, "Seed of the LNS random search"); +DEFINE_int32(lns_limit, 30, + "Limit the size of the search tree in a LNS fragment"); + +namespace operations_research { + +void Jobshop(const JobShopData& data) { + // ************************************************* + // MODEL + // ************************************************* + Solver solver("jobshop"); + const int machine_count = data.machine_count(); + const int job_count = data.job_count(); + const int horizon = data.horizon(); + + // Stores all tasks per job. + std::vector > jobs_to_tasks(job_count); + // Stores all tasks per machine. + std::vector > machines_to_tasks(machine_count); + + // Creates all interval variables. + for (int job_id = 0; job_id < job_count; ++job_id) { + const std::vector& tasks = data.TasksOfJob(job_id); + for (int task_index = 0; task_index < tasks.size(); ++task_index) { + const JobShopData::Task& task = tasks[task_index]; + CHECK_EQ(job_id, task.job_id); + const std::string name = StringPrintf("J%dM%dI%dD%d", + task.job_id, + task.machine_id, + task_index, + task.duration); + IntervalVar* const one_task = + solver.MakeFixedDurationIntervalVar(0, + horizon, + task.duration, + false, + name); + jobs_to_tasks[task.job_id].push_back(one_task); + machines_to_tasks[task.machine_id].push_back(one_task); + } + } + + // Add conjunctive constraintss. + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + if (task_count == 1) {continue;} + for (int task_index = 0; task_index < task_count - 1; ++task_index) { + IntervalVar* const t1 = jobs_to_tasks[job_id][task_index]; + IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1]; + Constraint* const prec = + solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1); + solver.AddConstraint(prec); + } + } + + // Adds disjunctive constraints and creates sequence variables. + std::vector all_sequences; + for (int machine_id = 0; machine_id < machine_count; ++machine_id) { + const std::string name = StringPrintf("Machine_%d", machine_id); + DisjunctiveConstraint* const ct = + solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id], name); + solver.AddConstraint(ct); + all_sequences.push_back(ct->MakeSequenceVar()); + } + + // Creates array of end_times of jobs. + std::vector all_ends; + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + IntervalVar* const task = jobs_to_tasks[job_id][task_count - 1]; + all_ends.push_back(task->EndExpr()->Var()); + } + + // Objective: minimize the makespan (maximum end times of all tasks). + IntVar* const objective_var = solver.MakeMax(all_ends)->Var(); + OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1); + + // ************************************************* + // First solution + // ************************************************* + // This decision builder will rank all tasks on all machines. + DecisionBuilder* const sequence_phase = + solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT); + + // After the ranking of tasks, the schedule is still loose. + // We schedule each task at its earliest start time. + DecisionBuilder* const obj_phase = + solver.MakePhase(objective_var, + Solver::CHOOSE_FIRST_UNBOUND, + Solver::ASSIGN_MIN_VALUE); + + Assignment* const first_solution = solver.MakeAssignment(); + first_solution->Add(all_sequences); + first_solution->AddObjective(objective_var); + // Store the first solution in the 'solution' object. + DecisionBuilder* const first_solution_store_db = + solver.MakeStoreAssignment(first_solution); + + // The main decision builder (ranks all tasks, then fixes the + // objective_variable). + DecisionBuilder* const first_solution_phase = + solver.Compose(sequence_phase, obj_phase, first_solution_store_db); + + LOG(INFO) << "Looking for the first solution to initialize " + "the LS to find the initial solution..."; + const bool first_solution_found = solver.Solve(first_solution_phase); + if (first_solution_found) { + LOG(INFO) << "First solution found with makespan = " + << first_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No first solution found!"; + return; + } + + + // ************************************************* + // Initial solution + // ************************************************* + LOG(INFO) << "Switching to local search to find a good initial solution..."; + + // Swap Operator with shuffle length 2. + LocalSearchOperator* const initial_shuffle_operator = + solver.RevAlloc(new ShuffleIntervals(all_sequences, + 2)); + // Complementary DecisionBuilder. + DecisionBuilder* const random_sequence_phase = + solver.MakePhase(all_sequences, Solver::CHOOSE_RANDOM_RANK_FORWARD); + DecisionBuilder* const complementary_ls_db = + solver.Compose(random_sequence_phase, obj_phase); + + // LS Parameters. + LocalSearchPhaseParameters* const initial_ls_param = + solver.MakeLocalSearchPhaseParameters(initial_shuffle_operator, + complementary_ls_db); + + // LS DecisionBuilder. + DecisionBuilder* const initial_ls_db = + solver.MakeLocalSearchPhase(first_solution, initial_ls_param); + + // Custom SearchLimit + SearchLimit * initial_search_limit = + solver.MakeCustomLimit( + new LSInitialSolLimit(&solver, + FLAGS_initial_time_limit_in_ms, + FLAGS_solutions_nbr_tolerance)); + + Assignment* const initial_solution = solver.MakeAssignment(); + initial_solution->Add(all_sequences); + initial_solution->AddObjective(objective_var); + // Store the initial solution in the 'solution' object. + DecisionBuilder* const initial_solution_store_db = + solver.MakeStoreAssignment(initial_solution); + + DecisionBuilder* const initial_solution_phase = + solver.Compose(initial_ls_db, initial_solution_store_db); + + LOG(INFO) << "Looking for the initial solution..."; + const bool initial_solution_found = + solver.Solve(initial_solution_phase, + objective_monitor, + initial_search_limit); + if (initial_solution_found) { + LOG(INFO) << "Initial solution found with makespan = " + << initial_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No initial solution found!"; + return; + } + + // ************************************************* + // Real Local Search + // ************************************************* + LOG(INFO) << "Switching to local search to find a good solution..."; + std::vector operators; + LOG(INFO) << " - use swap operator"; + LocalSearchOperator* const swap_operator = + solver.RevAlloc(new SwapIntervals(all_sequences)); + operators.push_back(swap_operator); + LOG(INFO) << " - use shuffle operator with a max length of " + << FLAGS_shuffle_length; + LocalSearchOperator* const shuffle_operator = + solver.RevAlloc(new ShuffleIntervals(all_sequences, + FLAGS_shuffle_length)); + operators.push_back(shuffle_operator); + + LOG(INFO) << " - use sequence_lns operator with seed = " + << FLAGS_lns_seed << " and sub sequence length of " << FLAGS_sub_sequence_length; + // SequenceLns Operator. + LocalSearchOperator* const sequence_lns = + solver.RevAlloc(new SequenceLns(all_sequences, + FLAGS_lns_seed, + FLAGS_sub_sequence_length)); + + operators.push_back(sequence_lns); + + // Creates the local search decision builder. + LocalSearchOperator* const ls_concat = + solver.ConcatenateOperators(operators, true); + + SearchLimit* const lns_limit = + solver.MakeLimit(kint64max, FLAGS_lns_limit, kint64max, kint64max); + + DecisionBuilder* const ls_db = + solver.MakeSolveOnce(solver.Compose(random_sequence_phase, obj_phase), lns_limit); + + LocalSearchPhaseParameters* const parameters = + solver.MakeLocalSearchPhaseParameters(ls_concat, ls_db); + DecisionBuilder* const final_db = + solver.MakeLocalSearchPhase(initial_solution, parameters); + + SearchLimit* const limit = FLAGS_time_limit_in_ms > 0 ? + solver.MakeTimeLimit(FLAGS_time_limit_in_ms) : + NULL; + + // Search log. + const int kLogFrequency = 1000000; + SearchMonitor* const search_log = + solver.MakeSearchLog(kLogFrequency, objective_monitor); + +SolutionCollector* const collector = + solver.MakeLastSolutionCollector(); + collector->Add(all_sequences); + collector->AddObjective(objective_var); + // IntervalVar + for (int seq = 0; seq < all_sequences.size(); ++seq) { + const SequenceVar * sequence = all_sequences[seq]; + const int sequence_count = sequence->size(); + for (int i = 0; i < sequence_count; ++i) { + IntervalVar * t = sequence->Interval(i); + collector->Add(t->StartExpr()->Var()); + collector->Add(t->EndExpr()->Var()); + } + } + + std::vector search_monitors; + search_monitors.push_back(search_log); + search_monitors.push_back(objective_monitor); + search_monitors.push_back(limit); + search_monitors.push_back(collector); + +#if defined(__GNUC__) // Linux + SearchLimit * ctrl_catch_limit = MakeCatchCTRLBreakLimit(&solver); + search_monitors.push_back(ctrl_catch_limit); +#endif + + // Search. + if (solver.Solve(final_db, + search_monitors)) { + LOG(INFO) << "Objective value: " << collector->objective_value(0); + for (int m = 0; m < machine_count; ++m) { + SequenceVar* const seq = all_sequences[m]; + std::ostringstream s; + s << seq->name() << ": "; + const std::vector & sequence = collector->ForwardSequence(0, seq); + const int seq_size = sequence.size(); + for (int i = 0; i < seq_size; ++i) { + IntervalVar * t = seq->Interval(sequence[i]); + s << "Job " << sequence[i] << " ("; + s << collector->Value(0, t->StartExpr()->Var()); + s << ","; + s << collector->Value(0, t->EndExpr()->Var()); + s << ") "; + } + s.flush(); + LOG(INFO) << s.str(); + } + } else { + LOG(INFO) << "No solution found..."; + } + return; +} + +} // namespace operations_research + +static const char kUsage[] = +"Usage: jobshop --data_file=instance.txt.\n\n" +"This program solves the job-shop problem in JSSP or " +"Taillard's format with two basic local search operators and Large Neighborhood Search.\n"; + +int main(int argc, char **argv) { + google::SetUsageMessage(kUsage); + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_data_file.empty()) { + LOG(FATAL) << "Please supply a data file with --data_file="; + } + operations_research::JobShopData data(FLAGS_data_file); + operations_research::Jobshop(data); + + return 0; +} diff --git a/documentation/tutorials/cplusplus/chap7/jobshop_lns.cc b/documentation/tutorials/cplusplus/chap7/jobshop_lns.cc new file mode 100644 index 0000000000..44cbb8e301 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/jobshop_lns.cc @@ -0,0 +1,250 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// A simple program to solve the Job-Shop Problem with Large Neighborhood Search. +// See jobshop_lns.h for the LNS operator. +// +// We use the disjunctive model and specialized IntervalVars and SequenceVars. +// +// Freeing contiguous IntervalVars for each SequenceVar or +// freeing two SequenceVar completely. + +#include +#include +#include + +#include "base/commandlineflags.h" +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "constraint_solver/constraint_solver.h" +#include "constraint_solver/constraint_solveri.h" +#include "jobshop_lns.h" +#include "jobshop.h" +#include "util/string_array.h" + + +DEFINE_string( + data_file, + "", + "Input file with a description of the job-shop problem instance to solve " + "in JSSP or Taillard's format.\n"); + +DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit."); +DEFINE_int32(sub_sequence_length, 4, + "Length of sub-sequences to relax in LNS."); +DEFINE_int32(lns_seed, 1, "Seed of the LNS random search"); +DEFINE_int32(lns_limit, 30, + "Limit the size of the search tree in a LNS fragment"); + +namespace operations_research { + +void Jobshop(const JobShopData& data) { + Solver solver("jobshop"); + const int machine_count = data.machine_count(); + const int job_count = data.job_count(); + const int horizon = data.horizon(); + + // Stores all tasks per job. + std::vector > jobs_to_tasks(job_count); + // Stores all tasks per machine. + std::vector > machines_to_tasks(machine_count); + + // Creates all interval variables. + for (int job_id = 0; job_id < job_count; ++job_id) { + const std::vector& tasks = data.TasksOfJob(job_id); + for (int task_index = 0; task_index < tasks.size(); ++task_index) { + const JobShopData::Task& task = tasks[task_index]; + CHECK_EQ(job_id, task.job_id); + const std::string name = StringPrintf("J%dM%dI%dD%d", + task.job_id, + task.machine_id, + task_index, + task.duration); + IntervalVar* const one_task = + solver.MakeFixedDurationIntervalVar(0, + horizon, + task.duration, + false, + name); + jobs_to_tasks[task.job_id].push_back(one_task); + machines_to_tasks[task.machine_id].push_back(one_task); + } + } + + // Add conjunctive constraintss. + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + if (task_count == 1) {continue;} + for (int task_index = 0; task_index < task_count - 1; ++task_index) { + IntervalVar* const t1 = jobs_to_tasks[job_id][task_index]; + IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1]; + Constraint* const prec = + solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1); + solver.AddConstraint(prec); + } + } + + // Adds disjunctive constraints and creates sequence variables. + std::vector all_sequences; + for (int machine_id = 0; machine_id < machine_count; ++machine_id) { + const std::string name = StringPrintf("Machine_%d", machine_id); + DisjunctiveConstraint* const ct = + solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id], name); + solver.AddConstraint(ct); + all_sequences.push_back(ct->MakeSequenceVar()); + } + + // Creates array of end_times of jobs. + std::vector all_ends; + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + IntervalVar* const task = jobs_to_tasks[job_id][task_count - 1]; + all_ends.push_back(task->EndExpr()->Var()); + } + + // Objective: minimize the makespan (maximum end times of all tasks). + IntVar* const objective_var = solver.MakeMax(all_ends)->Var(); + OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1); + + // This decision builder will rank all tasks on all machines. + DecisionBuilder* const sequence_phase = + solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT); + + // After the ranking of tasks, the schedule is still loose. + // We schedule each task at its earliest start time. + DecisionBuilder* const obj_phase = + solver.MakePhase(objective_var, + Solver::CHOOSE_FIRST_UNBOUND, + Solver::ASSIGN_MIN_VALUE); + + Assignment* const first_solution = solver.MakeAssignment(); + first_solution->Add(all_sequences); + first_solution->AddObjective(objective_var); + // Store the first solution in the 'solution' object. + DecisionBuilder* const store_db = solver.MakeStoreAssignment(first_solution); + + // The main decision builder (ranks all tasks, then fixes the + // objective_variable). + DecisionBuilder* const first_solution_phase = + solver.Compose(sequence_phase, obj_phase, store_db); + + LOG(INFO) << "Looking for the first solution"; + const bool first_solution_found = solver.Solve(first_solution_phase); + if (first_solution_found) { + LOG(INFO) << "Solution found with makespan = " + << first_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No initial solution found!"; + return; + } + + LOG(INFO) << "Switching to large neighborhood search"; + + // SequenceLns Operator. + LocalSearchOperator* const sequence_lns = + solver.RevAlloc(new SequenceLns(all_sequences, + FLAGS_lns_seed, + FLAGS_sub_sequence_length)); + + SearchLimit* const lns_limit = + solver.MakeLimit(kint64max, FLAGS_lns_limit, kint64max, kint64max); + + // Complementary DecisionBuilder. + DecisionBuilder* const random_sequence_phase = + solver.MakePhase(all_sequences, Solver::CHOOSE_RANDOM_RANK_FORWARD); + DecisionBuilder* const complementary_ls_db = + solver.MakeSolveOnce(solver.Compose(random_sequence_phase, obj_phase), lns_limit); + + + // LS Parameters. + LocalSearchPhaseParameters* const ls_param = + solver.MakeLocalSearchPhaseParameters(sequence_lns, complementary_ls_db); + + // LS DecisionBuilder. + DecisionBuilder* const ls_db = + solver.MakeLocalSearchPhase(first_solution, ls_param); + + // Search log. + const int kLogFrequency = 1000000; + SearchMonitor* const search_log = + solver.MakeSearchLog(kLogFrequency, objective_monitor); + + SearchLimit* limit = NULL; + if (FLAGS_time_limit_in_ms > 0) { + limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms); + } + + SolutionCollector* const collector = + solver.MakeLastSolutionCollector(); + collector->Add(all_sequences); + collector->AddObjective(objective_var); + // IntervalVar + for (int seq = 0; seq < all_sequences.size(); ++seq) { + const SequenceVar * sequence = all_sequences[seq]; + const int sequence_count = sequence->size(); + for (int i = 0; i < sequence_count; ++i) { + IntervalVar * t = sequence->Interval(i); + collector->Add(t->StartExpr()->Var()); + collector->Add(t->EndExpr()->Var()); + } + } + + // Search. + if (solver.Solve(ls_db, + search_log, + objective_monitor, + limit, + collector)) { + LOG(INFO) << "Objective value: " << collector->objective_value(0); + for (int m = 0; m < machine_count; ++m) { + SequenceVar* const seq = all_sequences[m]; + std::ostringstream s; + s << seq->name() << ": "; + const std::vector & sequence = collector->ForwardSequence(0, seq); + const int seq_size = sequence.size(); + for (int i = 0; i < seq_size; ++i) { + IntervalVar * t = seq->Interval(sequence[i]); + s << "Job " << sequence[i] << " ("; + s << collector->Value(0, t->StartExpr()->Var()); + s << ","; + s << collector->Value(0, t->EndExpr()->Var()); + s << ") "; + } + s.flush(); + LOG(INFO) << s.str(); + } + } else { + LOG(INFO) << "No solution found..."; + } +} + +} // namespace operations_research + +static const char kUsage[] = +"Usage: jobshop --data_file=instance.txt.\n\n" +"This program solves the Job-Shop Problem in JSSP or" +"Taillard's format with a basic swap operator and Large Neighborhood Search.\n"; + +int main(int argc, char **argv) { + google::SetUsageMessage(kUsage); + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_data_file.empty()) { + LOG(FATAL) << "Please supply a data file with --data_file="; + } + operations_research::JobShopData data(FLAGS_data_file); + operations_research::Jobshop(data); + + return 0; +} diff --git a/documentation/tutorials/cplusplus/chap7/jobshop_lns.h b/documentation/tutorials/cplusplus/chap7/jobshop_lns.h new file mode 100644 index 0000000000..bcfac1191b --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/jobshop_lns.h @@ -0,0 +1,91 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// A basic Large Neighborhood Search operator for +// the Job-Shop Problem. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_JOBSHOP_LNS_H_ +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_JOBSHOP_LNS_H_ + +#include +#include + +#include "constraint_solver/constraint_solver.h" +#include "constraint_solver/constraint_solveri.h" + +namespace operations_research { + +class SequenceLns : public SequenceVarLocalSearchOperator { + public: + SequenceLns(const std::vector& vars, + int seed, + int max_length) + : SequenceVarLocalSearchOperator(vars), + random_(seed), + max_length_(max_length) {} + + virtual ~SequenceLns() {} + + virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { + CHECK_NOTNULL(delta); + while (true) { + RevertChanges(true); + if (random_.Uniform(2) == 0) { + FreeTimeWindow(); + } else { + FreeTwoResources(); + } + if (ApplyChanges(delta, deltadelta)) { + VLOG(1) << "Delta = " << delta->DebugString(); + return true; + } + } + return false; + } + + private: + void FreeTimeWindow() { + for (int i = 0; i < Size(); ++i) { + std::vector sequence = Sequence(i); + const int current_length = + std::min(static_cast(sequence.size()), max_length_); + const int start_position = + random_.Uniform(sequence.size() - current_length); + std::vector forward; + for (int j = 0; j < start_position; ++j) { + forward.push_back(sequence[j]); + } + std::vector backward; + for (int j = sequence.size() - 1; + j >= start_position + current_length; + --j) { + backward.push_back(sequence[j]); + } + SetForwardSequence(i, forward); + SetBackwardSequence(i, backward); + } + } + + void FreeTwoResources() { + std::vector free_sequence; + SetForwardSequence(random_.Uniform(Size()), free_sequence); + SetForwardSequence(random_.Uniform(Size()), free_sequence); + } + + ACMRandom random_; + const int max_length_; +}; + +} // namespace operations_research +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_JOBSHOP_LNS_H_ \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/chap7/jobshop_ls.h b/documentation/tutorials/cplusplus/chap7/jobshop_ls.h new file mode 100644 index 0000000000..261eb6ca21 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/jobshop_ls.h @@ -0,0 +1,184 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Two basic LocalSearchOperators for the job-shop problem. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_JOBSHOP_LS_H_ +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_JOBSHOP_LS_H_ + +#include +#include +#include +#include + +#include "base/commandlineflags.h" +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/bitmap.h" +#include "constraint_solver/constraint_solver.h" +#include "constraint_solver/constraint_solveri.h" + +namespace operations_research { + + // ----- Exchange 2 intervals on a sequence variable ----- +class SwapIntervals : public SequenceVarLocalSearchOperator { + public: + SwapIntervals(const std::vector& vars) + : SequenceVarLocalSearchOperator(vars), + current_var_(-1), + current_first_(-1), + current_second_(-1) {} + + virtual ~SwapIntervals() {} + + virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { + CHECK_NOTNULL(delta); + while (true) { + RevertChanges(true); + if (!Increment()) { + VLOG(1) << "End neighborhood search"; + return false; + } + + std::vector sequence = Sequence(current_var_); + const int tmp = sequence[current_first_]; + sequence[current_first_] = sequence[current_second_]; + sequence[current_second_] = tmp; + SetForwardSequence(current_var_, sequence); + if (ApplyChanges(delta, deltadelta)) { + VLOG(1) << "Delta = " << delta->DebugString(); + return true; + } + } + return false; + } + + protected: + virtual void OnStart() { + VLOG(1) << "Start neighborhood search"; + current_var_ = 0; + current_first_ = 0; + current_second_ = 0; + } + + private: + bool Increment() { + const SequenceVar* const var = Var(current_var_); + if (++current_second_ >= var->size()) { + if (++current_first_ >= var->size() - 1) { + current_var_++; + current_first_ = 0; + } + current_second_ = current_first_ + 1; + } + return current_var_ < Size(); + } + + int current_var_; + int current_first_; + int current_second_; +}; + +// ----- Shuffle a fixed-length sub-sequence on one sequence variable ----- +class ShuffleIntervals : public SequenceVarLocalSearchOperator { + public: + ShuffleIntervals(const std::vector& vars, const int64 max_length) + : SequenceVarLocalSearchOperator(vars), + max_length_(max_length), + current_var_(-1), + current_first_(-1), + current_length_(-1) { + CHECK_GE(max_length_, 2) + << "The suffle length should be greater or equal to 2."; + } + + virtual ~ShuffleIntervals() {} + + virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { + CHECK_NOTNULL(delta); + while (true) { + RevertChanges(true); + if (!Increment()) { + VLOG(1) << "Finish neighborhood search"; + return false; + } + + std::vector sequence = Sequence(current_var_); + std::vector sequence_backup(current_length_); + + for (int i = 0; i < current_length_; ++i) { + sequence_backup[i] = sequence[i + current_first_]; + } + for (int i = 0; i < current_length_; ++i) { + sequence[i + current_first_] = + sequence_backup[current_permutation_[i]]; + } + + SetForwardSequence(current_var_, sequence); + if (ApplyChanges(delta, deltadelta)) { + VLOG(1) << "Delta = " << delta->DebugString(); + return true; + } + } + return false; + } + + protected: + virtual void OnStart() { + VLOG(1) << "Start neighborhood search"; + current_var_ = 0; + current_first_ = 0; + current_length_ = std::min(Var(current_var_)->size(), max_length_); + current_permutation_.resize(current_length_); + for (int i = 0; i < current_length_; ++i) { + current_permutation_[i] = i; + } + } + + private: + bool Increment() { + if (!std::next_permutation(current_permutation_.begin(), + current_permutation_.end())) { + // No permutation anymore -> update indices + if (++current_first_ > Var(current_var_)->size() - current_length_) { + if (++current_var_ >= Size()) { + return false; + } + current_first_ = 0; + current_length_ = std::min(Var(current_var_)->size(), max_length_); + current_permutation_.resize(current_length_); + } + for (int i = 0; i < current_length_; ++i) { + current_permutation_[i] = i; + } + // start with the next permutation, not the identity just constructed + if (!std::next_permutation(current_permutation_.begin(), + current_permutation_.end())) { + LOG(FATAL) << "Should never happen!"; + } + } + return true; + } + + const int64 max_length_; + int current_var_; + int current_first_; + int current_length_; + std::vector current_permutation_; +}; + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_JOBSHOP_LS_H_ diff --git a/documentation/tutorials/cplusplus/chap7/jobshop_sa1.cc b/documentation/tutorials/cplusplus/chap7/jobshop_sa1.cc new file mode 100644 index 0000000000..d8ec13b9d7 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/jobshop_sa1.cc @@ -0,0 +1,273 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// A simple program to solve the Job-Shop Problem with Local Search and Simulated Annealing. +// See jobshop_ls.h for the local operators. +// +// We use the disjunctive model and specialized IntervalVars and SequenceVars. +// +// Exchanging two IntervalVars on a SequenceVar. + +#include +#include +#include + +#include "base/commandlineflags.h" +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "constraint_solver/constraint_solver.h" +#include "constraint_solver/constraint_solveri.h" +#include "jobshop.h" +#include "jobshop_ls.h" +#include "limits.h" +#include "util/string_array.h" + +DEFINE_string( + data_file, + "", + "Input file with a description of the job-shop problem instance to solve " + "in JSSP or Taillard's format.\n"); + +DEFINE_int64(solution_nbr_tolerance, 30, "Number of solutions without improvement"); +DEFINE_int64(initial_temperature, 30, "Initial temperature"); + +DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit."); +DEFINE_bool(print_solution, false, "Print best solution or not"); + +namespace operations_research { + +void Jobshop(const JobShopData& data) { + Solver solver("jobshop"); + const int machine_count = data.machine_count(); + const int job_count = data.job_count(); + const int horizon = data.horizon(); + + // Stores all tasks per job. + std::vector > jobs_to_tasks(job_count); + // Stores all tasks per machine. + std::vector > machines_to_tasks(machine_count); + + // Creates all interval variables. + for (int job_id = 0; job_id < job_count; ++job_id) { + const std::vector& tasks = data.TasksOfJob(job_id); + for (int task_index = 0; task_index < tasks.size(); ++task_index) { + const JobShopData::Task& task = tasks[task_index]; + CHECK_EQ(job_id, task.job_id); + const std::string name = StringPrintf("J%dM%dI%dD%d", + task.job_id, + task.machine_id, + task_index, + task.duration); + IntervalVar* const one_task = + solver.MakeFixedDurationIntervalVar(0, + horizon, + task.duration, + false, + name); + jobs_to_tasks[task.job_id].push_back(one_task); + machines_to_tasks[task.machine_id].push_back(one_task); + } + } + + // Add conjunctive constraintss. + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + if (task_count == 1) {continue;} + for (int task_index = 0; task_index < task_count - 1; ++task_index) { + IntervalVar* const t1 = jobs_to_tasks[job_id][task_index]; + IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1]; + Constraint* const prec = + solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1); + solver.AddConstraint(prec); + } + } + + // Adds disjunctive constraints and creates sequence variables. + std::vector all_sequences; + for (int machine_id = 0; machine_id < machine_count; ++machine_id) { + const std::string name = StringPrintf("Machine_%d", machine_id); + DisjunctiveConstraint* const ct = + solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id], name); + solver.AddConstraint(ct); + all_sequences.push_back(ct->MakeSequenceVar()); + } + + // Creates array of end_times of jobs. + std::vector all_ends; + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + IntervalVar* const task = jobs_to_tasks[job_id][task_count - 1]; + all_ends.push_back(task->EndExpr()->Var()); + } + + // Objective: minimize the makespan (maximum end times of all tasks). + IntVar* const objective_var = solver.MakeMax(all_ends)->Var(); + + // Tabu variables + std::vector tabu_vars; + for (int seq = 0; seq < all_sequences.size(); ++seq) { + SequenceVar * seq_var = all_sequences[seq]; + for (int interval = 0; interval < seq_var->size(); ++interval ) { + IntVar * next = seq_var->Next(interval); + tabu_vars.push_back(next); + } + } + + // This decision builder will rank all tasks on all machines. + DecisionBuilder* const sequence_phase = + solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT); + + // After the ranking of tasks, the schedule is still loose. + // We schedule each task at its earliest start time. + DecisionBuilder* const obj_phase = + solver.MakePhase(objective_var, + Solver::CHOOSE_FIRST_UNBOUND, + Solver::ASSIGN_MIN_VALUE); + + Assignment* const first_solution = solver.MakeAssignment(); + first_solution->Add(all_sequences); + first_solution->AddObjective(objective_var); + // Store the first solution in the 'solution' object. + DecisionBuilder* const store_db = solver.MakeStoreAssignment(first_solution); + + // The main decision builder (ranks all tasks, then fixes the + // objective_variable). + DecisionBuilder* const first_solution_phase = + solver.Compose(sequence_phase, obj_phase, store_db); + + LOG(INFO) << "Looking for the first solution"; + const bool first_solution_found = solver.Solve(first_solution_phase); + if (first_solution_found) { + LOG(INFO) << "Solution found with makespan = " + << first_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No initial solution found!"; + return; + } + + LOG(INFO) << "Switching to local search"; + + // Swap Operator. + LocalSearchOperator* const swap_operator = + solver.RevAlloc(new SwapIntervals(all_sequences)); + // Complementary DecisionBuilder. + DecisionBuilder* const random_sequence_phase = + solver.MakePhase(all_sequences, Solver::CHOOSE_RANDOM_RANK_FORWARD); + DecisionBuilder* const complementary_ls_db = + solver.MakeSolveOnce(solver.Compose(random_sequence_phase, obj_phase)); + // solver.Compose(random_sequence_phase, obj_phase); + + // LS Parameters. + LocalSearchPhaseParameters* const ls_param = + solver.MakeLocalSearchPhaseParameters(swap_operator, complementary_ls_db); + + // LS DecisionBuilder. + DecisionBuilder* const ls_db = + solver.MakeLocalSearchPhase(first_solution, ls_param); + + // Search log. + const int kLogFrequency = 1000000; + SearchMonitor* const search_log = + solver.MakeSearchLog(kLogFrequency, objective_var); + + SolutionCollector* const collector = + solver.MakeBestValueSolutionCollector(false); + collector->Add(all_sequences); + collector->AddObjective(objective_var); + // IntervalVar + for (int seq = 0; seq < all_sequences.size(); ++seq) { + const SequenceVar * sequence = all_sequences[seq]; + const int sequence_count = sequence->size(); + for (int i = 0; i < sequence_count; ++i) { + IntervalVar * t = sequence->Interval(i); + collector->Add(t->StartExpr()->Var()); + collector->Add(t->EndExpr()->Var()); + } + } + + SearchMonitor * simulated_annealing = solver.MakeSimulatedAnnealing(false, + objective_var, + 1, + FLAGS_initial_temperature); + + SearchLimit * const no_improvement_limit = MakeNoImprovementLimit(&solver, objective_var, FLAGS_solution_nbr_tolerance); + SearchLimit * ctrl_catch_limit = nullptr; + +#if defined(__GNUC__) // Linux + ctrl_catch_limit = MakeCatchCTRLBreakLimit(&solver); +#endif + SearchLimit* time_limit = nullptr; + if (FLAGS_time_limit_in_ms > 0) { + time_limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms); + } + + std::vector search_monitors; + search_monitors.push_back(search_log); + search_monitors.push_back(simulated_annealing); + search_monitors.push_back(no_improvement_limit); + if (ctrl_catch_limit != nullptr) { + search_monitors.push_back(ctrl_catch_limit); + } + if (time_limit != nullptr) { + search_monitors.push_back(time_limit); + } + search_monitors.push_back(collector); + + // Search. + if (solver.Solve(ls_db, + search_monitors)) { + LOG(INFO) << "Best objective value: " << collector->objective_value(0); + if (FLAGS_print_solution) { + for (int m = 0; m < machine_count; ++m) { + SequenceVar* const seq = all_sequences[m]; + std::ostringstream s; + s << seq->name() << ": "; + const std::vector & sequence = collector->ForwardSequence(0, seq); + const int seq_size = sequence.size(); + for (int i = 0; i < seq_size; ++i) { + IntervalVar * t = seq->Interval(sequence[i]); + s << "Job " << sequence[i] << " ("; + s << collector->Value(0, t->StartExpr()->Var()); + s << ","; + s << collector->Value(0, t->EndExpr()->Var()); + s << ") "; + } + s.flush(); + LOG(INFO) << s.str(); + } + } + } else { + LOG(INFO) << "No solution found..."; + } +} + +} // namespace operations_research + +static const char kUsage[] = +"Usage: jobshop --data_file=instance.txt.\n\n" +"This program solves the job-shop problem in JSSP or" +"Taillard's format with a basic swap operator and Local Search and Simulated Annealing.\n"; + +int main(int argc, char **argv) { + google::SetUsageMessage(kUsage); + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_data_file.empty()) { + LOG(FATAL) << "Please supply a data file with --data_file="; + } + operations_research::JobShopData data(FLAGS_data_file); + operations_research::Jobshop(data); + + return 0; +} diff --git a/documentation/tutorials/cplusplus/chap7/jobshop_sa2.cc b/documentation/tutorials/cplusplus/chap7/jobshop_sa2.cc new file mode 100644 index 0000000000..cdc001453d --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/jobshop_sa2.cc @@ -0,0 +1,352 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// A simple program to solve the Job-Shop Problem with Local Search and Simulated Annealing. +// See jobshop_ls.h for the local operators. +// +// We use the disjunctive model and specialized IntervalVars and SequenceVars. +// +// Use of two local search operators: +// - swap_operator: Exchanging two IntervalVars on a SequenceVar. +// - suffle_operator: Exchanging an arbitratry number of contiguous +// IntervalVars on a SequenceVar. +// +// We also use Local Search to find an initial solution. + + +#include +#include +#include + +#include "base/commandlineflags.h" +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "constraint_solver/constraint_solver.h" +#include "constraint_solver/constraint_solveri.h" +#include "util/string_array.h" +#include "jobshop.h" +#include "jobshop_ls.h" +#include "limits.h" + + +DEFINE_string( + data_file, + "", + "Input file with a description of the job-shop problem instance to solve " + "in JSSP or Taillard's format.\n"); + +DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit."); +DEFINE_int32(shuffle_length, 4, "Length of sub-sequences to shuffle LS."); +DEFINE_int64(initial_time_limit_in_ms, 20000, + "Time limit in ms to find the initial solution by LS."); +DEFINE_int32(solutions_nbr_tolerance, 1, + "initial_time_limit_in_ms is applied except if the number of solutions" + "produced since last check is greater of equal to solutions_nbr_tolerance."); +DEFINE_int32(global_solution_nbr_tolerance, 30, "Number of solutions without improvement in the global Local Search"); +DEFINE_int64(initial_temperature, 30, "Initial temperature"); + +namespace operations_research { + +void Jobshop(const JobShopData& data) { + // ************************************************* + // MODEL + // ************************************************* + Solver solver("jobshop"); + const int machine_count = data.machine_count(); + const int job_count = data.job_count(); + const int horizon = data.horizon(); + + // Stores all tasks per job. + std::vector > jobs_to_tasks(job_count); + // Stores all tasks per machine. + std::vector > machines_to_tasks(machine_count); + + // Creates all interval variables. + for (int job_id = 0; job_id < job_count; ++job_id) { + const std::vector& tasks = data.TasksOfJob(job_id); + for (int task_index = 0; task_index < tasks.size(); ++task_index) { + const JobShopData::Task& task = tasks[task_index]; + CHECK_EQ(job_id, task.job_id); + const std::string name = StringPrintf("J%dM%dI%dD%d", + task.job_id, + task.machine_id, + task_index, + task.duration); + IntervalVar* const one_task = + solver.MakeFixedDurationIntervalVar(0, + horizon, + task.duration, + false, + name); + jobs_to_tasks[task.job_id].push_back(one_task); + machines_to_tasks[task.machine_id].push_back(one_task); + } + } + + // Add conjunctive constraintss. + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + if (task_count == 1) {continue;} + for (int task_index = 0; task_index < task_count - 1; ++task_index) { + IntervalVar* const t1 = jobs_to_tasks[job_id][task_index]; + IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1]; + Constraint* const prec = + solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1); + solver.AddConstraint(prec); + } + } + + // Adds disjunctive constraints and creates sequence variables. + std::vector all_sequences; + for (int machine_id = 0; machine_id < machine_count; ++machine_id) { + const std::string name = StringPrintf("Machine_%d", machine_id); + DisjunctiveConstraint* const ct = + solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id], name); + solver.AddConstraint(ct); + all_sequences.push_back(ct->MakeSequenceVar()); + } + + // Creates array of end_times of jobs. + std::vector all_ends; + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + IntervalVar* const task = jobs_to_tasks[job_id][task_count - 1]; + all_ends.push_back(task->EndExpr()->Var()); + } + + // Objective: minimize the makespan (maximum end times of all tasks). + IntVar* const objective_var = solver.MakeMax(all_ends)->Var(); + OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1); + + // ************************************************* + // First solution + // ************************************************* + // This decision builder will rank all tasks on all machines. + DecisionBuilder* const sequence_phase = + solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT); + + // After the ranking of tasks, the schedule is still loose. + // We schedule each task at its earliest start time. + DecisionBuilder* const obj_phase = + solver.MakePhase(objective_var, + Solver::CHOOSE_FIRST_UNBOUND, + Solver::ASSIGN_MIN_VALUE); + + Assignment* const first_solution = solver.MakeAssignment(); + first_solution->Add(all_sequences); + first_solution->AddObjective(objective_var); + // Store the first solution in the 'solution' object. + DecisionBuilder* const first_solution_store_db = + solver.MakeStoreAssignment(first_solution); + + // The main decision builder (ranks all tasks, then fixes the + // objective_variable). + DecisionBuilder* const first_solution_phase = + solver.Compose(sequence_phase, obj_phase, first_solution_store_db); + + LOG(INFO) << "Looking for the first solution to initialize " + "the LS to find the initial solution..."; + const bool first_solution_found = solver.Solve(first_solution_phase); + if (first_solution_found) { + LOG(INFO) << "First solution found with makespan = " + << first_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No first solution found!"; + return; + } + + // ************************************************* + // Initial solution + // ************************************************* + LOG(INFO) << "Switching to local search to find a good initial solution..."; + + // Swap Operator with shuffle length 2. + LocalSearchOperator* const initial_shuffle_operator = + solver.RevAlloc(new ShuffleIntervals(all_sequences, + 2)); + // Complementary DecisionBuilder. + DecisionBuilder* const random_sequence_phase = + solver.MakePhase(all_sequences, Solver::CHOOSE_RANDOM_RANK_FORWARD); + DecisionBuilder* const complementary_ls_db = + solver.Compose(random_sequence_phase, obj_phase); + + // LS Parameters. + LocalSearchPhaseParameters* const initial_ls_param = + solver.MakeLocalSearchPhaseParameters(initial_shuffle_operator, + complementary_ls_db); + + // LS DecisionBuilder. + DecisionBuilder* const initial_ls_db = + solver.MakeLocalSearchPhase(first_solution, initial_ls_param); + + // Custom SearchLimit + SearchLimit * initial_search_limit = + solver.MakeCustomLimit( + new LSInitialSolLimit(&solver, + FLAGS_initial_time_limit_in_ms, + FLAGS_solutions_nbr_tolerance)); + + Assignment* const initial_solution = solver.MakeAssignment(); + initial_solution->Add(all_sequences); + initial_solution->AddObjective(objective_var); + // Store the initial solution in the 'solution' object. + DecisionBuilder* const initial_solution_store_db = + solver.MakeStoreAssignment(initial_solution); + + DecisionBuilder* const initial_solution_phase = + solver.Compose(initial_ls_db, initial_solution_store_db); + + LOG(INFO) << "Looking for the initial solution..."; + const bool initial_solution_found = + solver.Solve(initial_solution_phase, + objective_monitor, + initial_search_limit); + if (initial_solution_found) { + LOG(INFO) << "Initial solution found with makespan = " + << initial_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No initial solution found!"; + return; + } + + // ************************************************* + // Real Local Search with two operators + // ************************************************* + LOG(INFO) << "Switching to local search to find a good solution..."; + std::vector operators; + LOG(INFO) << " - use swap operator"; + LocalSearchOperator* const swap_operator = + solver.RevAlloc(new SwapIntervals(all_sequences)); + operators.push_back(swap_operator); + LOG(INFO) << " - use shuffle operator with a max length of " + << FLAGS_shuffle_length; + LocalSearchOperator* const shuffle_operator = + solver.RevAlloc(new ShuffleIntervals(all_sequences, + FLAGS_shuffle_length)); + operators.push_back(shuffle_operator); + + + // Creates the local search decision builder. + LocalSearchOperator* const ls_concat = + solver.ConcatenateOperators(operators, true); + + + DecisionBuilder* const ls_db = + solver.Compose(random_sequence_phase, obj_phase); + + LocalSearchPhaseParameters* const parameters = + solver.MakeLocalSearchPhaseParameters(ls_concat, ls_db); + DecisionBuilder* const final_db = + solver.MakeLocalSearchPhase(initial_solution, parameters); + + + SearchLimit* const limit = FLAGS_time_limit_in_ms > 0 ? + solver.MakeTimeLimit(FLAGS_time_limit_in_ms) : + NULL; + + // Search log. + const int kLogFrequency = 1000000; + SearchMonitor* const search_log = + solver.MakeSearchLog(kLogFrequency, objective_monitor); + +SolutionCollector* const collector = + solver.MakeBestValueSolutionCollector(false); + collector->Add(all_sequences); + collector->AddObjective(objective_var); + // IntervalVar + for (int seq = 0; seq < all_sequences.size(); ++seq) { + const SequenceVar * sequence = all_sequences[seq]; + const int sequence_count = sequence->size(); + for (int i = 0; i < sequence_count; ++i) { + IntervalVar * t = sequence->Interval(i); + collector->Add(t->StartExpr()->Var()); + collector->Add(t->EndExpr()->Var()); + } + } + + SearchMonitor * simulated_annealing = solver.MakeSimulatedAnnealing(false, + objective_var, + 1, + FLAGS_initial_temperature); + + SearchLimit * const no_improvement_limit = MakeNoImprovementLimit(&solver, objective_var, FLAGS_global_solution_nbr_tolerance); + SearchLimit * ctrl_catch_limit = nullptr; + +#if defined(__GNUC__) // Linux + ctrl_catch_limit = MakeCatchCTRLBreakLimit(&solver); +#endif + + SearchLimit* time_limit = nullptr; + if (FLAGS_time_limit_in_ms > 0) { + time_limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms); + } + + std::vector search_monitors; + search_monitors.push_back(search_log); + search_monitors.push_back(simulated_annealing); + search_monitors.push_back(no_improvement_limit); + if (ctrl_catch_limit != nullptr) { + search_monitors.push_back(ctrl_catch_limit); + } + if (time_limit != nullptr) { + search_monitors.push_back(time_limit); + } + search_monitors.push_back(collector); + + // Search. + if (solver.Solve(final_db, + search_monitors)) { + LOG(INFO) << "Objective value: " << collector->objective_value(0); + for (int m = 0; m < machine_count; ++m) { + SequenceVar* const seq = all_sequences[m]; + std::ostringstream s; + s << seq->name() << ": "; + const std::vector & sequence = collector->ForwardSequence(0, seq); + const int seq_size = sequence.size(); + for (int i = 0; i < seq_size; ++i) { + IntervalVar * t = seq->Interval(sequence[i]); + s << "Job " << sequence[i] << " ("; + s << collector->Value(0, t->StartExpr()->Var()); + s << ","; + s << collector->Value(0, t->EndExpr()->Var()); + s << ") "; + } + s.flush(); + LOG(INFO) << s.str(); + } + } else { + LOG(INFO) << "No solution found..."; + } + return; +} + +} // namespace operations_research + +static const char kUsage[] = +"Usage: jobshop --data_file=instance.txt.\n\n" +"This program solves the job-shop problem in JSSP or " +"Taillard's format with two basic local search operators and Local Search and Simulated Annealing.\n"; + +int main(int argc, char **argv) { + google::SetUsageMessage(kUsage); + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_data_file.empty()) { + LOG(FATAL) << "Please supply a data file with --data_file="; + } + operations_research::JobShopData data(FLAGS_data_file); + operations_research::Jobshop(data); + + return 0; +} diff --git a/documentation/tutorials/cplusplus/chap7/jobshop_ts1.cc b/documentation/tutorials/cplusplus/chap7/jobshop_ts1.cc new file mode 100644 index 0000000000..c6c2bc4da6 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/jobshop_ts1.cc @@ -0,0 +1,278 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// A simple program to solve the Job-Shop Problem with Local Search and Tabu Search. +// See jobshop_ls.h for the Local Search operators. +// +// We use the disjunctive model and specialized IntervalVars and SequenceVars. +// +// Exchanging two IntervalVars on a SequenceVar. + + +#include +#include +#include + +#include "base/commandlineflags.h" +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "constraint_solver/constraint_solver.h" +#include "constraint_solver/constraint_solveri.h" +#include "util/string_array.h" +#include "jobshop.h" +#include "jobshop_ls.h" +#include "limits.h" + +DEFINE_string( + data_file, + "", + "Input file with a description of the Job-Shop Problem instance to solve " + "in JSSP or Taillard's format.\n"); + +DEFINE_int32(solution_nbr_tolerance, 30, "Number of solutions without improvement"); + +DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit."); +DEFINE_int64(keep_tenure, 10, "Keep Tabu tenure"); +DEFINE_int64(forbid_tenure, 5, "Forbid Tabu tenure"); +DEFINE_double(tabu_factor, 1.0, "Tabu factor (percentage)"); +DEFINE_bool(print_solution, false, "Print best solution or not"); + +namespace operations_research { + +void Jobshop(const JobShopData& data) { + Solver solver("jobshop"); + const int machine_count = data.machine_count(); + const int job_count = data.job_count(); + const int horizon = data.horizon(); + + // Stores all tasks per job. + std::vector > jobs_to_tasks(job_count); + // Stores all tasks per machine. + std::vector > machines_to_tasks(machine_count); + + // Creates all interval variables. + for (int job_id = 0; job_id < job_count; ++job_id) { + const std::vector& tasks = data.TasksOfJob(job_id); + for (int task_index = 0; task_index < tasks.size(); ++task_index) { + const JobShopData::Task& task = tasks[task_index]; + CHECK_EQ(job_id, task.job_id); + const std::string name = StringPrintf("J%dM%dI%dD%d", + task.job_id, + task.machine_id, + task_index, + task.duration); + IntervalVar* const one_task = + solver.MakeFixedDurationIntervalVar(0, + horizon, + task.duration, + false, + name); + jobs_to_tasks[task.job_id].push_back(one_task); + machines_to_tasks[task.machine_id].push_back(one_task); + } + } + + // Add conjunctive constraintss. + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + if (task_count == 1) {continue;} + for (int task_index = 0; task_index < task_count - 1; ++task_index) { + IntervalVar* const t1 = jobs_to_tasks[job_id][task_index]; + IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1]; + Constraint* const prec = + solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1); + solver.AddConstraint(prec); + } + } + + // Adds disjunctive constraints and creates sequence variables. + std::vector all_sequences; + for (int machine_id = 0; machine_id < machine_count; ++machine_id) { + const std::string name = StringPrintf("Machine_%d", machine_id); + DisjunctiveConstraint* const ct = + solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id], name); + solver.AddConstraint(ct); + all_sequences.push_back(ct->MakeSequenceVar()); + } + + // Creates array of end_times of jobs. + std::vector all_ends; + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + IntervalVar* const task = jobs_to_tasks[job_id][task_count - 1]; + all_ends.push_back(task->EndExpr()->Var()); + } + + // Objective: minimize the makespan (maximum end times of all tasks). + IntVar* const objective_var = solver.MakeMax(all_ends)->Var(); + + // Tabu variables + std::vector tabu_vars; + for (int seq = 0; seq < all_sequences.size(); ++seq) { + SequenceVar * seq_var = all_sequences[seq]; + for (int interval = 0; interval < seq_var->size(); ++interval ) { + IntVar * next = seq_var->Next(interval); + tabu_vars.push_back(next); + } + } + + // This decision builder will rank all tasks on all machines. + DecisionBuilder* const sequence_phase = + solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT); + + // After the ranking of tasks, the schedule is still loose. + // We schedule each task at its earliest start time. + DecisionBuilder* const obj_phase = + solver.MakePhase(objective_var, + Solver::CHOOSE_FIRST_UNBOUND, + Solver::ASSIGN_MIN_VALUE); + + Assignment* const first_solution = solver.MakeAssignment(); + first_solution->Add(all_sequences); + first_solution->AddObjective(objective_var); + // Store the first solution in the 'solution' object. + DecisionBuilder* const store_db = solver.MakeStoreAssignment(first_solution); + + // The main decision builder (ranks all tasks, then fixes the + // objective_variable). + DecisionBuilder* const first_solution_phase = + solver.Compose(sequence_phase, obj_phase, store_db); + + LOG(INFO) << "Looking for the first solution"; + const bool first_solution_found = solver.Solve(first_solution_phase); + if (first_solution_found) { + LOG(INFO) << "Solution found with makespan = " + << first_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No initial solution found!"; + return; + } + + LOG(INFO) << "Switching to local search"; + + // Swap Operator. + LocalSearchOperator* const swap_operator = + solver.RevAlloc(new SwapIntervals(all_sequences)); + // Complementary DecisionBuilder. + DecisionBuilder* const random_sequence_phase = + solver.MakePhase(all_sequences, Solver::CHOOSE_RANDOM_RANK_FORWARD); + DecisionBuilder* const complementary_ls_db = + solver.MakeSolveOnce(solver.Compose(random_sequence_phase, obj_phase)); + + // LS Parameters. + LocalSearchPhaseParameters* const ls_param = + solver.MakeLocalSearchPhaseParameters(swap_operator, complementary_ls_db); + + // LS DecisionBuilder. + DecisionBuilder* const ls_db = + solver.MakeLocalSearchPhase(first_solution, ls_param); + + // Search log. + const int kLogFrequency = 1000000; + SearchMonitor* const search_log = + solver.MakeSearchLog(kLogFrequency, objective_var); + + SolutionCollector* const collector = + solver.MakeBestValueSolutionCollector(false); + collector->Add(all_sequences); + collector->AddObjective(objective_var); + // IntervalVar + for (int seq = 0; seq < all_sequences.size(); ++seq) { + const SequenceVar * sequence = all_sequences[seq]; + const int sequence_count = sequence->size(); + for (int i = 0; i < sequence_count; ++i) { + IntervalVar * t = sequence->Interval(i); + collector->Add(t->StartExpr()->Var()); + collector->Add(t->EndExpr()->Var()); + } + } + + SearchMonitor * tabu_search = solver.MakeTabuSearch(false, + objective_var, + 1, + tabu_vars, + FLAGS_keep_tenure, + FLAGS_forbid_tenure, + FLAGS_tabu_factor); + + SearchLimit * const no_improvement_limit = MakeNoImprovementLimit(&solver, objective_var, FLAGS_solution_nbr_tolerance); + SearchLimit * ctrl_catch_limit = nullptr; + +#if defined(__GNUC__) // Linux + ctrl_catch_limit = MakeCatchCTRLBreakLimit(&solver); +#endif + SearchLimit* time_limit = nullptr; + if (FLAGS_time_limit_in_ms > 0) { + time_limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms); + } + + std::vector search_monitors; + search_monitors.push_back(search_log); + search_monitors.push_back(tabu_search); + search_monitors.push_back(no_improvement_limit); + if (ctrl_catch_limit != nullptr) { + search_monitors.push_back(ctrl_catch_limit); + } + if (time_limit != nullptr) { + search_monitors.push_back(time_limit); + } + search_monitors.push_back(collector); + + // Search. + if (solver.Solve(ls_db, + search_monitors)) { + LOG(INFO) << "Best objective value: " << collector->objective_value(0); + if (FLAGS_print_solution) { + for (int m = 0; m < machine_count; ++m) { + SequenceVar* const seq = all_sequences[m]; + std::ostringstream s; + s << seq->name() << ": "; + const std::vector & sequence = collector->ForwardSequence(0, seq); + const int seq_size = sequence.size(); + for (int i = 0; i < seq_size; ++i) { + IntervalVar * t = seq->Interval(sequence[i]); + s << "Job " << sequence[i] << " ("; + s << collector->Value(0, t->StartExpr()->Var()); + s << ","; + s << collector->Value(0, t->EndExpr()->Var()); + s << ") "; + } + s.flush(); + LOG(INFO) << s.str(); + } + } + } else { + LOG(INFO) << "No solution found..."; + } +} + +} // namespace operations_research + +static const char kUsage[] = +"Usage: jobshop --data_file=instance.txt.\n\n" +"This program solves the job-shop problem in JSSP or" +"Taillard's format with a basic swap operator, Local Search and Tabu Search.\n"; + +int main(int argc, char **argv) { + google::SetUsageMessage(kUsage); + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_data_file.empty()) { + LOG(FATAL) << "Please supply a data file with --data_file="; + } + operations_research::JobShopData data(FLAGS_data_file); + operations_research::Jobshop(data); + + return 0; +} diff --git a/documentation/tutorials/cplusplus/chap7/jobshop_ts2.cc b/documentation/tutorials/cplusplus/chap7/jobshop_ts2.cc new file mode 100644 index 0000000000..aab7f65853 --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/jobshop_ts2.cc @@ -0,0 +1,361 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// A simple program to solve the job-shop problem with local search and Tabu Search. +// See jobshop_ls.h for the local operators. +// +// We use the disjunctive model and specialized IntervalVars and SequenceVars. +// +// Use of two local search operators: +// - swap_operator: Exchanging two IntervalVars on a SequenceVar. +// - suffle_operator: Exchanging an arbitratry number of contiguous +// IntervalVars on a SequenceVar. +// +// We also use local search to find an initial solution. + + +#include +#include +#include + +#include "base/commandlineflags.h" +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "constraint_solver/constraint_solver.h" +#include "constraint_solver/constraint_solveri.h" +#include "util/string_array.h" +#include "jobshop.h" +#include "jobshop_ls.h" +#include "limits.h" + + + +DEFINE_string( + data_file, + "", + "Input file with a description of the job-shop problem instance to solve " + "in JSSP or Taillard's format.\n"); + +DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit."); +DEFINE_int32(shuffle_length, 4, "Length of sub-sequences to shuffle LS."); +DEFINE_int64(initial_time_limit_in_ms, 20000, + "Time limit in ms to find the initial solution by LS."); +DEFINE_int32(solutions_nbr_tolerance, 1, + "initial_time_limit_in_ms is applied except if the number of solutions" + "produced since last check is greater of equal to solutions_nbr_tolerance."); +DEFINE_int32(global_solution_nbr_tolerance, 30, "Number of solutions without improvement in the global Local Search"); +DEFINE_int64(keep_tenure, 10, "Keep Tabu tenure"); +DEFINE_int64(forbid_tenure, 5, "Forbid Tabu tenure"); +DEFINE_double(tabu_factor, 1.0, "Tabu factor (percentage)"); + +namespace operations_research { + +void Jobshop(const JobShopData& data) { + // ************************************************* + // MODEL + // ************************************************* + Solver solver("jobshop"); + const int machine_count = data.machine_count(); + const int job_count = data.job_count(); + const int horizon = data.horizon(); + + // Stores all tasks per job. + std::vector > jobs_to_tasks(job_count); + // Stores all tasks per machine. + std::vector > machines_to_tasks(machine_count); + + // Creates all interval variables. + for (int job_id = 0; job_id < job_count; ++job_id) { + const std::vector& tasks = data.TasksOfJob(job_id); + for (int task_index = 0; task_index < tasks.size(); ++task_index) { + const JobShopData::Task& task = tasks[task_index]; + CHECK_EQ(job_id, task.job_id); + const std::string name = StringPrintf("J%dM%dI%dD%d", + task.job_id, + task.machine_id, + task_index, + task.duration); + IntervalVar* const one_task = + solver.MakeFixedDurationIntervalVar(0, + horizon, + task.duration, + false, + name); + jobs_to_tasks[task.job_id].push_back(one_task); + machines_to_tasks[task.machine_id].push_back(one_task); + } + } + + // Add conjunctive constraintss. + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + if (task_count == 1) {continue;} + for (int task_index = 0; task_index < task_count - 1; ++task_index) { + IntervalVar* const t1 = jobs_to_tasks[job_id][task_index]; + IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1]; + Constraint* const prec = + solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1); + solver.AddConstraint(prec); + } + } + + // Adds disjunctive constraints and creates sequence variables. + std::vector all_sequences; + for (int machine_id = 0; machine_id < machine_count; ++machine_id) { + const std::string name = StringPrintf("Machine_%d", machine_id); + DisjunctiveConstraint* const ct = + solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id], name); + solver.AddConstraint(ct); + all_sequences.push_back(ct->MakeSequenceVar()); + } + + // Creates array of end_times of jobs. + std::vector all_ends; + for (int job_id = 0; job_id < job_count; ++job_id) { + const int task_count = jobs_to_tasks[job_id].size(); + IntervalVar* const task = jobs_to_tasks[job_id][task_count - 1]; + all_ends.push_back(task->EndExpr()->Var()); + } + + // Objective: minimize the makespan (maximum end times of all tasks). + IntVar* const objective_var = solver.MakeMax(all_ends)->Var(); + OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1); + + // Tabu variables + std::vector tabu_vars; + for (int seq = 0; seq < all_sequences.size(); ++seq) { + SequenceVar * seq_var = all_sequences[seq]; + for (int interval = 0; interval < seq_var->size(); ++interval ) { + IntVar * next = seq_var->Next(interval); + tabu_vars.push_back(next); + } + } + + // ************************************************* + // First solution + // ************************************************* + // This decision builder will rank all tasks on all machines. + DecisionBuilder* const sequence_phase = + solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT); + + // After the ranking of tasks, the schedule is still loose. + // We schedule each task at its earliest start time. + DecisionBuilder* const obj_phase = + solver.MakePhase(objective_var, + Solver::CHOOSE_FIRST_UNBOUND, + Solver::ASSIGN_MIN_VALUE); + + Assignment* const first_solution = solver.MakeAssignment(); + first_solution->Add(all_sequences); + first_solution->AddObjective(objective_var); + // Store the first solution in the 'solution' object. + DecisionBuilder* const first_solution_store_db = + solver.MakeStoreAssignment(first_solution); + + // The main decision builder (ranks all tasks, then fixes the + // objective_variable). + DecisionBuilder* const first_solution_phase = + solver.Compose(sequence_phase, obj_phase, first_solution_store_db); + + LOG(INFO) << "Looking for the first solution to initialize " + "the LS to find the initial solution..."; + const bool first_solution_found = solver.Solve(first_solution_phase); + if (first_solution_found) { + LOG(INFO) << "First solution found with makespan = " + << first_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No first solution found!"; + return; + } + + // ************************************************* + // Initial solution + // ************************************************* + LOG(INFO) << "Switching to local search to find a good initial solution..."; + + // Swap Operator with shuffle length 2. + LocalSearchOperator* const initial_shuffle_operator = + solver.RevAlloc(new ShuffleIntervals(all_sequences, + 2)); + // Complementary DecisionBuilder. + DecisionBuilder* const random_sequence_phase = + solver.MakePhase(all_sequences, Solver::CHOOSE_RANDOM_RANK_FORWARD); + DecisionBuilder* const complementary_ls_db = + solver.Compose(random_sequence_phase, obj_phase); + + // LS Parameters. + LocalSearchPhaseParameters* const initial_ls_param = + solver.MakeLocalSearchPhaseParameters(initial_shuffle_operator, + complementary_ls_db); + + // LS DecisionBuilder. + DecisionBuilder* const initial_ls_db = + solver.MakeLocalSearchPhase(first_solution, initial_ls_param); + + // Custom SearchLimit + SearchLimit * initial_search_limit = + solver.MakeCustomLimit( + new LSInitialSolLimit(&solver, + FLAGS_initial_time_limit_in_ms, + FLAGS_solutions_nbr_tolerance)); + + Assignment* const initial_solution = solver.MakeAssignment(); + initial_solution->Add(all_sequences); + initial_solution->AddObjective(objective_var); + // Store the initial solution in the 'solution' object. + DecisionBuilder* const initial_solution_store_db = + solver.MakeStoreAssignment(initial_solution); + + DecisionBuilder* const initial_solution_phase = + solver.Compose(initial_ls_db, initial_solution_store_db); + + LOG(INFO) << "Looking for the initial solution..."; + const bool initial_solution_found = + solver.Solve(initial_solution_phase, + objective_monitor, + initial_search_limit); + if (initial_solution_found) { + LOG(INFO) << "Initial solution found with makespan = " + << initial_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No initial solution found!"; + return; + } + + // ************************************************* + // Real Local Search with two operators + // ************************************************* + LOG(INFO) << "Switching to local search to find a good solution..."; + std::vector operators; + LOG(INFO) << " - use swap operator"; + LocalSearchOperator* const swap_operator = + solver.RevAlloc(new SwapIntervals(all_sequences)); + operators.push_back(swap_operator); + LOG(INFO) << " - use shuffle operator with a max length of " + << FLAGS_shuffle_length; + LocalSearchOperator* const shuffle_operator = + solver.RevAlloc(new ShuffleIntervals(all_sequences, + FLAGS_shuffle_length)); + operators.push_back(shuffle_operator); + + // Creates the local search decision builder. + LocalSearchOperator* const ls_concat = + solver.ConcatenateOperators(operators, true); + + DecisionBuilder* const ls_db = + solver.Compose(random_sequence_phase, obj_phase); + + LocalSearchPhaseParameters* const parameters = + solver.MakeLocalSearchPhaseParameters(ls_concat, ls_db); + DecisionBuilder* const final_db = + solver.MakeLocalSearchPhase(initial_solution, parameters); + + // Search log. + const int kLogFrequency = 1000000; + SearchMonitor* const search_log = + solver.MakeSearchLog(kLogFrequency, objective_var); + +SolutionCollector* const collector = + solver.MakeBestValueSolutionCollector(false); + collector->Add(all_sequences); + collector->AddObjective(objective_var); + // IntervalVar + for (int seq = 0; seq < all_sequences.size(); ++seq) { + const SequenceVar * sequence = all_sequences[seq]; + const int sequence_count = sequence->size(); + for (int i = 0; i < sequence_count; ++i) { + IntervalVar * t = sequence->Interval(i); + collector->Add(t->StartExpr()->Var()); + collector->Add(t->EndExpr()->Var()); + } + } + + SearchMonitor * tabu_search = solver.MakeTabuSearch(false, + objective_var, + 1, + tabu_vars, + FLAGS_keep_tenure, + FLAGS_forbid_tenure, + FLAGS_tabu_factor); + + SearchLimit * const no_improvement_limit = MakeNoImprovementLimit(&solver, objective_var, FLAGS_global_solution_nbr_tolerance); + SearchLimit * ctrl_catch_limit = nullptr; + +#if defined(__GNUC__) // Linux + ctrl_catch_limit = MakeCatchCTRLBreakLimit(&solver); +#endif + + SearchLimit* time_limit = nullptr; + if (FLAGS_time_limit_in_ms > 0) { + time_limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms); + } + + std::vector search_monitors; + search_monitors.push_back(search_log); + search_monitors.push_back(tabu_search); + search_monitors.push_back(no_improvement_limit); + if (ctrl_catch_limit != nullptr) { + search_monitors.push_back(ctrl_catch_limit); + } + if (time_limit != nullptr) { + search_monitors.push_back(time_limit); + } + search_monitors.push_back(collector); + + // Search. + if (solver.Solve(final_db, + search_monitors)) { + LOG(INFO) << "Objective value: " << collector->objective_value(0); + for (int m = 0; m < machine_count; ++m) { + SequenceVar* const seq = all_sequences[m]; + std::ostringstream s; + s << seq->name() << ": "; + const std::vector & sequence = collector->ForwardSequence(0, seq); + const int seq_size = sequence.size(); + for (int i = 0; i < seq_size; ++i) { + IntervalVar * t = seq->Interval(sequence[i]); + s << "Job " << sequence[i] << " ("; + s << collector->Value(0, t->StartExpr()->Var()); + s << ","; + s << collector->Value(0, t->EndExpr()->Var()); + s << ") "; + } + s.flush(); + LOG(INFO) << s.str(); + } + } else { + LOG(INFO) << "No solution found..."; + } + return; +} + +} // namespace operations_research + +static const char kUsage[] = +"Usage: jobshop --data_file=instance.txt.\n\n" +"This program solves the job-shop problem in JSSP or " +"Taillard's format with two basic local search operators and Tabu Search.\n"; + +int main(int argc, char **argv) { + google::SetUsageMessage(kUsage); + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_data_file.empty()) { + LOG(FATAL) << "Please supply a data file with --data_file="; + } + operations_research::JobShopData data(FLAGS_data_file); + operations_research::Jobshop(data); + + return 0; +} diff --git a/documentation/tutorials/cplusplus/chap7/limits.h b/documentation/tutorials/cplusplus/chap7/limits.h new file mode 100644 index 0000000000..ee7affe51b --- /dev/null +++ b/documentation/tutorials/cplusplus/chap7/limits.h @@ -0,0 +1,230 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Costum search limits. + + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_LIMITS_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_LIMITS_H + +#include +#include +#include +#include +#include + +#include "base/bitmap.h" +#include "base/logging.h" +#include "base/file.h" +#include "base/split.h" +#include "base/filelinereader.h" +#include "base/join.h" +#include "base/strtoint.h" + +#include "constraint_solver/constraint_solver.h" + +namespace operations_research { + +namespace { + +// See jobshop_ls3.cc for more. +class LSInitialSolLimit : public ResultCallback { + public: + LSInitialSolLimit(Solver * solver, int64 global_time_limit, + int solution_nbr_tolerance) : + solver_(solver), global_time_limit_(global_time_limit), + solution_nbr_tolerance_(solution_nbr_tolerance), + time_at_beginning_(solver_->wall_time()), + solutions_at_beginning_(solver_->solutions()), + solutions_since_last_check_(0) {} + + // Returns true if limit is reached, false otherwise. + virtual bool Run() { + bool limit_reached = false; + + // Test if time limit is reached. + if ((solver_->wall_time() - time_at_beginning_) + > global_time_limit_) { + limit_reached = true; + // Test if we continue despite time limit reached. + if (solver_->solutions() - solutions_since_last_check_ + >= solution_nbr_tolerance_) { + // We continue because we produce enough new solutions. + limit_reached = false; + } + } + solutions_since_last_check_ = solver_->solutions(); + + return limit_reached; + } + + private: + Solver * solver_; + int64 global_time_limit_; + int solution_nbr_tolerance_; + + int64 time_at_beginning_; + int solutions_at_beginning_; + int solutions_since_last_check_; +}; + +} // namespace + +SearchLimit * MakeLSInitialSolLimit(Solver * solver, + int64 global_time_limit, + int solution_nbr_tolerance) { + + // By default, the solver takes the ownership of the callback, no need to delete it! + return solver->MakeCustomLimit(new LSInitialSolLimit(solver, global_time_limit, solution_nbr_tolerance)); +} + +#if defined(__GNUC__) // Linux + +extern "C" { + bool ctrl_catch_limit_reached = false; + void CTRLBreakHandler(int s) { + LG << "Ctrl-break catched! exit properly.."; + ctrl_catch_limit_reached = true; + } +} + +namespace { + +class CatchCTRLBreakLimit : public ResultCallback { + public: + CatchCTRLBreakLimit(Solver * const solver) : + solver_(solver) { + sigIntHandler_.sa_handler = CTRLBreakHandler; + sigemptyset(&sigIntHandler_.sa_mask); + sigIntHandler_.sa_flags = 0; + sigaction(SIGINT, &sigIntHandler_, NULL); + } + + // Returns true if limit is reached, false otherwise. + virtual bool Run() { + return ctrl_catch_limit_reached; + } + + private: + Solver * const solver_; + struct sigaction sigIntHandler_; +}; + +} // namespace + +SearchLimit * MakeCatchCTRLBreakLimit(Solver * const solver) { + return solver->MakeCustomLimit(new CatchCTRLBreakLimit(solver)); +} + +#endif + + +namespace { + +// Don't use this class within a MakeLimit factory method! +class NoImprovementLimit : public SearchLimit { + public: + NoImprovementLimit(Solver * const solver, IntVar * const objective_var, int solution_nbr_tolerance, const bool minimize = true) : + SearchLimit(solver), + solver_(solver), prototype_(new Assignment(solver_)), + solution_nbr_tolerance_(solution_nbr_tolerance), + nbr_solutions_with_no_better_obj_(0), + minimize_(minimize), + limit_reached_(false) { + if (minimize_) { + best_result_ = kint64max; + } else { + best_result_ = kint64min; + } + CHECK_NOTNULL(objective_var); + prototype_->AddObjective(objective_var); + } + + virtual void Init() { + nbr_solutions_with_no_better_obj_ = 0; + limit_reached_ = false; + if (minimize_) { + best_result_ = kint64max; + } else { + best_result_ = kint64min; + } + } + + // Returns true if limit is reached, false otherwise. + virtual bool Check() { + VLOG(2) << "NoImprovementLimit's limit reached? " << limit_reached_; + + return limit_reached_; + } + + virtual bool AtSolution() { + ++nbr_solutions_with_no_better_obj_; + + prototype_->Store(); + + const IntVar* objective = prototype_->Objective(); + + if (minimize_ && objective->Min() < best_result_) { + best_result_ = objective->Min(); + nbr_solutions_with_no_better_obj_ = 0; + } else if (!minimize_ && objective->Max() > best_result_) { + best_result_ = objective->Max(); + nbr_solutions_with_no_better_obj_ = 0; + } + + if (nbr_solutions_with_no_better_obj_ > solution_nbr_tolerance_) { + limit_reached_ = true; + } + return true; + } + + virtual void Copy(const SearchLimit* const limit) { + const NoImprovementLimit* const copy_limit = + reinterpret_cast(limit); + + best_result_ = copy_limit->best_result_; + solution_nbr_tolerance_ = copy_limit->solution_nbr_tolerance_; + minimize_ = copy_limit->minimize_; + limit_reached_ = copy_limit->limit_reached_; + nbr_solutions_with_no_better_obj_ = copy_limit->nbr_solutions_with_no_better_obj_; + } + + // Allocates a clone of the limit + virtual SearchLimit* MakeClone() const { + // we don't to copy the variables + return solver_->RevAlloc(new NoImprovementLimit(solver_, prototype_->Objective(), solution_nbr_tolerance_, minimize_)); + } + + virtual std::string DebugString() const { + return StringPrintf("NoImprovementLimit(crossed = %i)", limit_reached_); + } + + private: + Solver * const solver_; + int64 best_result_; + int solution_nbr_tolerance_; + bool minimize_; + bool limit_reached_; + int nbr_solutions_with_no_better_obj_; + std::unique_ptr prototype_; +}; + +} // namespace + +NoImprovementLimit * MakeNoImprovementLimit(Solver * const solver, IntVar * const objective_var, const int solution_nbr_tolerance, const bool minimize = true) { + return solver->RevAlloc(new NoImprovementLimit(solver, objective_var, solution_nbr_tolerance, minimize)); +} +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_LIMITS_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/common/IO_helpers.h b/documentation/tutorials/cplusplus/common/IO_helpers.h new file mode 100644 index 0000000000..f86538a588 --- /dev/null +++ b/documentation/tutorials/cplusplus/common/IO_helpers.h @@ -0,0 +1,98 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Some helpers for Input/Ouput. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_IO_HELPERS_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_IO_HELPERS_H + +namespace operations_research { + +// Generic class to write an object of class T to a file thanks +// to one of its member with MemberSignature signature. +// We restrict ourself to this signature as all checking are done with +// asserts. +// You must pass a FULLY qualified method name to SetMember()! +template +class WriteToFile { +public: + typedef void (T::*MemberSignature)(std::ostream &) const; + WriteToFile(const T * t, const std::string & filename) : t_(t), filename_(filename), member_(NULL) {}; + void SetMember(MemberSignature m) { + member_ = m; + } + void Run() { + std::ofstream write_stream(filename_.c_str()); + CHECK_EQ(write_stream.is_open(), true) << "Unable to open file: " << filename_; + CHECK_NE(member_, NULL) << "Object method is not set!"; + (t_->*member_)(write_stream); + write_stream.close(); + } +private: + const T * t_; + const std::string & filename_; + MemberSignature member_; +}; + +// Same but with an additional parameter. +template +class WriteToFileP1 { +public: + typedef void (T::*MemberSignature)(std::ostream &, const P1 &) const; + WriteToFileP1(const T * t, const std::string & filename) : t_(t), filename_(filename), member_(NULL) {}; + void SetMember(MemberSignature m) { + member_ = m; + } + void Run(const P1 & p) { + std::ofstream write_stream(filename_.c_str()); + CHECK_EQ(write_stream.is_open(), true) << "Unable to open file: " << filename_; + CHECK_NE(member_, NULL) << "Object method is not set!"; + (t_->*member_)(write_stream, p); + write_stream.close(); + } +private: + const T * t_; + const std::string & filename_; + MemberSignature member_; +}; + +// One entry class to fatally log to different std::ostreams if needed. +class FatalInstanceLoadingLog { +public: + FatalInstanceLoadingLog() {} + void AddOutputStream(std::ostream * out) { + streams_.push_back(out); + } + void Write(const char * msg, const std::string & wrong_keyword = "", int line_number = -1) { + std::stringstream line; + line << msg; + if (wrong_keyword != "") { + line << ": \"" << wrong_keyword << "\""; + } + if (line_number != -1) { + line << " on line " << line_number; + } + line << std::endl; + for (int i = 0; i < streams_.size(); ++i) { + (*streams_[i]) << line.str(); + } + LOG(FATAL) << msg << ": \"" << wrong_keyword << "\" on line " << line_number; + } +private: + std::vector streams_; +}; + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_IO_HELPERS_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/common/common_flags.h b/documentation/tutorials/cplusplus/common/common_flags.h new file mode 100644 index 0000000000..d5d09dcca8 --- /dev/null +++ b/documentation/tutorials/cplusplus/common/common_flags.h @@ -0,0 +1,30 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common flags. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_COMMON_FLAGS_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_COMMON_FLAGS_H + +#include "base/commandlineflags.h" + +#include "common/constants.h" + +DEFINE_bool(deterministic_random_seed, true, + "Use deterministic random seeds or not?"); + +DEFINE_int32(random_generated_graph_max_edges_percent, 80, "Maximal percent of edges allowed for a randomly generated graph."); +DEFINE_bool(print_graph_labels, true, "Print graph labels for nodes?"); + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_COMMON_FLAGS_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/common/constants.h b/documentation/tutorials/cplusplus/common/constants.h new file mode 100644 index 0000000000..108b02b89a --- /dev/null +++ b/documentation/tutorials/cplusplus/common/constants.h @@ -0,0 +1,27 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common constants. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_CONSTANTS_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_CONSTANTS_H + +namespace operations_research { + +const static int64 kPostiveInfinityInt64 = std::numeric_limits::max(); + + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_CONSTANTS_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/common/limits.h b/documentation/tutorials/cplusplus/common/limits.h new file mode 100644 index 0000000000..d026c039d5 --- /dev/null +++ b/documentation/tutorials/cplusplus/common/limits.h @@ -0,0 +1,233 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Costum search limits. + + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_LIMITS_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_LIMITS_H + +#include +#include +#include +#include +#include + +#include "base/bitmap.h" +#include "base/logging.h" +#include "base/file.h" +#include "base/split.h" +#include "base/filelinereader.h" +#include "base/join.h" +#include "base/strtoint.h" + +#include "constraint_solver/constraint_solver.h" + + +namespace operations_research { + +namespace { + +// See jobshop_ls3.cc for more. +class LSInitialSolLimit : public ResultCallback { + public: + LSInitialSolLimit(Solver * solver, int64 global_time_limit, + int solution_nbr_tolerance) : + solver_(solver), global_time_limit_(global_time_limit), + solution_nbr_tolerance_(solution_nbr_tolerance), + time_at_beginning_(solver_->wall_time()), + solutions_at_beginning_(solver_->solutions()), + solutions_since_last_check_(0) {} + + // Returns true if limit is reached, false otherwise. + virtual bool Run() { + bool limit_reached = false; + + // Test if time limit is reached. + if ((solver_->wall_time() - time_at_beginning_) + > global_time_limit_) { + limit_reached = true; + // Test if we continue despite time limit reached. + if (solver_->solutions() - solutions_since_last_check_ + >= solution_nbr_tolerance_) { + // We continue because we produce enough new solutions. + limit_reached = false; + } + } + solutions_since_last_check_ = solver_->solutions(); + + return limit_reached; + } + + private: + Solver * solver_; + int64 global_time_limit_; + int solution_nbr_tolerance_; + + int64 time_at_beginning_; + int solutions_at_beginning_; + int solutions_since_last_check_; +}; + + } + +SearchLimit * MakeLSInitialSolLimit(Solver * solver, + int64 global_time_limit, + int solution_nbr_tolerance) { + + // By default, the solver takes the ownership of the callback, no need to delete it! + return solver->MakeCustomLimit(new LSInitialSolLimit(solver, global_time_limit, solution_nbr_tolerance)); +} + +#if defined(__GNUC__) // Linux + +extern "C" { + bool ctrl_catch_limit_reached = false; + void CTRLBreakHandler(int s) { + LG << "Ctrl-break catched! exit properly.."; + ctrl_catch_limit_reached = true; + } +} + +namespace { + +class CatchCTRLBreakLimit : public ResultCallback { + public: + CatchCTRLBreakLimit(Solver * const solver) : + solver_(solver) { + sigIntHandler_.sa_handler = CTRLBreakHandler; + sigemptyset(&sigIntHandler_.sa_mask); + sigIntHandler_.sa_flags = 0; + sigaction(SIGINT, &sigIntHandler_, NULL); + } + + // Returns true if limit is reached, false otherwise. + virtual bool Run() { + return ctrl_catch_limit_reached; + } + + private: + Solver * const solver_; + struct sigaction sigIntHandler_; +}; + +} // namespace + +SearchLimit * MakeCatchCTRLBreakLimit(Solver * const solver) { + return solver->MakeCustomLimit(new CatchCTRLBreakLimit(solver)); +} + +#endif + + +namespace { + +// Don't use this class within a MakeLimit factory method! +class NoImprovementLimit : public SearchLimit { + public: + NoImprovementLimit(Solver * const solver, IntVar * const objective_var, int solution_nbr_tolerance, const bool minimize = true) : + SearchLimit(solver), + solver_(solver), prototype_(new Assignment(solver_)), + solution_nbr_tolerance_(solution_nbr_tolerance), + nbr_solutions_with_no_better_obj_(0), + minimize_(minimize), + limit_reached_(false) { + if (minimize_) { + best_result_ = kint64max; + } else { + best_result_ = kint64min; + } + + CHECK_NOTNULL(objective_var); + prototype_->AddObjective(objective_var); + + } + + virtual void Init() { + nbr_solutions_with_no_better_obj_ = 0; + limit_reached_ = false; + if (minimize_) { + best_result_ = kint64max; + } else { + best_result_ = kint64min; + } + } + + // Returns true if limit is reached, false otherwise. + virtual bool Check() { + VLOG(2) << "NoImprovementLimit's limit reached? " << limit_reached_; + + return limit_reached_; + } + + virtual bool AtSolution() { + ++nbr_solutions_with_no_better_obj_; + + prototype_->Store(); + + const IntVar* objective = prototype_->Objective(); + + if (minimize_ && objective->Min() < best_result_) { + best_result_ = objective->Min(); + nbr_solutions_with_no_better_obj_ = 0; + } else if (!minimize_ && objective->Max() > best_result_) { + best_result_ = objective->Max(); + nbr_solutions_with_no_better_obj_ = 0; + } + + if (nbr_solutions_with_no_better_obj_ > solution_nbr_tolerance_) { + limit_reached_ = true; + } + return true; + } + + virtual void Copy(const SearchLimit* const limit) { + const NoImprovementLimit* const copy_limit = + reinterpret_cast(limit); + + best_result_ = copy_limit->best_result_; + solution_nbr_tolerance_ = copy_limit->solution_nbr_tolerance_; + minimize_ = copy_limit->minimize_; + limit_reached_ = copy_limit->limit_reached_; + nbr_solutions_with_no_better_obj_ = copy_limit->nbr_solutions_with_no_better_obj_; + } + + // Allocates a clone of the limit + virtual SearchLimit* MakeClone() const { + // we don't to copy the variables + return solver_->RevAlloc(new NoImprovementLimit(solver_, prototype_->Objective(), solution_nbr_tolerance_, minimize_)); + } + + virtual std::string DebugString() const { + return StringPrintf("NoImprovementLimit(crossed = %i)", limit_reached_); + } + + private: + Solver * const solver_; + int64 best_result_; + int solution_nbr_tolerance_; + bool minimize_; + bool limit_reached_; + int nbr_solutions_with_no_better_obj_; + std::unique_ptr prototype_; +}; + +} // namespace + +NoImprovementLimit * MakeNoImprovementLimit(Solver * const solver, IntVar * const objective_var, const int solution_nbr_tolerance, const bool minimize = true) { + return solver->RevAlloc(new NoImprovementLimit(solver, objective_var, solution_nbr_tolerance, minimize)); +} +} + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_LIMITS_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/common/random.h b/documentation/tutorials/cplusplus/common/random.h new file mode 100644 index 0000000000..338f5368e6 --- /dev/null +++ b/documentation/tutorials/cplusplus/common/random.h @@ -0,0 +1,39 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Several helper functions to generate random numbers. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_RANDOM_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_RANDOM_H + +#include "base/random.h" + +#include "common/constants.h" + +#include "common/common_flags.h" + +namespace operations_research { + +// Random seed generator. +int32 GetSeed() { + if (FLAGS_deterministic_random_seed) { + return ACMRandom::DeterministicSeed(); + } else { + return ACMRandom::HostnamePidTimeSeed(); + } +} + +} // namespace opertional_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_RANDOM_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/routing_common/routing_common.h b/documentation/tutorials/cplusplus/routing_common/routing_common.h new file mode 100644 index 0000000000..746b2e157b --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/routing_common.h @@ -0,0 +1,233 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common routing stuff. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_COMMON_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_COMMON_H + +#include +#include +#include +#include +#include + +#include "base/random.h" +#include "constraint_solver/routing.h" + +#include "routing_common/routing_common_flags.h" +#include "common/IO_helpers.h" +#include "common/constants.h" + +namespace operations_research { + + +// Simple struct to contain points in the plane or in the space. +struct Point { + Point(const double x_, const double y_, const double z_ = 0.0): + x(x_), y(y_), z(z_){} + Point(): x(-1.0), y(-1.0), z(-1.0){} + double x; + double y; + double z; +}; + +// Simple container class to hold cost on arcs of a complete graph. +// This class doesn't compute the distances but only stores them. +// IsCreated(): the cost/distance matrix exists; +// IsInstanciated(): the matrix is filled. +// +// Distances/costs can be symetric or not. +class CompleteGraphArcCost { +public: + explicit CompleteGraphArcCost(int32 size = 0): size_(size), is_created_(false), is_instanciated_(false), is_symmetric_(false), + min_cost_(kPostiveInfinityInt64), max_cost_(-1) { + if (size_ > 0) { + CreateMatrix(size_); + } + } + + int32 Size() const { + return size_; + } + + void Create(int32 size) { + CHECK(!IsCreated()) << "Matrix already created!"; + size_ = size; + CreateMatrix(size); + } + + bool IsCreated() const { + return is_created_; + } + + bool IsInstanciated() const { + return is_instanciated_; + } + + void SetIsInstanciated(const bool instanciated = true) { + CHECK(IsCreated()) << "Instance is not created!"; + is_instanciated_ = instanciated; + ComputeExtremeDistance(); + ComputeIsSymmetric(); + } + + int64 Cost(RoutingModel::NodeIndex from, + RoutingModel::NodeIndex to) const { + return matrix_[MatrixIndex(from, to)]; + } + + int64& Cost(RoutingModel::NodeIndex from, + RoutingModel::NodeIndex to) { + return matrix_[MatrixIndex(from, to)]; + } + + int64 MaxCost() const { + CHECK(IsInstanciated()) << "Instance is not instanciated!"; + return max_cost_; + } + + int64 MinCost() const { + CHECK(IsInstanciated()) << "Instance is not instanciated!"; + return min_cost_; + } + + bool IsSymmetric() const { + CHECK(IsInstanciated()) << "Instance is not instanciated!"; + return is_symmetric_; + } + + + void Print(std::ostream & out, const bool label = false, const int width = FLAGS_width_size) const; + +private: + int64 MatrixIndex(RoutingModel::NodeIndex from, + RoutingModel::NodeIndex to) const { + return (from * size_ + to).value(); + } + + void CreateMatrix(const int size) { + CHECK_GT(size, 2) << "Size for matrix non consistent."; + int64 * p_array = NULL; + try { + p_array = new int64 [size_ * size_]; + } catch (std::bad_alloc & e) { + p_array = NULL; + LOG(FATAL) << "Problems allocating ressource. Try with a smaller size."; + } + CHECK_NE(p_array, NULL) << "Not enough resources to create matrix"; + matrix_.reset(p_array); + is_created_ = true; + } + + void UpdateExtremeDistance(int64 dist) { + if (min_cost_ > dist) { min_cost_ = dist;} + if (max_cost_ < dist) { max_cost_ = dist;} + } + + void ComputeExtremeDistance() { + CHECK(IsInstanciated()) << "Instance is not instanciated!"; + min_cost_ = kPostiveInfinityInt64; + max_cost_ = -1; + for (RoutingModel::NodeIndex i(0); i < size_; ++i) { + for (RoutingModel::NodeIndex j(0); j < size_; ++j) { + if (i == j) {continue;} + UpdateExtremeDistance(Cost(i,j)); + } + } + } + + bool ComputeIsSymmetric() { + CHECK(IsInstanciated()) << "Instance is not instanciated!"; + for (RoutingModel::NodeIndex i(0); i < Size(); ++i) { + for (RoutingModel::NodeIndex j(i + 1); j < Size(); ++j) { + if (matrix_[MatrixIndex(i,j)] != matrix_[MatrixIndex(j,i)]) { + return false; + } + } + } + + return true; + } + + int32 size_; + //scoped_array matrix_; + std::unique_ptr matrix_; + + + + bool is_created_; + bool is_instanciated_; + bool is_symmetric_; + int64 min_cost_; + int64 max_cost_; +}; + +void CompleteGraphArcCost::Print(std::ostream& out, const bool label, const int width) const { + CHECK(IsInstanciated()) << "Instance is not instanciated!"; + // titel + out.width(width); + if (label) { + out << std::left << " "; + for (RoutingModel::NodeIndex to = RoutingModel::kFirstNode; to < size_; ++to) { + out.width(width); + out << std::right << to.value() + 1 ; + } + out << std::endl; + } + // fill + for (RoutingModel::NodeIndex from = RoutingModel::kFirstNode; from < size_; ++from) { + if (label) { + out.width(width); + out << std::right << from.value() + 1; + } + for (RoutingModel::NodeIndex to = RoutingModel::kFirstNode; to < size_; ++to) { + out.width(width); + out << std::right << matrix_[MatrixIndex(from, to)]; + } + out << std::endl; + } +} + +struct BoundingBox { + + BoundingBox(): min_x(std::numeric_limits::max()), + max_x(std::numeric_limits::min()), + min_y(std::numeric_limits::max()), + max_y(std::numeric_limits::min()), + min_z(std::numeric_limits::max()), + max_z(std::numeric_limits::min()) {} + BoundingBox(double min_x_, double max_x_, double min_y_, double max_y_, double min_z_, double max_z_): + min_x(min_x_), max_x(max_x_), min_y(min_y_), max_y(max_y_), min_z(min_z_), max_z(max_z_) {} + + void Update(Point p) { + if (p.x < min_x) {min_x = p.x;} + if (p.x > max_x) {max_x = p.x;} + if (p.y < min_y) {min_y = p.y;} + if (p.y > max_y) {max_y = p.y;} + if (p.z < min_z) {min_z = p.z;} + if (p.z > max_z) {max_z = p.z;} + } + // Bounding box + double min_x; + double max_x; + double min_y; + double max_y; + double min_z; + double max_z; +}; + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_COMMON_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/routing_common/routing_common_flags.h b/documentation/tutorials/cplusplus/routing_common/routing_common_flags.h new file mode 100644 index 0000000000..c503c058dc --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/routing_common_flags.h @@ -0,0 +1,58 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common routing flags. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_COMMON_FLAGS_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_COMMON_FLAGS_H + +#include "base/commandlineflags.h" + +#include "common/constants.h" + +DEFINE_int32(instance_size, 10, "Number of nodes, including the depot."); +DEFINE_string(instance_name, "Dummy instance", "Instance name."); + +DEFINE_int32(instance_depot, 0, "Depot for instance."); + +DEFINE_string(instance_file, "", "TSPLIB instance file."); +DEFINE_string(solution_file, "", "TSPLIB solution file."); + +DEFINE_string(distance_file, "", "TSP matrix distance file."); + + +DEFINE_int32(edge_min, 1, "Minimum edge value."); +DEFINE_int32(edge_max, 100, "Maximum edge value."); + + +DEFINE_int32(instance_edges_percent, 20, "Percent of edges in the graph."); + +DEFINE_int32(x_max, 100, "Maximum x coordinate."); +DEFINE_int32(y_max, 100, "Maximum y coordinate."); + + +DEFINE_int32(width_size, 6, "Width size of fields in output."); + +DEFINE_int32(epix_width, 10, "Width of the pictures in cm."); +DEFINE_int32(epix_height, 10, "Height of the pictures in cm."); + +DEFINE_double(epix_radius, 0.3, "Radius of circles."); + +DEFINE_bool(epix_node_labels, false, "Print node labels?"); + +DEFINE_int32(percentage_forbidden_arcs_max, 94, "Maximum percentage of arcs to forbid."); +DEFINE_int64(M, operations_research::kPostiveInfinityInt64, "Big m value to represent infinity."); + + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_COMMON_FLAGS_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/routing_common/routing_data.h b/documentation/tutorials/cplusplus/routing_common/routing_data.h new file mode 100644 index 0000000000..d21dae31bb --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/routing_data.h @@ -0,0 +1,228 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common minimalistic base for routing data (instance) classes. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_DATA_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_DATA_H + +#include + +#include "constraint_solver/routing.h" + +#include "routing_common/routing_common.h" +#include "routing_common/routing_data_generator.h" + +DECLARE_int32(width_size); + +namespace operations_research { + +// Forward declaration. +class TSPLIBReader; + + +// Base class with Routing Data (instances). +class RoutingData { +public: + explicit RoutingData(int32 size = 0) : size_(size), name_("no name"), is_routing_data_created_(false), is_routing_data_instanciated_(false), + has_coordinates_(false), has_diplay_coords_(false), raw_bbox_(BoundingBox()) + { + if (size > 0) { + CreateRoutingData(size); + } + } + explicit RoutingData(const RoutingDataGenerator & g): size_(g.Size()), name_(g.InstanceName()), is_routing_data_created_(false), is_routing_data_instanciated_(false), + has_coordinates_(false), has_diplay_coords_(false), raw_bbox_(BoundingBox()) + { + if (Size() > 0) { + CreateRoutingData(Size()); + for (RoutingModel::NodeIndex i(0); i < Size(); ++i) { + Coordinate(i) = g.Coordinate(i); + for (RoutingModel::NodeIndex j(0); j < Size(); ++j) { + InternalDistance(i,j) = g.Distance(i,j); + } + } + SetRoutingDataInstanciated(); + SetHasCoordinates(); + } + + } + + explicit RoutingData(const RoutingData & other) { + CreateRoutingData(other.Size()); + name_ = other.Name(); + comment_ = other.Comment(); + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + for (RoutingModel::NodeIndex j(RoutingModel::kFirstNode); j < Size(); ++j) { + InternalDistance(i,j) = other.Distance(i,j); + } + } + + if (other.HasDisplayCoordinates()) { + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + Coordinate(i) = other.Coordinate(i); + } + } + + if (other.HasCoordinates()) { + for (RoutingModel::NodeIndex i(RoutingModel::kFirstNode); i < Size(); ++i) { + DisplayCoordinate(i) = other.DisplayCoordinate(i); + } + } + SetHasCoordinates(other.HasCoordinates()); + SetHasDisplayCoordinates(other.HasDisplayCoordinates()); + SetRoutingDataInstanciated(); + } + + + virtual ~RoutingData() {} + + void SetHasCoordinates(const bool coordinates = true) { + has_coordinates_ = coordinates; + } + + void SetHasDisplayCoordinates(const bool display_coordinates = true) { + has_diplay_coords_ = display_coordinates; + } + + bool HasCoordinates() const { + return has_coordinates_; + } + + bool HasDisplayCoordinates() const { + return has_diplay_coords_; + } + + bool IsVisualizable() const { + return HasCoordinates() || HasDisplayCoordinates(); + } + + int32 Size() const { + return size_; + } + + std::string Name() const { + return name_; + } + + std::string Comment() const { + return comment_; + } + + int64 Distance(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j) const { + CheckNodeIsValid(i); + return distances_.Cost(i,j); + } + + Point Coordinate(RoutingModel::NodeIndex i) const { + CheckNodeIsValid(i); + return coordinates_[i.value()]; + } + + Point DisplayCoordinate(RoutingModel::NodeIndex i) const { + CheckNodeIsValid(i); + return display_coords_[i.value()]; + } + + BoundingBox RawBoundingBox() const { + return raw_bbox_; + } + + void PrintDistanceMatrix(std::ostream& out, const int32 & width = FLAGS_width_size) const; + void WriteDistanceMatrix(const std::string & filename, const int32 & width = FLAGS_width_size) const; + +protected: + void CreateRoutingData(int32 size) { + size_ = size; + distances_.Create(size); + coordinates_.resize(size); + display_coords_.resize(size); + is_routing_data_created_ = true; + } + + bool IsRoutingDataCreated() const { + return is_routing_data_created_; + } + + bool IsRoutingDataInstanciated() const { + return is_routing_data_instanciated_; + } + + void SetRoutingDataInstanciated() { + is_routing_data_instanciated_ = true; + distances_.SetIsInstanciated(); + //Find bounding box + if (HasDisplayCoordinates()) { + for (int32 i = 0; i < Size(); ++i ) { + raw_bbox_.Update(display_coords_[i]); + } + } else if (HasCoordinates()) { + for (int32 i = 0; i < Size(); ++i ) { + raw_bbox_.Update(coordinates_[i]); + } + } + } + + void CheckNodeIsValid(const RoutingModel::NodeIndex i) const { + CHECK_GE(i.value(), 0) << "Internal node " << i.value() << " should be greater than 0!"; + CHECK_LT(i.value(), Size()) << "Internal node " << i.value() << " should be less than " << Size(); + } + + int64& InternalDistance(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j) { + CheckNodeIsValid(i); + CheckNodeIsValid(j); + return distances_.Cost(i,j); + } + + Point& Coordinate(RoutingModel::NodeIndex i) { + CheckNodeIsValid(i); + return coordinates_[i.value()]; + } + + Point& DisplayCoordinate(RoutingModel::NodeIndex i) { + CheckNodeIsValid(i); + return display_coords_[i.value()]; + } + +protected: + int32 size_; + std::string name_; + std::string comment_; +private: + bool is_routing_data_created_; + bool is_routing_data_instanciated_; + bool has_coordinates_; + bool has_diplay_coords_; +protected: + CompleteGraphArcCost distances_; + std::vector coordinates_; + std::vector display_coords_; + BoundingBox raw_bbox_; + +}; + +void RoutingData::PrintDistanceMatrix(std::ostream& out, const int32 & width) const { + distances_.Print(out, width); +} + +void RoutingData::WriteDistanceMatrix(const std::string& filename, const int32 & width) const { + WriteToFileP1 writer(this, filename); + writer.SetMember(&operations_research::RoutingData::PrintDistanceMatrix); + writer.Run(width); +} + + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_DATA_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/routing_common/routing_data_generator.h b/documentation/tutorials/cplusplus/routing_common/routing_data_generator.h new file mode 100644 index 0000000000..22067768af --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/routing_data_generator.h @@ -0,0 +1,80 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common base to generate routing data (instances). + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_DATA_GENERATOR_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_DATA_GENERATOR_H + +#include + +#include "base/join.h" +#include "base/integral_types.h" +#include "constraint_solver/routing.h" + +#include "common/random.h" +#include "routing_common/routing_common_flags.h" +#include "routing_common/routing_common.h" +#include "routing_common/routing_random.h" +#include "routing_common/routing_distance.h" + +namespace operations_research { + class RoutingDataGenerator { + public: + RoutingDataGenerator(std::string problem_name, std::string instance_name, int32 size) : + problem_name_(problem_name), instance_name_(instance_name), size_(size), randomizer_(GetSeed()), + coordinates_(size_), dist_coords_(coordinates_) {} + int64 Distance(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j) const { + return dist_coords_.Distance(i,j); + } + + Point Coordinate(RoutingModel::NodeIndex i) const { + return coordinates_.Coordinate(i); + } + std::string ProblemName() const { + return problem_name_; + } + std::string InstanceName() const { + return instance_name_; + } + int32 Size() const { + return size_; + } + + void ReplaceDistance(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j, int64 dist) { + dist_coords_.ReplaceDistance(i, j, dist); + } + protected: + std::string problem_name_; + std::string instance_name_; + int32 size_; + ACMRandom randomizer_; + GenerateTWODCoordinates coordinates_; + DistancesFromTWODCoordinates dist_coords_; + + }; + + // Common usage message for instance generators. + std::string GeneratorUsage(std::string invocation, std::string problem_name) { + return StrCat("Generates a ", problem_name, " instance.\n" + "See Google or-tools tutorials\n" + "Sample usage:\n\n", + invocation, + " -instance_name= -instance_size=\n\n"); + } +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_DATA_GENERATOR_H + + diff --git a/documentation/tutorials/cplusplus/routing_common/routing_distance.h b/documentation/tutorials/cplusplus/routing_common/routing_distance.h new file mode 100644 index 0000000000..461c660d63 --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/routing_distance.h @@ -0,0 +1,76 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common routing distance stuff. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_DISTANCE_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_DISTANCE_H + +#include + +#include "constraint_solver/routing.h" + +#include "routing_random.h" +#include "tsplib.h" + +DECLARE_int32(width_size); + +namespace operations_research { + + class CompleteGraphDistances { + public: + CompleteGraphDistances(int32 size): size_(size), costs_(size_) {} + virtual int64 Distance(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j) const = 0; + int32 Size() const { + return size_; + } + void Print(std::ostream & out, const int width = FLAGS_width_size); + void ReplaceDistance(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j, int64 dist) { + costs_.Cost(i,j) = dist; + } + private: + int32 size_; + protected: + CompleteGraphArcCost costs_; + }; + + void CompleteGraphDistances::Print(std::ostream & out, const int width) { + costs_.Print(out, width); + } + + // Basic distance class on "complete" graphs from coordinates. + class DistancesFromTWODCoordinates : public CompleteGraphDistances { + public: + explicit DistancesFromTWODCoordinates(const GenerateTWODCoordinates& coords): CompleteGraphDistances(coords.Size()), coords_(coords) { + for (RoutingModel::NodeIndex i(0); i < Size(); ++i) { + costs_.Cost(i,i) = 0; + for (RoutingModel::NodeIndex j(i.value()+1); j < Size(); ++j) { + int64 dist = TSPLIBDistanceFunctions::TWOD_euc_2d_distance(coords_.Coordinate(i), coords_.Coordinate(j)); + costs_.Cost(i,j) = dist; + costs_.Cost(j,i) = dist; + } + } + } + virtual int64 Distance(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j) const { + return costs_.Cost(i,j); + } + void ReplaceDistance(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j, int64 dist) { + costs_.Cost(i,j) = dist; + } + private: + const GenerateTWODCoordinates& coords_; + }; +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_COMMON_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/routing_common/routing_epix_helper.h b/documentation/tutorials/cplusplus/routing_common/routing_epix_helper.h new file mode 100644 index 0000000000..6c7b5c923e --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/routing_epix_helper.h @@ -0,0 +1,124 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common base to use the epix library to visualize +// routing data (instance) and solutions. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_EPIX_HELPER_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_EPIX_HELPER_H + +#include + +#include "constraint_solver/routing.h" +#include "base/join.h" + +#include "routing_common/routing_common.h" + +namespace operations_research { + +class RoutingEpixHelper { +public: + explicit RoutingEpixHelper(std::ostream & out) : out_(&out) {} + void SetOuptutStream(std::ostream & out) { + out_ = &out; + } +private: + std::ostream * out_; +}; + + void PrintEpixBeginFile(std::ostream & out) { + out << "#include \"epix.h\"" << std::endl; + out << "using namespace ePiX;" << std::endl; + out << std::endl; + out << "int main(int argc, char **argv)" << std::endl; + out << "{" << std::endl; + } + + void PrintEpixPreamble(std::ostream & out) { + out << std::endl; + out << "unitlength(\"1cm\");" << std::endl; + out << "picture(" << FLAGS_epix_width << "," << FLAGS_epix_height << ");" << std::endl; + out << "double radius = " << FLAGS_epix_radius << ";" << std::endl; + out << "font_size(\"tiny\");" << std::endl; + } + + void PrintEpixBoundingBox(std::ostream & out, const BoundingBox & P) { + out << "bounding_box(P(" << P.min_x << "," << P.min_y << "), P(" << P.max_x << "," << P.max_y << "));" << std::endl; + } + + + void PrintEpixBeginFigure(std::ostream & out) { + out << std::endl; + out << "begin(); // ---- Figure body starts here ----" << std::endl; + } + + void PrintEpixEndFigure(std::ostream & out) { + out << std::endl; + out << "end(); // ---- End figure; write output file ----" << std::endl; + } + + void PrintEpixEndFile(std::ostream & out) { + out << std::endl; + out << "}" << std::endl; + } + + void PrintEpixNewLine(std::ostream & out) { + out << std::endl; + } + + void PrintEpixRaw(std::ostream & out, const char* s) { + out << s << std::endl; + } + + void PrintEpixComment(std::ostream & out, const char* s) { + out << " // " << s << std::endl; + } + + void PrintEpixPoint(std::ostream & out, Point p, RoutingModel::NodeIndex i) { + out << " P " << StrCat("P",i.value()) << "(" << p.x << "," << p.y << ");" << std::endl; + out << StrCat(" Circle C",i.value()) << StrCat("(P",i.value()) << ", radius);" << std::endl; + } + + void PrintEpixSegment(std::ostream & out, int segment_index, RoutingModel::NodeIndex i, RoutingModel::NodeIndex j) { + out << StrCat(" Segment L", segment_index) <<"(P" << StrCat(i.value()) << ",P" << + StrCat(j.value()) << ");" << std::endl; + } + + void PrintEpixDepot(std::ostream & out, RoutingModel::NodeIndex d) { + out << " fill(Red());" << std::endl; + out << StrCat(" C", d.value(), ".draw();") << std::endl; + out << " fill(White());" << std::endl; + } + + void PrintEpixDrawMultiplePoints(std::ostream & out, int size) { + for (int i = 0; i < size; ++i) { + out << StrCat(" C",i) << ".draw();" << std::endl; + if (FLAGS_epix_node_labels) { + out << StrCat(" label (P", i) << StrCat(",P(0.2,0.1),\"", i+1 ) << "\",tr);" << std::endl; + } + } + } + void PrintEpixArrow(std::ostream & out, RoutingModel::NodeIndex from_node, RoutingModel::NodeIndex to_node) { + out << "arrow " <<"(P" << from_node.value() << ", P" << to_node.value() << ");" << std::endl; + } + + void PrintEpixDrawMultipleSegments(std::ostream & out, int size) { + for (int i = 0; i < size; ++i) { + out << StrCat(" L",i) << ".draw();" << std::endl; + } + } + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_EPIX_HELPER_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/routing_common/routing_random.h b/documentation/tutorials/cplusplus/routing_common/routing_random.h new file mode 100644 index 0000000000..ed05fe73b8 --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/routing_random.h @@ -0,0 +1,73 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common random routing stuff. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_RANDOM_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_RANDOM_H + +#include +#include + +#include "base/random.h" +#include "constraint_solver/routing.h" + +#include "common/random.h" +#include "routing_common/routing_common.h" + +namespace operations_research { + + +class GenerateTWODCoordinates { +public: + GenerateTWODCoordinates(int32 size): size_(size), randomizer_(GetSeed()), coordinates_(size_) { + Generate(); + } + const Point Coordinate(RoutingModel::NodeIndex i) const { + return coordinates_[i.value()]; + } + const int32 Size() const { + return size_; + } +private: + void Generate() { + bool coord_found = false; + int32 x = 0; + int32 y = 0; + for (int32 i = 0; i < size_; i++) { + coord_found = false; + while (!coord_found) { + x = randomizer_.Uniform(FLAGS_x_max); + y = randomizer_.Uniform(FLAGS_y_max); + // test if coordinates are not already taken + // maybe we should ask for a minimum distance between points? + coord_found = true; + for (int32 j = 0; j < i; ++j) { + if (x == coordinates_[j].x && y == coordinates_[j].y) { + coord_found = false; + break; + } + } + } + coordinates_[i] = Point(x, y); + } + } + int32 size_; + ACMRandom randomizer_; + std::vector coordinates_; + +}; +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_RANDOM_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/routing_common/routing_solution.h b/documentation/tutorials/cplusplus/routing_common/routing_solution.h new file mode 100644 index 0000000000..15034aa2ec --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/routing_solution.h @@ -0,0 +1,51 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Common base for routing solution classes. +// A route is given by its depot (first node) and then the path (except for the first node), e.g. +// RoutingSolution 1 -> 2 -> 5 is in fact path 1 -> 2 -> 5 -> 1 and 1 is a depot. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_SOLUTION_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_SOLUTION_H + +#include "constraint_solver/routing.h" + +#include "routing_data.h" + +namespace operations_research { + + class RoutingSolution { + public: + explicit RoutingSolution(const RoutingData & data) : size_(data.Size()) {} + virtual ~RoutingSolution() {} + virtual bool IsSolution() const = 0; + virtual bool IsFeasibleSolution() const = 0; + virtual int64 ComputeObjectiveValue() const = 0; + + void SetSize(int32 size) { + size_ = size; + } + // Size of the instance. + int32 Size() const { + return size_; + } + virtual bool Add(RoutingModel::NodeIndex i, int route_number = 0) = 0; + virtual void Print(std::ostream & out) const = 0; + protected: + int32 size_; + }; + +} // namespace operations_research + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_ROUTING_SOLUTION_H \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/routing_common/tsplib.h b/documentation/tutorials/cplusplus/routing_common/tsplib.h new file mode 100644 index 0000000000..b11c62d427 --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/tsplib.h @@ -0,0 +1,414 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Definitions for the TSPLIB format. + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_TSPLIB_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_TSPLIB_H + +#include +#include + +//#include "base/commandlineflags.h" +#include "base/integral_types.h" + +#include "routing_common.h" + +//DEFINE_bool(tsp_start_counting_at_1, true, "TSPLIB convention: first node is 1 (not 0)."); + +// You can find the technical description of the TSPLIB in +// http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/DOC.PS + +// EOF is tested separately because it is sometimes redefined + +namespace operations_research { + +typedef int64 (*TWOD_distance_function)(const Point, const Point); +typedef int64 (*THREED_distance_function)(const Point, const Point); + +const int kTSPLIBDelimiter = -1; +const char * kTSPLIBEndFileDelimiter = "EOF"; + +#define TSPLIB_STATES \ + ENUM_OR_STRING( NAME ), \ + ENUM_OR_STRING( TYPE ), \ + ENUM_OR_STRING( COMMENT ), \ + ENUM_OR_STRING( DIMENSION ), \ + ENUM_OR_STRING( CAPACITY ), \ + ENUM_OR_STRING( EDGE_WEIGHT_TYPE ), \ + ENUM_OR_STRING( EDGE_WEIGHT_FORMAT ), \ + ENUM_OR_STRING( EDGE_DATA_FORMAT ), \ + ENUM_OR_STRING( NODE_COORD_TYPE ), \ + ENUM_OR_STRING( DISPLAY_DATA_TYPE ), \ + ENUM_OR_STRING( NODE_COORD_SECTION ), \ + ENUM_OR_STRING( DEPOT_SECTION ), \ + ENUM_OR_STRING( DEMAND_SECTION ), \ + ENUM_OR_STRING( EDGE_DATA_SECTION ), \ + ENUM_OR_STRING( FIXED_EDGE_SECTION ), \ + ENUM_OR_STRING( DISPLAY_DATA_SECTION ), \ + ENUM_OR_STRING( TOUR_SECTION ), \ + ENUM_OR_STRING( EDGE_WEIGHT_SECTION ), \ + ENUM_OR_STRING( TSPLIB_STATES_COUNT ), \ + ENUM_OR_STRING( TSPLIB_STATES_UNDEFINED ) + +// TYPE +#define TSPLIB_PROBLEM_TYPES \ + ENUM_OR_STRING( TSP ), \ + ENUM_OR_STRING( ATSP ), \ + ENUM_OR_STRING( CVRP ), \ + ENUM_OR_STRING( CCPP ), \ + ENUM_OR_STRING( TOUR ), \ + ENUM_OR_STRING( TSPLIB_PROBLEM_TYPES_COUNT ), \ + ENUM_OR_STRING( TSPLIB_PROBLEM_TYPES_UNDEFINED ) + +// EDGE_WEIGHT_TYPE +#define TSPLIB_EDGE_WEIGHT_TYPES \ + ENUM_OR_STRING( ATT ), \ + ENUM_OR_STRING( CEIL_2D ), \ + ENUM_OR_STRING( CEIL_3D ), \ + ENUM_OR_STRING( EUC_2D ), \ + ENUM_OR_STRING( EUC_3D ), \ + ENUM_OR_STRING( EXPLICIT ), \ + ENUM_OR_STRING( GEO ), \ + ENUM_OR_STRING( GEOM ), \ + ENUM_OR_STRING( GEO_MEEUS ), \ + ENUM_OR_STRING( GEOM_MEEUS ), \ + ENUM_OR_STRING( MAN_2D ), \ + ENUM_OR_STRING( MAN_3D ), \ + ENUM_OR_STRING( MAX_2D ), \ + ENUM_OR_STRING( MAX_3D ), \ + ENUM_OR_STRING( TSPLIB_EDGE_WEIGHT_TYPES_COUNT ), \ + ENUM_OR_STRING( TSPLIB_EDGE_WEIGHT_TYPES_UNDEFINED ) + +// EDGE_WEIGHT_FORMAT +#define TSPLIB_EDGE_WEIGHT_FORMAT_TYPES \ + ENUM_OR_STRING( FUNCTION ), \ + ENUM_OR_STRING( FULL_MATRIX ), \ + ENUM_OR_STRING( UPPER_ROW ), \ + ENUM_OR_STRING( LOWER_ROW ), \ + ENUM_OR_STRING( UPPER_DIAG_ROW ), \ + ENUM_OR_STRING( LOWER_DIAG_ROW ), \ + ENUM_OR_STRING( UPPER_COL ), \ + ENUM_OR_STRING( LOWER_COL ), \ + ENUM_OR_STRING( UPPER_DIAG_COL ), \ + ENUM_OR_STRING( LOWER_DIAG_COL ), \ + ENUM_OR_STRING( TSPLIB_EDGE_WEIGHT_FORMAT_TYPES_COUNT ), \ + ENUM_OR_STRING( TSPLIB_EDGE_WEIGHT_FORMAT_TYPES_UNDEFINED ) + +// EDGE_DATA_FORMAT +#define TSPLIB_EDGE_DATA_FORMAT_TYPES \ + ENUM_OR_STRING( EDGE_LIST ), \ + ENUM_OR_STRING( ADJ_LIST ), \ + ENUM_OR_STRING( TSPLIB_EDGE_DATA_FORMAT_TYPES_COUNT ), \ + ENUM_OR_STRING( TSPLIB_EDGE_DATA_FORMAT_TYPES_UNDEFINED ) + +// NODE_COORD_TYPE +#define TSPLIB_NODE_COORD_TYPE_TYPES \ + ENUM_OR_STRING( TWOD_COORDS ), \ + ENUM_OR_STRING( THREED_COORDS ), \ + ENUM_OR_STRING( NO_COORDS ), \ + ENUM_OR_STRING( TSPLIB_NODE_COORD_TYPE_TYPES_COUNT ), \ + ENUM_OR_STRING( TSPLIB_NODE_COORD_TYPE_TYPES_UNDEFINED ) + +// DISPLAY_DATA_TYPE +#define TSPLIB_DISPLAY_DATA_TYPE_TYPES \ + ENUM_OR_STRING( COORD_DISPLAY ), \ + ENUM_OR_STRING( TWOD_DISPLAY ), \ + ENUM_OR_STRING( NO_DISPLAY ), \ + ENUM_OR_STRING( TSPLIB_DISPLAY_DATA_TYPE_TYPES_COUNT ), \ + ENUM_OR_STRING( TSPLIB_DISPLAY_DATA_TYPE_TYPES_UNDEFINED ) + + +// Enum + +#undef ENUM_OR_STRING +#define ENUM_OR_STRING( x ) x + +enum TSPLIB_STATES_enum +{ + TSPLIB_STATES +}; + +enum TSPLIB_PROBLEM_TYPES_enum +{ + TSPLIB_PROBLEM_TYPES +}; + +enum TSPLIB_EDGE_WEIGHT_TYPES_enum +{ + TSPLIB_EDGE_WEIGHT_TYPES +}; + +enum TSPLIB_EDGE_WEIGHT_FORMAT_TYPES_enum +{ + TSPLIB_EDGE_WEIGHT_FORMAT_TYPES +}; + +enum TSPLIB_EDGE_DATA_FORMAT_TYPES_enum +{ + TSPLIB_EDGE_DATA_FORMAT_TYPES +}; + +enum TSPLIB_NODE_COORD_TYPE_TYPES_enum +{ + TSPLIB_NODE_COORD_TYPE_TYPES +}; + +enum TSPLIB_DISPLAY_DATA_TYPE_TYPES_enum +{ + TSPLIB_DISPLAY_DATA_TYPE_TYPES +}; + + +// Strings + +#undef ENUM_OR_STRING +#define ENUM_OR_STRING( x ) #x + +const char * TSPLIB_STATES_KEYWORDS[] = +{ + TSPLIB_STATES +}; + +const char * TSPLIB_PROBLEM_TYPES_KEYWORDS[] = +{ + TSPLIB_PROBLEM_TYPES +}; + + +const char * TSPLIB_EDGE_WEIGHT_TYPES_KEYWORDS[] = +{ + TSPLIB_EDGE_WEIGHT_TYPES +}; + + +const char * TSPLIB_EDGE_WEIGHT_FORMAT_TYPES_KEYWORDS[] = +{ + TSPLIB_EDGE_WEIGHT_FORMAT_TYPES +}; + +const char * TSPLIB_EDGE_DATA_FORMAT_TYPES_KEYWORDS[] = +{ + TSPLIB_EDGE_DATA_FORMAT_TYPES +}; + +const char * TSPLIB_NODE_COORD_TYPE_TYPES_KEYWORDS[] = +{ + TSPLIB_NODE_COORD_TYPE_TYPES +}; + +const char * TSPLIB_DISPLAY_DATA_TYPE_TYPES_KEYWORDS[] = +{ + TSPLIB_DISPLAY_DATA_TYPE_TYPES +}; + + +struct TSPLIBDistanceFunctions { + + typedef int64 (*TWOD_distance_function)(const Point, const Point); + typedef int64 (*THREED_distance_function)(const Point, const Point, const Point); + + static constexpr double PI = 3.141594; + // Earth radius in km + static constexpr double RRR = 6378.388; + + TSPLIBDistanceFunctions(const TSPLIB_NODE_COORD_TYPE_TYPES_enum dim , const TSPLIB_EDGE_WEIGHT_TYPES_enum type) { + switch (dim) { + case TWOD_COORDS: { + switch (type) { + case EUC_2D: { + TWOD_dist_fun_ = &TWOD_euc_2d_distance; + break; + } + + case GEO: + case GEOM: { + TWOD_dist_fun_ = &TSPLIBDistanceFunctions::TWOD_geo_distance; + break; + } + case ATT: { + TWOD_dist_fun_ = &TSPLIBDistanceFunctions::TWOD_att_distance; + break; + } + } +break; + } // case TWOD_COORDS: + + case THREED_COORDS: { + break; + } + + case NO_COORDS: { + break; + } + + case TSPLIB_NODE_COORD_TYPE_TYPES_UNDEFINED: { + break; + } + } + } + + + int64 TWOD_distance(const Point x, const Point y) { + return TWOD_dist_fun_(x,y); + } + + + + + + + // Rounds to the nearest int +static int64 nint(double d) +{ + return std::floor(d+0.5); +} + +// Convert longitude and latitude given in DDD.MM with DDD = degrees and MM = minutes +// into longitude and latitude given in radians. +static double convert_to_geo(double x) { + int64 deg = nint(x); + + return PI * (deg + 5.0 * (x - deg) / 3.0 ) / 180.0; +} + + +// 2D functions + +static int64 TWOD_euc_2d_distance(const Point a, const Point b) { + double xd = a.x - b.x; + double yd = a.y - b.y; + + return nint(std::sqrt(xd*xd + yd*yd));; + +} + +static int64 THREED_euc_3d_distance(const Point a, const Point b) { + double xd = a.x - b.x; + double yd = a.y - b.y; + double zd = a.z - b.z; + + return nint(std::sqrt(xd*xd + yd*yd + zd*zd));; + +} + +static int64 TWOD_max_2d_distance(const Point a, const Point b) { + double xd = std::abs(a.x - b.x); + double yd = std::abs(a.y - b.y); + + return std::max(nint(xd), nint(yd)); +} + +static int64 THREED_max_3d_distance(const Point a, const Point b) { + double xd = std::abs(a.x - b.x); + double yd = std::abs(a.y - b.y); + double zd = std::abs(a.z - b.z); + + return std::max(std::max(nint(xd), nint(yd)), nint(zd)); +} + +static int64 TWOD_man_2d_distance(const Point a, const Point b) { + double xd = std::abs(a.x - b.x); + double yd = std::abs(a.y - b.y); + + return nint(xd + yd); +} + + +static int64 THREED_man_3d_distance(const Point a, const Point b) { + double xd = std::abs(a.x - b.x); + double yd = std::abs(a.y - b.y); + double zd = std::abs(a.z - b.z); + + return nint(xd + yd +zd); +} + +static int64 TWOD_ceil_2d_distance(const Point a, const Point b) { + double xd = std::abs(a.x - b.x); + double yd = std::abs(a.y - b.y); + + return std::ceil(xd + yd); +} + +static int64 THREED_ceil_3d_distance(const Point a, const Point b) { + double xd = std::abs(a.x - b.x); + double yd = std::abs(a.y - b.y); + double zd = std::abs(a.z - b.z); + + return std::ceil(xd + yd +zd); +} + +static int64 TWOD_geo_distance(const Point a, const Point b) { + Point a_geo(convert_to_geo(a.x), convert_to_geo(a.y) ); + Point b_geo(convert_to_geo(b.x), convert_to_geo(b.y) ); + + double q1 = std::cos(a_geo.y - b_geo.y); + double q2 = std::cos(a_geo.x - b_geo.x); + double q3 = std::cos(a_geo.x + b_geo.x); + return (int64) RRR * std::acos( 0.5 * ((1.0 + q1)*q2 - (1.0 - q1) * q3)) + 1.0; + } + +static int64 TWOD_att_distance(const Point a, const Point b) { + double xd = a.x - b.x; + double yd = a.y - b.y; + + double rij = std::sqrt( (xd*xd + yd*yd) / 10.0); + int64 tij = nint(rij); + + return (tij < rij)? tij + 1: tij; +} +TWOD_distance_function TWOD_dist_fun_; + + // 3D functions +THREED_distance_function THREED_dist_fun_; + +}; + +void PrintFatalLog(const char * msg,const std::string & wrong_keyword, int line_number) { + LOG(FATAL) << "TSPLIB: " << msg << ": \"" << wrong_keyword << "\" on line " << line_number; +} + + +// Find the enum corresponding to a string +// This only works is the strings and enums are ordered in the same way +// and an "undefined enum" is placed at the end of the enum (hence the "index + 1"). +template +E FindEnumKeyword(const char** list,const std::string word,const E end_index) { + int index = 0; + for (; index < end_index; ++index) { + if (!strcmp(word.c_str(),list[index])) { + return (E) index; + } + } + return (E) (index + 1); +} + +// Find the enum corresponding to a string +// This only works is the strings and enums are ordered in the same way +// and an "undefined enum" is placed at the end of the enum (hence the "index + 1") +// and a "count enum" gives the number of elements in the enum (XXX_UNDEFINED = XXX_COUNT + 1). +// Print a LOG(FATAL) if no enum if found. +template +E FindOrDieEnumKeyword(const char** list, const std::string word, const E end_index, const char * err_msg, int line_number) { + E enum_element = FindEnumKeyword(list, word, end_index); + if (enum_element == end_index + 1) { + PrintFatalLog(err_msg, word, line_number); + } + return enum_element; +} + + +} // namespace operations_research + +#endif \ No newline at end of file diff --git a/documentation/tutorials/cplusplus/routing_common/tsplib_reader.h b/documentation/tutorials/cplusplus/routing_common/tsplib_reader.h new file mode 100644 index 0000000000..cbafa9ed3b --- /dev/null +++ b/documentation/tutorials/cplusplus/routing_common/tsplib_reader.h @@ -0,0 +1,618 @@ +// Copyright 2011-2014 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// TSPLIBReader. +// +// Only valid for: +// - TSP +// - ATSP +// - CVRP +// - CCPP (this is an extension) + +#ifndef OR_TOOLS_TUTORIALS_CPLUSPLUS_TSPLIB_READER_H +#define OR_TOOLS_TUTORIALS_CPLUSPLUS_TSPLIB_READER_H + +#include +#include +#include + +#include "base/integral_types.h" +#include "base/filelinereader.h" +#include "base/split.h" +#include "base/strtoint.h" + +#include "routing_common/routing_common.h" +#include "routing_common/routing_data.h" +#include "routing_common/tsplib.h" + +namespace operations_research { + class TSPLIBReader : public RoutingData { + public: + typedef std::vector::iterator solution_iterator; + typedef std::vector::const_iterator const_solution_iterator; + explicit TSPLIBReader(const std::string & filename) : + RoutingData(0), + line_number_(0), + visualizable_(false), + two_dimension_(false), + symmetric_(false), + need_to_compute_distances_(false), + tsplib_state_unknown_(true), + tsplib_state_(TSPLIB_STATES_UNDEFINED), + name_(""), + type_(TSPLIB_PROBLEM_TYPES_UNDEFINED), + comment_(""), + capacity_(-1), + edge_weight_type_(TSPLIB_EDGE_WEIGHT_TYPES_UNDEFINED), + edge_weight_format_type_(TSPLIB_EDGE_WEIGHT_FORMAT_TYPES_UNDEFINED), + edge_data_format_type_(TSPLIB_EDGE_DATA_FORMAT_TYPES_UNDEFINED), + node_coord_type_(TWOD_COORDS), // If no coord type is given, we assume 2D. + display_data_type_(TSPLIB_DISPLAY_DATA_TYPE_TYPES_UNDEFINED) + + { + LoadInstance(filename); + if (depots_.size() == 0) { + depots_.push_back(RoutingModel::kFirstNode); + } + SetRoutingDataInstanciated(); + } + TSPLIB_PROBLEM_TYPES_enum TSPLIBType () const { + return type_; + } + RoutingModel::NodeIndex Depot() const { + return depots_[0]; + } + +std::vector Depots() const { + return depots_; +} + +int32 Capacity() const { + return capacity_; +} + +int64 Demand(RoutingModel::NodeIndex i) const { + return demands_[i.value()]; +} + bool HasDimensionTwo() const { + return two_dimension_; + } + TSPLIB_NODE_COORD_TYPE_TYPES_enum NodeCoordinateType() const { + return node_coord_type_; + } + + TSPLIB_DISPLAY_DATA_TYPE_TYPES_enum DisplayDataType() const { + return display_data_type_; + } + + TSPLIB_EDGE_WEIGHT_TYPES_enum EdgeWeightType() const { + return edge_weight_type_; + } + + TSPLIB_EDGE_WEIGHT_FORMAT_TYPES_enum EdgeWeightTypeFormat() const { + return edge_weight_format_type_; + } + + solution_iterator solution_begin() { + return tsp_sol_.begin(); + } + const_solution_iterator solution_begin() const { + return tsp_sol_.begin(); + } + solution_iterator solution_end() { + return tsp_sol_.end(); + } + const_solution_iterator solution_end() const { + return tsp_sol_.end(); + } + protected: + void LoadInstance(const std::string& filename); + // Helper function + int64& SetMatrix(int i, int j) { + return distances_.Cost(RoutingModel::NodeIndex(i), RoutingModel::NodeIndex(j)); + } + + private: + void ProcessNewLine(char* const line); + + std::vector depots_; + int line_number_; + bool visualizable_; + bool two_dimension_; + bool symmetric_; + bool need_to_compute_distances_; + + TSPLIB_STATES_enum tsplib_state_; + bool tsplib_state_unknown_; + + TSPLIB_PROBLEM_TYPES_enum type_; + std::string name_; + std::string comment_; + int32 capacity_; + TSPLIB_EDGE_WEIGHT_TYPES_enum edge_weight_type_; + TSPLIB_EDGE_WEIGHT_FORMAT_TYPES_enum edge_weight_format_type_; + TSPLIB_EDGE_DATA_FORMAT_TYPES_enum edge_data_format_type_; + TSPLIB_NODE_COORD_TYPE_TYPES_enum node_coord_type_; + TSPLIB_DISPLAY_DATA_TYPE_TYPES_enum display_data_type_; + + TWOD_distance_function TWOD_dist_fun_; + THREED_distance_function THREED_dist_fun_; + + std::vector tsp_sol_; + std::vector demands_; + }; + + void TSPLIBReader::LoadInstance(const std::string& filename) + { + + FileLineReader reader(filename.c_str()); + reader.set_line_callback(NewPermanentCallback( + this, + &TSPLIBReader::ProcessNewLine)); + reader.Reload(); + if (!reader.loaded_successfully()) { + LOG(FATAL) << "Could not open TSPLIB instance file: " << filename; + } + } + + + void TSPLIBReader::ProcessNewLine(char*const line) { + ++line_number_; + VLOG(2) << "Line " << line_number_ << ": " << line; + // Must always be -1 outside a section + static int32 nodes_nbr = -1; + static bool read_matrix_done = false; + + static const char kWordDelimiters[] = " :"; + std::vector words; + words = strings::Split(line, kWordDelimiters, strings::SkipEmpty()); + + // Empty lines + if (words.size() == 0) { + return; + } + + // FIND TSPLIB KEYWORD + if (tsplib_state_unknown_) { + bool keyword_found = false; + //read_matrix_done = false; + + tsplib_state_ = FindEnumKeyword(TSPLIB_STATES_KEYWORDS, words[0], TSPLIB_STATES_COUNT); + if (tsplib_state_ != TSPLIB_STATES_UNDEFINED) { + keyword_found = true; + } + + // separate test because "EOF" is sometimes redefined + if (words[0] == kTSPLIBEndFileDelimiter) { + return; + } + + if (!keyword_found) { + PrintFatalLog("Unknown keyword", words[0], line_number_); + } + + tsplib_state_unknown_ = false; + } + + // SWITCH FOLLOWING TSPLIB KEYWORD + switch (tsplib_state_) { + case NAME: { + name_ = words[1]; + tsplib_state_unknown_ = true; + break; + } + case TYPE: { + type_ = FindOrDieEnumKeyword(TSPLIB_PROBLEM_TYPES_KEYWORDS, words[1], TSPLIB_PROBLEM_TYPES_COUNT, "Unknown problem type", line_number_); + tsplib_state_unknown_ = true; + break; + } + case COMMENT: { + if (words.size() > 1) { + for (int index = 1; index < words.size(); ++index) { + comment_ = StrCat(comment_, words[index] + " "); + } + } + tsplib_state_unknown_ = true; + break; + } + case DIMENSION: { + int32 size = atoi32(words[1]); + CreateRoutingData(size); + tsplib_state_unknown_ = true; + break; + } + case CAPACITY: { + capacity_ = atoi32(words[1]); + tsplib_state_unknown_ = true; + break; + } + case DEPOT_SECTION: { + if (nodes_nbr == -1) { + // titel + ++nodes_nbr; + break; + } + if (atoi32(words[0]) == -1) { + nodes_nbr = -1; + tsplib_state_unknown_ = true; + break; + } + depots_.push_back(RoutingModel::NodeIndex(atoi32(words[1]) - 1)); + ++nodes_nbr; + break; + } + case DEMAND_SECTION: { + if (nodes_nbr == -1) { + // titel + demands_.resize(Size()); + ++nodes_nbr; + break; + } + + if (nodes_nbr == Size() - 1) { + tsplib_state_unknown_ = true; + nodes_nbr = -1; + break; + } + CHECK_EQ(words.size(), 2) << "Demand section should only contain node_id and demand on line " << line_number_; + CHECK_LE(atoi32(words[0]), Size()) << "Node with node_id bigger than size of instance on line " << line_number_; + demands_[atoi32(words[0]) - 1] = atoi32(words[1]); + ++nodes_nbr; + break; + } + case TOUR_SECTION: { + if (nodes_nbr == -1) { + // titel + tsp_sol_.resize(Size()); + ++nodes_nbr; + break; + } + if (nodes_nbr == Size()) { + CHECK_EQ(atoi32(words[0]), -1) << "Tour is supposed to end with -1."; + tsplib_state_unknown_ = true; + break; + } + RoutingModel::NodeIndex node(atoi32(words[0]) - 1); + tsp_sol_[nodes_nbr] = node; + ++nodes_nbr; + break; + } + case EDGE_WEIGHT_TYPE: { + edge_weight_type_ = FindOrDieEnumKeyword(TSPLIB_EDGE_WEIGHT_TYPES_KEYWORDS, + words[1], + TSPLIB_EDGE_WEIGHT_TYPES_COUNT, + "Unknown edge weight type", + line_number_); + // Do we need to compute the distances? + switch (edge_weight_type_) { + case EXPLICIT: { + need_to_compute_distances_ = false; + break; + } + case EUC_2D: { + need_to_compute_distances_ = true; + two_dimension_ = true; + symmetric_ = true; + visualizable_ = true; + TWOD_dist_fun_ = &TSPLIBDistanceFunctions::TWOD_euc_2d_distance; + break; + } + case EUC_3D: { + need_to_compute_distances_ = true; + two_dimension_ = false; + symmetric_ = true; + visualizable_ = true; + THREED_dist_fun_ = &TSPLIBDistanceFunctions::THREED_euc_3d_distance; + break; + } + case MAX_2D: { + need_to_compute_distances_ = true; + two_dimension_ = true; + symmetric_ = true; + visualizable_ = true; + TWOD_dist_fun_ = &TSPLIBDistanceFunctions::TWOD_max_2d_distance; + break; + } + case MAX_3D: { + need_to_compute_distances_ = true; + two_dimension_ = false; + symmetric_ = true; + visualizable_ = true; + THREED_dist_fun_ = &TSPLIBDistanceFunctions::THREED_max_3d_distance; + break; + } + case MAN_2D: { + need_to_compute_distances_ = true; + two_dimension_ = true; + symmetric_ = true; + visualizable_ = true; + TWOD_dist_fun_ = &TSPLIBDistanceFunctions::TWOD_man_2d_distance; + break; + } + case MAN_3D: { + need_to_compute_distances_ = true; + two_dimension_ = false; + symmetric_ = true; + visualizable_ = true; + THREED_dist_fun_ = &TSPLIBDistanceFunctions::THREED_man_3d_distance; + break; + } + case CEIL_2D: { + need_to_compute_distances_ = true; + two_dimension_ = true; + symmetric_ = true; + visualizable_ = true; + TWOD_dist_fun_ = &TSPLIBDistanceFunctions::TWOD_ceil_2d_distance; + break; + } + case CEIL_3D: { + need_to_compute_distances_ = true; + two_dimension_ = false; + symmetric_ = true; + visualizable_ = true; + THREED_dist_fun_ = &TSPLIBDistanceFunctions::THREED_ceil_3d_distance; + break; + } + case GEO: + case GEOM: { + need_to_compute_distances_ = true; + two_dimension_ = true; + symmetric_ = true; + visualizable_ = true; + TWOD_dist_fun_ = &TSPLIBDistanceFunctions::TWOD_geo_distance; + break; + } + case ATT: { + need_to_compute_distances_ = true; + two_dimension_ = true; + symmetric_ = true; + visualizable_ = true; + TWOD_dist_fun_ = &TSPLIBDistanceFunctions::TWOD_att_distance; + break; + } + } // switch (edge_weight_type_) + tsplib_state_unknown_ = true; + break; + } + case EDGE_WEIGHT_FORMAT: { + edge_weight_format_type_ = FindOrDieEnumKeyword(TSPLIB_EDGE_WEIGHT_FORMAT_TYPES_KEYWORDS, + words[1], + TSPLIB_EDGE_WEIGHT_FORMAT_TYPES_COUNT, + "Unknown edge weight format type", + line_number_); + tsplib_state_unknown_ = true; + break; + } + case EDGE_DATA_FORMAT: { + edge_data_format_type_ = FindOrDieEnumKeyword(TSPLIB_EDGE_DATA_FORMAT_TYPES_KEYWORDS, + words[1], + TSPLIB_EDGE_DATA_FORMAT_TYPES_COUNT, + "Unknown edge data format type", + line_number_); + tsplib_state_unknown_ = true; + break; + } + case NODE_COORD_TYPE: { + node_coord_type_ = FindOrDieEnumKeyword(TSPLIB_NODE_COORD_TYPE_TYPES_KEYWORDS, + words[1], + TSPLIB_NODE_COORD_TYPE_TYPES_COUNT, + "Unknown node coord format type", + line_number_); + tsplib_state_unknown_ = true; + break; + } + case DISPLAY_DATA_TYPE: { + display_data_type_ = FindOrDieEnumKeyword(TSPLIB_DISPLAY_DATA_TYPE_TYPES_KEYWORDS, + words[1], + TSPLIB_DISPLAY_DATA_TYPE_TYPES_COUNT, + "Unknown display data format type", + line_number_); + switch (display_data_type_) { + case NO_DISPLAY: + break; + case COORD_DISPLAY: + case TWOD_DISPLAY: + visualizable_ = true; + break; + } + tsplib_state_unknown_ = true; + break; + } + case NODE_COORD_SECTION: { + if (nodes_nbr == -1) { + ++nodes_nbr; + visualizable_ = true; + break; + } + ++nodes_nbr; + switch (node_coord_type_) { + case TWOD_COORDS: { + CHECK_EQ(words.size(), 3) << "Node coord data not conform on line " << line_number_; + CHECK_LE(atoi32(words[0].c_str()), size_) << "Unknown node number " << atoi32(words[0].c_str()) << " on line " << line_number_; + coordinates_[atoi32(words[0].c_str()) -1] = Point(atof(words[1].c_str()), atof(words[2].c_str())); + break; + } + case THREED_COORDS: { + CHECK_EQ(words.size(), 4) << "Node coord data not conform on line " << line_number_; + CHECK_LE(atoi32(words[0].c_str()), size_) << "Unknown node number " << atoi32(words[0].c_str()) << " on line " << line_number_; + coordinates_[atoi32(words[0].c_str()) -1] = Point(atof(words[1].c_str()), atof(words[2].c_str()), atof(words[3].c_str())); + break; + } + case NO_COORDS: { + LOG(FATAL) << "Coordinate is non existent but there is a node coordinate section???"; + break; + } + default: + LOG(FATAL) << "Coordinate type is not defined."; + } + if (nodes_nbr == size_) { + SetHasCoordinates(); + // Compute distance if needed + TSPLIBDistanceFunctions tsplib_dist_function(node_coord_type_, edge_weight_type_); + int64 dist = 0; + if (need_to_compute_distances_) { + LG << "Computing distance matrix..."; + // TWO DIMENSION + if (two_dimension_) { + // SYMMETRIC + if (symmetric_) { + for (int i = 0; i < size_; ++i) { + for (int j = i + 1; j < size_; ++j ) { + dist = tsplib_dist_function.TWOD_distance(coordinates_[i], coordinates_[j]); + SetMatrix(i,j) = dist; + SetMatrix(j,i) = dist; + } + } + for (int i = 0; i < size_; ++i) { + SetMatrix(i,i) = 0LL; + } + // NOT SYMMETRIC + } else { + for (int i = 0; i < size_; ++i) { + for (int j = 0; j < size_; ++j ) { + if (i == j) { + SetMatrix(i,j) = 0LL; + } else { + SetMatrix(i,j) = dist; + } + } + } + } + // THREE DIMENSION + } else { + + } + LG << "Computing distance matrix... Done!"; + } // if (tsplib_dist_function.NeedToComputeDistances()) + tsplib_state_unknown_ = true; + nodes_nbr = -1; + } + break; + } // case NODE_COORD_SECTION: + case DISPLAY_DATA_SECTION: { + if (nodes_nbr == -1) { + ++nodes_nbr; + break; + } + if (display_data_type_ == TWOD_DISPLAY) { + CHECK_EQ(words.size(), 3) << "Display data not conform on line " << line_number_; + CHECK_LE(atoi32(words[0].c_str()), size_) << "Unknown node number " << atoi32(words[0].c_str()) << " on line " << line_number_; + display_coords_[atoi32(words[0].c_str()) -1] = Point(atof(words[1].c_str()), atof(words[2].c_str())); + ++nodes_nbr; + if (nodes_nbr == size_) { + SetHasDisplayCoordinates(); + tsplib_state_unknown_ = true; + nodes_nbr = -1; + } + } else { + tsplib_state_unknown_ = true; + nodes_nbr = -1; + } + break; + } + case EDGE_DATA_SECTION: { + if (words.size() == 1 && words[0] == "-1") { + // complete matrix + //TO DO + read_matrix_done = true; + tsplib_state_unknown_ = true; + break; + } + switch(edge_data_format_type_) { + case EDGE_LIST: { + CHECK_EQ(words.size(), 2) << "Edge not well defined on line " << line_number_; + break; + } + case ADJ_LIST: { + break; + } + } + } + case EDGE_WEIGHT_SECTION: { + if (nodes_nbr == -1) { + ++nodes_nbr; + read_matrix_done = false; + break; + } + switch (edge_weight_format_type_) { + case FULL_MATRIX: { + CHECK_EQ(words.size(),size_) << "Matrix not full on line " << line_number_; + for (int index = 0; index < size_; ++index) { + int64 dist = atoi64(words[index].c_str()); + SetMatrix(nodes_nbr, index) = dist; + } + if (nodes_nbr == size_ - 1) { + read_matrix_done = true; + } + break; + } + case UPPER_ROW: { + CHECK_EQ(words.size(), size_ - nodes_nbr - 1) << " Wrong number of tokens on line " << line_number_; + SetMatrix(nodes_nbr, nodes_nbr) = 0LL; + for (int index = 0; index < size_ - nodes_nbr - 1; ++index) { + int64 dist = atoi64(words[index].c_str()); + SetMatrix(nodes_nbr, index + nodes_nbr + 1) = dist; + SetMatrix(index + nodes_nbr + 1, nodes_nbr) = dist; + } + if (nodes_nbr == size_ - 2) { + read_matrix_done = true; + } + break; + } + case UPPER_DIAG_ROW: { // BUGGY? + CHECK_EQ(words.size(), size_ - nodes_nbr - 1); + for (int index = 0; index < size_ - nodes_nbr ; ++index) { + int64 dist = atoi64(words[index].c_str()); + std::cout << dist << " "; + SetMatrix(nodes_nbr, index + nodes_nbr) = dist; + SetMatrix(index + nodes_nbr , nodes_nbr) = dist; + } + std::cout << std::endl; + if (nodes_nbr == size_ - 2) { + read_matrix_done = true; + } + break; + } + case LOWER_ROW: { // TO BE CHECKED + CHECK_EQ(words.size(), nodes_nbr + 1); + SetMatrix(nodes_nbr, nodes_nbr) = 0LL; + for (int index = 0; index < nodes_nbr + 1; ++index) { + int64 dist = atoi64(words[index].c_str()); + std::cout << dist << " "; + SetMatrix(nodes_nbr, index) = dist; + SetMatrix(index , nodes_nbr) = dist; + } + std::cout << std::endl; + if (nodes_nbr == size_ - 2) { + read_matrix_done = true; + break; + } + break; + } + } // switch (edge_weight_format_type_) + + if (read_matrix_done) { + tsplib_state_unknown_ = true; + nodes_nbr = -1; + break; + } + ++nodes_nbr; + } // case EDGE_WEIGHT_SECTION: + } // switch + } // ProcessNewLine() + +} // namespace operations_research + + +#endif // OR_TOOLS_TUTORIALS_CPLUSPLUS_TSPLIB_READER_H diff --git a/documentation/user_manual/_images/LNS_basic_pseudo_code.png b/documentation/user_manual/_images/LNS_basic_pseudo_code.png new file mode 100644 index 0000000000000000000000000000000000000000..7ef1eb70ca033e75f0059f781f966d310c57b882 GIT binary patch literal 18837 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 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/SA_pseudo_code.png b/documentation/user_manual/_images/SA_pseudo_code.png new file mode 100644 index 0000000000000000000000000000000000000000..f8c2c940d6fedfe28c17ab98c4d688786628a1ba GIT binary patch literal 48971 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 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/local_search_basic.png b/documentation/user_manual/_images/local_search_basic.png index de2f97125129317fcc202fec778c45a805620d95..eef015ad64ed719c2503b1ec6929971586e2e3cf 100644 GIT binary patch literal 9239 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 HY0&=y0{2%5 literal 20555 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≤yWZW*CrX~K^OuEhZB-R9@|pcKHA8d6kqkq#%^t6ma@cf^_lIHI&#~p2snHd zBG7dmINTQnCiaz;4PlnrDv1OTv%Gi6W6bKN1y>bSH1uO7`d|))^m|X$sVGl?vEs!LGHNJ=R&D!gnhkNIY8>JMBUwLXET*Ul{EnQuo zn5YRXlha+d-wmLKNg^UrxFmnIq9JKI^>W=f9hGb7!zrWu>3LM_v)dkh`ZB_53cZ8?mcPb1MLqiyD%T?B|!1 zE~v>{>Y5Sx+LK9XlCZyFY~PFRjbF=Do1RSrBl12y9i>=jO(|cy8HmvZZrD`)cwate zlABx`v7JxjE%$-X9}5=A<^{|}?@teh)_wc!MI1LSwfg(PRSOBGd@``2#`*#KLKvhH zl0p+8U?Bx}#X~v%3sGJ3+h(6del8yWb486I4LP!87+2m(Dyz`+swvYJtu~l`+1v(t zcW!Pbh;dl1k(HuOeumX&s&%_>okUkxls+S~rP5rfYiE0u!H?L5+VBfUm6R-t9b>)K zKra{tc?65iNsgGvn~!>JG!I?gRM`KC?|N|mza9h%KPnzRQ}x_A@P`8q4NTL) z2>C)iqZ_?bTlz`nm{3=LMw4K`?yR#iJEI~B*T(7YT=S!m-PO9h6|I*Pq|QSmMz$!( z)Q=7;lFp=tI2FTUgk33iZ0G0SrgMCzAETKBcLemBofrP%eUtFyaCNDd;geUk`(aj&@b7S{J4<4da0{G^y2e~p@nqt}tyYREnWEv4H&bhSNIAr{b?9qWwl&{U0PK+9^%^YM%zfP12> zC~_1$4PezvBfxAyo}@aTC@g319W<#CqY`r^jBcXDsqCN;O8Gv*3aZq3eq?h)$abM$ zbN_?fns2HG_Z>5WVlxu2W#AFlNqBI9z+oP6S7Y(K?BRug!w?=YSC}^f2J5-5p8P|D zbYfy^67~N#c!~_eg9b;9J$yq(oJ+lgCkGHIgv3-rasV+K;^PC)_VWJH#lzdj1@7zX z(Rb5U1?nIBSpPge6Q2e-lSoX!pCuA9Ts_=i2#3J1uwW0E0}STr27|%(dC#8x4m5!T M2Zevd35dV>UwB@VWB>pF diff --git a/documentation/user_manual/_images/local_search_basic1.png b/documentation/user_manual/_images/local_search_basic1.png new file mode 100644 index 0000000000000000000000000000000000000000..becc44743967fed562e33e2ef9e3d0c903440847 GIT binary patch literal 7209 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 BG&TSL literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/051510e1a32cb0a43379c30067d242d3e8ca6839.png b/documentation/user_manual/_images/math/051510e1a32cb0a43379c30067d242d3e8ca6839.png new file mode 100644 index 0000000000000000000000000000000000000000..84fd5109efc40fa31d716cf12f3b7a4c96e0c36c GIT binary patch literal 718 zcmeAS@N?(olHy`uVBq!ia0vp^tASXAg&9aPL|y0tk_G`jA+G=b{|AW>4fO9Ws0J$L zFA4GsW++)dw_9R=g2(Td=Re;ys!iWI4=BZ1;1OBOz`%C|gc+x5^GP!>FbR9QIEGl9 z-Wy`=ArmNKYy7U}&l`)n4cw8v5l7O5byTh%_FtUjBj7vvfx2SH*@Q(2iaxuY3b?or zrb!<>bclaZ!sZqcGe*VeE~tt8@`z*R2|e}Dh?{#Kk@bR zn8p3;f7i#|T_>;1Tf!rG`=gGH#QFW<-=rJ8tM3}!sw#S0blY>)TJc~*rtV80(gM$3 zjJCY_Nb;s)zXpfs+HQvn`nIA|r*4~BZv98&wYIR-Dv4n^WfK-qH&}VW%iQ$Q z#D-^KFZ5T*$h~hqbx@C&^Lo3|1&(6@Z5t0q_pW@$d}|v2O{44Kac3;n9$YoI)amb3 zdpn+sldk=(Wjp=I_mwK|+0>H>YchM>KW$n%AxGi|ceg>!DTj%DCxK~(tHd>;#3i+$ XBmF1`+}|%tLVZ0a0;M<$JR*x382FBWFymBhK4}I9#(Yl~#}JM4 zt&?oM4+n^}-M=PSMVD;}f$M3#ZFY-xu(@Hm1%n{O#PB{^HXrvhh8yPuY0k&b7QvLz3is@ zcDdC-Z=rtspWkCoi0p2bKdPP*slTh|C@?;_N?apKTv7{4GC;A+qTr`03SxM=`njxg HN@xNA0`u7% literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/0b4eaa0da05ee92eb6ddc7199719650597a7e8b3.png b/documentation/user_manual/_images/math/0b4eaa0da05ee92eb6ddc7199719650597a7e8b3.png new file mode 100644 index 0000000000000000000000000000000000000000..81b318baff1c271f4f84651023aa91a501403225 GIT binary patch literal 897 zcmeAS@N?(olHy`uVBq!ia0vp^TYy-bg&9aHU*10*NErn9gt-3y{~yRC9_Zg)Pz_Yg zUlQaO%pkG;^ZAmO-3dm&_urlCvCyUZ0Z@vwz$3Dlfr0M`2s2LA=96Y%U^?dM;uzv_ zd~3LOhH#*W?d1RYYL1TFTAaG3kNU+}rn7182x9x#ETY^{xe}%|WR?9Ek5vO)Dy$x*4EdL~U!olV}UrCMMZ1b}1MnO&5 zUzX&(b9>4VzwC{f`5_La8sVY^fv2Tabz-=D_qTq_blZ zE?&dGbN18}o&9^{AZ|G1i*@(O7gl zX0ycNmbLO$SI?dLz@g9{@#4(SlAbpPtEjlAA+!+Up*!(nG1L7jCSO~-vR zs;_kCpLllT-TKLlflZ7=6q_ZEMBJYC`36W$lRswrK5 zniKVx+&eOT!Pm#iow}M!UU}nKI|C=W@;|>HGdt z^MG#lWeb^w(Wd9@&l#K*Gb}!00!-#yC9V-AE~y1289)vLV}Q!VSs;d|tDnm{r-UW| DlM9LS literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/169d51d87e0861f4c5a32cab44a96601bb80f190.png b/documentation/user_manual/_images/math/169d51d87e0861f4c5a32cab44a96601bb80f190.png new file mode 100644 index 0000000000000000000000000000000000000000..fbca174c111ef46b57a8d4f95b11be6b1ab0575f GIT binary patch literal 763 zcmeAS@N?(olHy`uVBq!ia0vp^8-ZAeg&9ab>sQ(aqznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsW-#)&+xbm>fJ^978Nl zubpx>`$~YwQU6OmCceg#TVpjmFuSGz&k57cLXhe*9Ub%DsoIBT${p*r&=>wJ}ozy7^nzgT@=>wnieP9KZRlL?n=HJ@Knzat{?jx}gckAdfs zsyOG9K3ba+J{9Q~?VtWV{ES*zXWzmpi>iF=u5{O2Y|eXh^I5Tcr1p%N)A^XM%Uv;2 znz%8kt3 z6#Vt{y!!Tab*;79kYjVGylZQdTVar7o9jH~AIr8}(~n3$VVSD9w@uY*(T;+7A9ieY z+~#xUfp|?g|HEnvUDpdS@d08>CY(&z^iO!>zWGUhY?9Tp*ss2P^fP(&*HqhGhf`kP zh{`^?RQk+({|VLx5$Q$0Yv=7*ey^8D!Ev3;6Q=Jgd6p%ePpZ{xIryu1l}5V3<`!1H z=dJ5*P7q2t$L6Cny+veF`Rr#pYR9b{*=iw`x7fcB-e+J{ z)~S^Cq3wCn+*Nt&*UFYIwY-&4Z1LbDZ|K~>(|?Yi`f`{d{g}(n+NG6y{-~CPWqaAReEnzjveOnH#05L|u4i zmjw9*Gjy+)xLe}!a=+2}-*Xc_v+?g~0ZMTectjR6Fz_7#VaBQ2e9{aIjB`9)978nD zPn~?;n<-G>`2KHErMYMCE_rj|AkVIC%Q;hbuPm=i)jrgomf-hM{m<^B@(20+PYKNUZL+9= z_XUTL>Ai1)Q$2L$_C1{F%=Kvr%bo{P76*8~y=?s5cx_R1TdwmY(@L%li+n8SU*GM0 zuR+>}L3`5)i)^MIZcVHI!2y;>mzQ}kR-Adp5bV}CJMTmA7iZybClqGctWRSK+ literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/1893277d05fadf0c14f8c26c3cbf3afb3fd7f680.png b/documentation/user_manual/_images/math/1893277d05fadf0c14f8c26c3cbf3afb3fd7f680.png new file mode 100644 index 0000000000000000000000000000000000000000..5a7e197f822e31f1b8faaed4073b6b4142fd7f54 GIT binary patch literal 679 zcmeAS@N?(olHy`uVBq!ia0vp^Wk4*%!VDy9-k9eBDT4r?5ZC|z{{xxC1O2-Ts)4Hc zOM?7@8M=R;U%y`>!KlRJ<=nfU(}VUN21;=jctjR6Fz_7#VaBQ2e9{aIjBh+$978Nl z&z(%HUNJk5FAza3Va9>(vm75iLsd41B4&wj7txbOL!h20B#qi*pu z*1_0#(Tq#;GEaF<++DT&@T~MLk#9E~5Rbd&l<#JLRp)c3^voMM@0cb^FTLebt>L=o zP}13(k0fqxs?P4ZnR9s7`t>gzI4>u+S9z%UF`f;_ltB-!p5E%AdGf)}!>{ k_#R+l;3{#AC~-+GD9HdNF%|_sRZ$Sb)78&qol`;+089QM@Bjb+ literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/1e8aa82efdd03747feb4ddd348d0625e67eff5ea.png b/documentation/user_manual/_images/math/1e8aa82efdd03747feb4ddd348d0625e67eff5ea.png new file mode 100644 index 0000000000000000000000000000000000000000..b1531d9012c903b6cfddb94b96df6675bab33751 GIT binary patch literal 719 zcmeAS@N?(olHy`uVBq!ia0vp^w}4oPgBeJ2RTvlmDa!z#5ZC|z{{xw10H&B~AD}j& zk|4iehLY~}3FnP|d%Tp`fA{m;pHE+3xUnS=D9c&k5n0T@z;^_M8K-LVNi#4oad^5o zhIkx*8-9D*5(OR=MF*CY@}K|v=U&L_-m{bzNHbSh}g{rR=Gf z&vqKNdW27ooX^*Ihg_#$~@>z(4z1fXeDM z%ujcC&s^VoXTFwnl&UUI(uE%mleIPne~v#Bdtri=<|+N1Hw6l11dAl-!&c7i>>z9 zy!%pG#_cbTNBskIZgfdyR=3q`xbJ&a@1?Q!`dICE!jfCOKF^HG^bb45zpv@v@AG>M zQ+xwuV#D-re_mIppd`?C?T6Y`w)8(IBSpe4#pKByUDjDI`I)g1Wkm)YW~@n*lPjmZl^0fc(m3s*w)V}l+RGKb z1?GGUyItF39xmCkxhQV`_G!&YZ(=i#Z*N?EX~h)Nn$9hoDz@Ad`*_OT)vvZ(=##$q zyCr|G*St!&bnt)vMRT+zFI#y5-7!4;1OBOz`%C|gc+x5^GO2*uY0;UhG?8m z7BEUsV3;K$AtE6m!>nNQ`W@#BE{ktoMg|QcOTRlmv0#;$wn#fG@q{^pcG81(jUkDAIKyg=-*vX4OGov z666=mkWg}7qWibUT%+}$@9uxud7nWPD8*Ue5n0T@z;^_M8K-LVNi#4oReHKOhIkxb zJIyyk*ioYGer);VH_LpR6ApH}Y+1o*C}Fdu-DtW-$EAg)?A-}3mR>*F91!q|-}cEi zr3y`xwM(xwdrXWwdT$B4;tKYQ-U})|iX4^W`git?kGI*IoJH366Vr@*=AEheZ9AED zQj2TzxwdH%DonSoIPKQ|EvUO?+M8ml1huKnJXam0KRi{~J2fYKa(cw+-$>Ov?}Ia^9N%f+KAI5#D>6 zbAQV$neG2<#hHAcKL=(UjrYuhT+i2t+iOm1(7Nb$D7>Bwf?vEyUZxt6_@*J z@!UH{-E+5mSt=F0X~u+a`)u|F%Q=6D{~_jb-IIOAj`?3Nv~}#9sQGfy+25A^|Gw{T z{`%*8m3nU0LcZlw|J)6FQRX;foo3q7mr;i@y)SP)vN4{0>o;+U4{Kb^b~v^GQzlo5 dYeb1lYC%Z`IK8VYKjZ;1JYD@<);T3K0Ra6gW@rEa literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/2b2c91b7bf7514e1e910f722cd4138d1a9980ede.png b/documentation/user_manual/_images/math/2b2c91b7bf7514e1e910f722cd4138d1a9980ede.png new file mode 100644 index 0000000000000000000000000000000000000000..ec2925770d83fdc964b4dddcb50cd462616f551c GIT binary patch literal 380 zcmeAS@N?(olHy`uVBq!ia0vp^c0er5!VDy>?k=7WqznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsW+>@4+Q0s#$M4S)bI;#Rn8hXh7AVD8;1OBOz`%C|gc+x5^GO2*4|=*dhG?8m zPB_52gID4M<2PxZ9|xtGKKZdcQFqRCb4}FQv|Chqflhc^h)-r5>pW%wv0x@=#%2xX z!z>E3pYm|9FhpkY*l-C1^06wEGcq%;%AYM^!K1L~+l{8ipT^FtQzH4F6v`(A=sK9q z|I2(h&aOjaIb-vtLpsM=88z5S3YQu=y3A~zB4(gou#scI3475kH(Lc#LlY7Xm#{fa zDHddAU#2X$Xw7WNEW@XPF6GI+%sVn1jLb4yUW78{d~yh6SaC%s(Pn3e2GBEHC9V-A bE~y1289)vL+c^gpOAy1;)z4*}Q$iB}c+YHe literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/3010515ceeb0b173969e370cc46acb837b622b17.png b/documentation/user_manual/_images/math/3010515ceeb0b173969e370cc46acb837b622b17.png new file mode 100644 index 0000000000000000000000000000000000000000..6873e32d22c646421c44e51706324b049e76a463 GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^?m#TW!VDyT-@TUsqznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsX1II4#AB|JM0di=-|P2(-fQ%`7AVD8;1OBOz`%C|gc+x5^GO2*KYF@2hG?8m z7BEUsV3;K$Au{3T8NrDHZtelhGoE!!O-XPFHksIXn$hL6$%g+7+*^#FNH5S9N_)WY zxsp}FtdVt|fq|_<_?OnkV&)cB(@b{e^Xw+BIrk+Z_Bqe6VG1zhnH;gNQQ!vqbgl(S zU)d}~4L9U8c6B<{JlEi0S(Na)A+IUw_yXRA5zGcn57e2BZ#?wj<9RN8Kxr9|M3;(0 z#KD?`2QQjgEuGzfD4%2v(DPg+t`Q|J asRbpVP-kEb_Y`dgF+5%UT-G@yGywpuT7~!k literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/30cc1ce4f5e733f6bcd0099f5a944f316e143c51.png b/documentation/user_manual/_images/math/30cc1ce4f5e733f6bcd0099f5a944f316e143c51.png new file mode 100644 index 0000000000000000000000000000000000000000..26b70c7d960e0411a325facdb043b2d4b8c6b152 GIT binary patch literal 1210 zcmV;r1V#IaP)GRJRopis&a5WtO000SaNLh0L01m_e01m_fl`9S#000C3NklF|9LL|R>ul@n>d+xdp%!8(JZxfC7m*1M86J|TqqHCyn3|}eq||mW2s&(D zI(XSG1_rre6hYvG{i7rXDlAB{^yI;`A_C2A=l{H!H?wbi%g(;bJm356&(FN~=J$Tz z9{}jq0gADe|1L_b$w)C$h5r@hVwEP84pXmmsG9A2)0~jDOT5|}SDq)51n$Q3gmq0& zdnye@f|bk{R$N{G#C)6>OLIbuC4PwhcSk{Z`Dp|P>m&es8|CgtXaaJgV%CyiHG_RZ zc`E>~1_p+6VZ{Kr5lBnjDQH4qdD;%78_5F^#yDLtJIWxpqvn4(H!92V|}Kf zO+-2&!iu$bK^sEd$H5v1!Ex5)gdFQ!CX<4qfPKd5o2;$=C9g`X8JaOW5kJQ-8{W%_I_uC^ z!F#N1yqOoDD`Et^Cmrop@2Th(08HOql=U7fkg_WM>)vCPaF0SMcoV^8@0TkW;khx> z*p8&*>O){TuN(A62qDXFPC4#WU#e&k-J<78?oAuE)Z(>1xx%kV4-9*-M|_ox4wxkCAWlC$0!>{C;f?~s&sg6?xluB*knm_IjD)Ygr z#piOj?YoY3h(oAqgpAGxPbGNhlDmU-klfLTV|@$kmV-5P%QLG>4XdoQiTFyS?OQ&Q zFcnMDYM}@e@wBt$LLgP&%?Flp*vI5;TA1QdShSn;XICDuPAR8mSXw12lj$*IiSfIj zH3{iXNI2eraj#U`Pf(O?BwdtP)3=Lrxn6kt@Bahr;!zkj#)ohK000VfMObu1WpH$8 Y0000004F2i3;+NC07*qoM6N<$f)aKh3IG5A literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/311cabda3a9b09f0dde217303ca9d1cd9201dcf6.png b/documentation/user_manual/_images/math/311cabda3a9b09f0dde217303ca9d1cd9201dcf6.png new file mode 100644 index 0000000000000000000000000000000000000000..cac27014b2d9c2611e2adc5a20501e04418d82f3 GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRO!VDyxin=BMDct~{5ZC|z{{xvsf&*5Y7XX#= zl?3?(Gf1rOo@?|u;pP7GC3k;|)cUpoWjG5wB8!0vj({-ZRBb+KprE&>i(?4K_2h(v z5VOLB1P~`JAuY>mAFQfxTF@8WB@q~EDClA*W-MGQMbh0-!Dr-eLW`vr8o;bB8wRq_>O=u<5X=vX`tW*PZ!4!iOb0e z2?D<~GK3B^Fh&ST+^A3B`s|4rge#r($v^6&VWLoyYl&x(qw?XUvH}_&0 z(l&G(x*T-9x&Kgv&ufXi1Fe$$2O_#9Bp&3jEn^YT+&Qz+wA}gQTh=?fcblD`ci_N= ze~c%*8Fx!cq!}0FCloyGejNY3q4DEB`2dSH#kq|K`7}>(ZUHx3vIVCg!0APoL!2kdN literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/35bc7b3a267c87204236fefbe603eb79e49f6f3d.png b/documentation/user_manual/_images/math/35bc7b3a267c87204236fefbe603eb79e49f6f3d.png new file mode 100644 index 0000000000000000000000000000000000000000..0ee71dde15ec74d64c0ddfa45b43c01f5bfd8447 GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^?m#TW!VDyT-@TUsqznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsX1II4#AB|JM0di=-|P2(-fQ%`7AVD8;1OBOz`%C|gc+x5^GO2*KYF@2hG?8m z7BEUsV3;K$Au{3T8NrDHZJDoG49*H_pFGg8EJKNTI&;S}k%R~)v5fQ+<__U1XAUqv zGnO(iX=I&eU|{PI{-w3C7^tK?VFs_*je`e1Gx`eNHfNjmUcs%7skxrfh3Bx!w0s2? zmVcZIENoxdTr`AQf1C>x;BYy3jqxta#&eB{c1NtPG4v?>IC+4l@COT9c1D9?g2tqT zgjNmdKI;Vst0I5HRrvLx| literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/38a212babe1948e16074ec200305e87a5a0fcab7.png b/documentation/user_manual/_images/math/38a212babe1948e16074ec200305e87a5a0fcab7.png new file mode 100644 index 0000000000000000000000000000000000000000..06b7b8caf533da92324e0fda7655c1a2b441fb48 GIT binary patch literal 520 zcmeAS@N?(olHy`uVBq!ia0vp^{y;3m!VDx|aLtF#3ABIEHAP z@4aN`eb_;S{X>1`Y%d>=f-8z#N{d2c=IH)ne52H%dA4!gqWcH@)?GV#^r)+qg-Wwz zPOIBNN9!dI7Ij;dPQK(iA?U+hRj%0i1DNhO(JFPwUh|5d7UqW{=pJjXHkG@++%NR^ICMQYmc*tIN;QJP} zy+7r6{FAv+oqpNk|LDi>&QFf7*FFZ(&wc`--?{ zSHE%k?^*2j!RFYT2X=p2ox@jgp6p~c)I6qh?Li!K{F9G4`z|)`kOGD_SBYyxiA!oh XNd_nmSrq(KML`TtS3j3^P6`{nu1ca3V(x6T7faTa()7BevL9RXp+soH$f3=B+XJzX3_ zJdWR;?wcX(DA9I5r>^X>%I&3Iwzrnpep#?%lG3A0!>>vFYgId@C;ZV>$`V;x70~3d zfOTWLQmdR))u$O+feYn|1azB(1g->2e|fQxi}fS>qc`VrX8ERV`k1s>o=5uLp5JFD z>)(HKhUx3YsYmT4<{AF+pZG>WZ1K03ZDoFSbr)uwd-G=YDHWklZ#0*G_F)f{Uz&T- zehN>9oM$zrFiauOR`wtym z?3b%mJA7b57dRFlU-~AcCrr!O|v{v@e z5(kUi*s=qwO_QGcmV8?L$u!HxKt;)>^w+k@7jwlY_-<$lIH`D}uR!d4#8bJ$M-~07 zoK|)RaQ9wuPHFKtSbB0s(v|kjUU4f;0@fF)&1n77dRoL}Lp#^?^M{Tq`e;4Z_-d5h z@oR^-Tf)&l-G2S=>i=dY@9N;VV6AgLTxOr-ITnGdvsl(Hxi1iKUFOk}lMGAa>MNIK zTC$zJll@t8d+O=scI!B2{MnMdS0Ko^-D2gr6<1qpgnhSvJ$iy|{?|W7o$bpOADR1L zZHMNf=;_f(Sc+NoogIgiq8e zToGDy-&Z`#e!KHF#{*9m8doQ63~$zzeYC`OV{CU??^UA@ZI`dxU2+ec&iC-!)*OfD z#<8lhzK>;NfUXo=eNC-&rn9W(;mKvuy>Ct~7rV(a-!uKGn*MUxrndoVYSk+pR>T4o zw%ih#=Wx+?Vci7v`6hDRyYl`XKJepKQkdfDYx{p6S9muu{e{!kr~-~{$`Nb-ZDDz` zFFxn3fLd$O%etc?d;T&Tu%_k}zsdc4VA7_3hHVRSdpGP{yj?hGTl0$4#dj|&zW$T_ zt@qpQ!}mpB`LC+~5VjyUclnLyPJh_W@8Az!#{N(QnA*8YTq8+zFI#y5-7!4;1OBOz`%C|gc+x5^GO2*uY0;UhG?8m z7BEUsV3;K$AtE6m!>nNQ`W@#BE{ktoMg|QcOTRlmv0#;$wn#fG@q{^pcG81(jUsg@3{$|+4%Rg0Hrt!JR*x382FBWFymBhK4}I9#(kbHjv*GO zr%tx?U=9>$tKXb6>FV|!(jOg|;v%FSb9!4_V_n{`Fe^P`T(9&y4$8 zCueOiICDsJV#S@UvXded_3SQ+cSu!Cxz|uVhcQ~Bt%$uPq{!g4`egPidM_I%&5;aw z(jhA?d2_9axhrs|1YHR`zmoCfqrDdw%-Rzm zwzEBCl5r&Wv^!dJL}e#`|NL}e_0*YNNBQR&NS#Vcj@vLLR`&c)mpkgN{UNJagL48; zevIy~y19Z_(V2!^_*3$D6q9zxT3w?UxmY_gY-#1@PWFmooFj7Q>IpGgq&xNf%t$xMxkqN4Kw!zr0x)P%rlWRQm2_(f4%>_y0Vs o_}|AnO9U9bTqUj%B`&E2B^f{t1B-&6swjxz>FVdQ&MBb@05byvt^fc4 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/4fe72f28a9fe0a456173c9287d7a195c663e171b.png b/documentation/user_manual/_images/math/4fe72f28a9fe0a456173c9287d7a195c663e171b.png new file mode 100644 index 0000000000000000000000000000000000000000..beca25bfb561fbee9ea8db927dba69959d974f4b GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7T3?v)swEqJsg8-ip*Z==POyYt5-38S^)%+zv ze!&cLjXduDPB_26`*X=liS>7;efbNN;wb|fr3q*E{-7_*OL<% zm=xJ03^*H%Z*o2G>EkR=o6a=lpvF#7wPz;-_)-hy5>})=oNUc>+Z literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/599e91f1fa979b0c78fcc1782e59bffd2a5cbd6e.png b/documentation/user_manual/_images/math/599e91f1fa979b0c78fcc1782e59bffd2a5cbd6e.png new file mode 100644 index 0000000000000000000000000000000000000000..80f8ca05cd761084005c9eb77df878ac816f194f GIT binary patch literal 477 zcmeAS@N?(olHy`uVBq!ia0vp^89*$=!VDx`yW32GltF+`i0l9V|A9>6f&SeE)j-w! zB|(0{3=-=Os`MT%&VTDN&vo;-kBqN;0y2kl> z$()0%FKTpVN$fW=@L^*Uau%N;%p!F65fh7_16wsyvo$mGW`<1?F2b+fH>k0(i7L4D z>aAmHn;PJoVOA(@VBpbc@lwLkpq}YI)0?`adklib{qMOOX}J_{M5CvZQNXaBXM;bCb^01LBv)|=uLZojw;VqAS)t?qwW zu!dP+*2;ziBPXVSvu7U72$HdwEMb0S*=(0IUZUQ?hJkqwlfXw#1IBWVy^?Ad7?xTtFxk#Hu1=Yaz`2R;YgUp?!as-xR>5gU4L1ObqRw|0s}+KBh?Vs2R*Go-*A<< dMwGau7L;TFISecceyXA%hNr8a%Q~loCIF0nb9ev% literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/67162eb98dd00c45b4508034c18a16d1a1c1b005.png b/documentation/user_manual/_images/math/67162eb98dd00c45b4508034c18a16d1a1c1b005.png new file mode 100644 index 0000000000000000000000000000000000000000..7c4386e58e1e9e938e5eb3bab3aa4a6590680fae GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0vp^hCnRD!VDxCy0o_gNrM2N5ZC|z|ARz`2KsjwR09?B zmjw9*Gpzr-|E|aR1fy<=xxZhQg!+0;1WIuhctjR6Fz_7#VaBQ2e9}O{SDr48ArhC9 z6A}b|X=Df;XyDzTVzA-=0jN*9=DXjC*~-ZVa8w3^tR^oNzoieZU-M{IH-vAW=000SaNLh0L01m_e01m_fl`9S#000HWNkl;+Q%0OD5kln6E{ju0Agg7IIyPCRk?dV%aS?>9 zj!eLqa7HB>B%RTJtuh;gv{@rN1K1uKH z*Zci?pFV$n&+{Cx43P{vJb{W4H#>M%mf>E<^~YREBP)sH`^#ajCVrY+NuK6V@>=jaFVAq`gGsdtN?05n)p68u|1j{o$4FmCZYPa{ZcWv9o`PYmaJ#&URk?aTx<{8?Ilo+>kSd??k~P3*W! z9+C<20A`sM$8F`3dGHR)A}#e-@Ic@5ra1Z_-zEUzk=PWW(coi(>O-)eeg*weAhM;x=`@O+X-HgP$|6EUBI zuYs1nJGXQm`7_wDo{fiGXHzTL3vWTR5&|8xKC0K};Vo*DvBrVNGEa*Pr~?92Y!`-kg! z3AL_%aO;L(5c23VkcWc*Y6E)e?_SB4_fmMaPjwbr_)o;PX}152!Oj=CB$dalW!UDq zTzB&5FS-idx6UPad{k}=`3epp)n3((G`Of-HDWxwuKjTBsxncnQH#-DNWH<2_d(is z(#ThD*x(MSk6)zay%{+4BZvKk?xV4KOow#wDhm4tPWdi#-lJ4^$I0WoILWg{?WF|hUo>e`((hj; z9_FZ~QX2hGz9WE*z!+2tNnK#&CC@XzJVvsOyIF5qt2$-4wbHZ>XDKDaVEa4cfu||X zlazs!vP$}q535Sl%6x%%Auci1R0k4yCKtDoaBmzm66Z{(L9MMFB~sK?qf^K5uF-xf&y zRN8lYmW4BNBX7MbOyJL)liGP7wpb1?i|2biFHe<@-8m<1=5=&ggz@-8Sev2Pdjpll(>7gMJf4Nh z;=znjSv)((D@;Lel;<)313$;8ohYDbq5uE@3UozSbVOxvbZ7to0000dBjF4H0000< KMNUMnLSTY*DIzQY literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/681f4a3906d0bcfa952c51fd13df36999c67807a.png b/documentation/user_manual/_images/math/681f4a3906d0bcfa952c51fd13df36999c67807a.png new file mode 100644 index 0000000000000000000000000000000000000000..3dc223db17dbdbdd41dc24ec7c13344596bb9c0b GIT binary patch literal 708 zcmeAS@N?(olHy`uVBq!ia0vp^vw&ELg&9Z&8r_-*qznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsX6Rloaks?d<$j}t&vVcJ7VFqz0hHn_@Q5sCVBk9f!i-b3`J@>bm^eLM978Nl z&z<7yAsQ%h{Qth&XJ${FIZN}BSbidxZc~O?*6K^G(&1SWnN7?Sw|dm-Mn~!$e(=bP z>(jOkWlcMZp9-7wh}*F^%FoE3=qT_->32omt2>!q_wRgfoOf38+|K{^cYZ&!Q`};i zp}N${gCEa+5c#*bt7)bF<6i5NMklvtt=L`k_jJexnYAu9aZ}rlUeY`pm^>}Gb=kwR zmsdE~9s6STOEfRIau)9$-8pBORu=#Hy1Xh+<(=N4a z_`EK?`-a>33+(Dnlg(e()>XW}>L<25w!Y&0!7uOUn=Ilr3Fmuv_r=TOi7T&)p2#u0 z$*_6nU9ml%o?h5~VH1PEgsqMf+sseDkoI~Nd{1=o`Sm`RU(7tAm-*>)lUA+dG@0YR z*S*$GTA#%HZqknZLJ1<7Q^jAdSiiL0e)q;j3~x8tTLM!GSBYyxiA!ohNd_qWu_*Yd Pih>xPu6{1-oD!M<8ayxI literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/7aaf5e311e32ebc237f7beacbfddb6441b567293.png b/documentation/user_manual/_images/math/7aaf5e311e32ebc237f7beacbfddb6441b567293.png new file mode 100644 index 0000000000000000000000000000000000000000..c1f108825f4db15afef1d286497f5b75b78abd4e GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^20+Zs!VDx&PZYid5(w}KasB`QKafd0(7(H&8mOAT zB*-tAp`?3#!g-_L9xo;K&;5M2S#%`}P>Qp_BeIx*f$s@n8C!_2Kt_GDI`wT5Uuqa3$J+QxFapS?cFPYt&4(W=>{^72Wkl3Z#;wo`N zZGneDUQol$Wd|B$Kj}QzB}RN+~2u(?}AL_EbxddW?X?_wfUrhf>%9V978nDCkq%Q zC@{xo}z_o{s$2XzP zfVrJX&vg$YPw|1C)jV_93p7vpCUlfCT*|pF5wZS3kjQKXCmx13Tk^KZt(rW5Xjtk}%_K#pNk1fRj3 zoe$1MahXgOmyzUon#0TU+hN`5ga?dmTK@0uGRd))`7O9rDv&GugR3I4VI3ELazcBH zF7s_xwq?vbHe3SdSe`MpIV()}Z`Dp%nVOKou#&N9^FQ7iR-T;-%lK|^ZCGZ@bM`<( zSz%$}pa1`rnQt?(edj(cDZwJK!|Xuqj@gY1pYce@q(8WEU_$MIS&fGq8l~kdIjv4C z{Lk>jne81fPfda1W`-12Z%5C5-WSU>VmO&!oMdmdKI;Vst07b8{+W-In literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/84039f9efc024403b48c35fbad53021426a96dd4.png b/documentation/user_manual/_images/math/84039f9efc024403b48c35fbad53021426a96dd4.png new file mode 100644 index 0000000000000000000000000000000000000000..f3588d80a0c34f590a2869c54797f7a394936c11 GIT binary patch literal 564 zcmeAS@N?(olHy`uVBq!ia0vp^nLsSW!VDx=dA{WaQU(D&A+G=b{|7RO2l{sxR0CD> zmjw9*Gf1ree7@vmcY@LH{debjEOe=U0F>e^@Q5sCVBk9f!i-b3`J@>b7%M$p978nD zCnqp4A7f&RVc5>Yyq&>MGjq+No>TEm5_u2ICWxNhbmJDAz|m(14oql>7M~?^oUzSD z;B0(Dq8!(bzC({W6Eqkv{uP#}P+hh%qyZPvE-F@!jS6 zfofYGrIQQ(I_+#|WIZv%zVV^0#*0k{g=B=&Sl2jTFP(FcHN&do>45_;)}%0|?3vHt z#M2WmDWTS!!yYNa614FsE61D$o^LE{u^LDTv!8ykO@lkTx*zXuPr{x#jR5&Km9o|G2nq%^Dk}MWlFn0v)<4nO&Mb zsyj2UXYS&@Z~Q?nqv1tS!h;QScZ!RAV&w^UU}IX#)>b3_n|&WAYugGv5rJThh87zG zr`9zh3LEEeraYHtuiD-4urwxsg;_nyDxmj^Xu^)JX^Wbq5;8wBw4`Y%%vdy`QLZIP z?D&*N2j N!_(EzWt~$(696rbzGwgd literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/841fb80920b214cd9f8ccb4af360b4891402c0e3.png b/documentation/user_manual/_images/math/841fb80920b214cd9f8ccb4af360b4891402c0e3.png new file mode 100644 index 0000000000000000000000000000000000000000..9b7a5d6f6372b3b3413c933691c22e448f7a8aaa GIT binary patch literal 264 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CP!VDyp&Kp+&Dct~{5ZC|z{{xvsf&*5Y7XX#= zl?3?(Gf1ree7@vmcY@LH{deciYI=7bD8pIc5m^jWa0G-Ir)u*_0|f&;T^vI=t|uoX zgwzQgU|`ANQ<&7ap26p9Xkjo&Ja__(a?kkr1 z4GRxUaMXBj!JN(%F^4mWZ?i>9xiCX)1^4~eG1^8z!?{XaBT8IS3raG890ulnS{t^3 N7@n?vF6*2UngF2;Mr8m1 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/8772e879f0b05d2802c5deeb8a7dbd466fd5db8e.png b/documentation/user_manual/_images/math/8772e879f0b05d2802c5deeb8a7dbd466fd5db8e.png new file mode 100644 index 0000000000000000000000000000000000000000..e668d4b37b16e055b7c0fb6ed8a67b4e97df2171 GIT binary patch literal 629 zcmeAS@N?(olHy`uVBq!ia0vp^4L~f!!VDx6_nqDVqznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsW{`NffBoIhMhPAzbGv_^KmK)g7*L9{z$3Dlfr0M`2s2LA=96Y%VBF>D;uvCa zdg+wC-iI9|j?MpZ(IhitbBDKLle7B9T^gkY0p=6)tTzA<;z!b`vSj#r<*`}f_>-R6u{9QDe97ovSkgcLb4 z7IQD%xpv*P>YlV&Oxqr=OWN?5$u;-=_pM9Sc@$pHWw{-cq6TXP zu>}Xzx%kQ*<~rsx{t#Rc+|b{_`s&D%1`XzdyDx>NXKXcLFx?>(v%vjAM~q4B6RFud zQr#a|)gFrtty#=l?^gLqnI-k36f&SeE)j-w! zB|(0{4Bc}j&L?;nm3+Rte*f>6e`+@*0Hrt!JR*x382FBWFymBhK4}I9Mk7xb#}JM4 zwUg|94kZX2t zZE;DM^XEd~E!!stUM?uDbecV(*h^)~)uTS8k?NP5N@VYOe0iCuJ?m|GPB=@-ZyB$h z_6OCsC7<~aleb1vT;kEevX;DEjqKT7>R)B~_e}~m^S!_1`DImO0gv}eH;XLazS6bp z$v-QL4&J-T|@thWXxkKgg?cc1(F v|8PfleDGHry;~`>{eeNsRpJ^^;*wfWk^$r}uqgPcih>xPu6{1-oD!MFza}_IEHu} z-x})c5fUi!?_SU6IWx`kMYwplCM~(&+uwA_^{j&mi{m7Tbx8|!oIP&4Tv_DaBGGbo zRqyOZjZUsOU8hZFR=7^#R+LbjaEyaX*FYpX7xGy z;vcfl_nETh!=#J*^egNhn8h^Cc3E2yz3xf=GbQ!bD?;{P-*7H9br4&1z~3Xfu|E9s zg5^;^Q`8QqnXV`_J+O6G_V171|G0fhzT^C*JhJS_LRo7mh3@U^-Y)m8y)*I852beZ z;-@#Rn$%o8@Zrk`#|7cJ<$GRMB}gj=@hpyDmR@i9NB`XZ6VKmrYyY|#$ioQR9ftDu z2|m%RH?|yLH;ZwVk!e?FIKQCwhUCHeX+qY2Rj!Bsnslmv>xtKgA2Q6JI$u%i&h?HI zUj?7KbEj_<_|(m4TO_ZY`+4fw+@ha6o#~%^xeiEn9c}sI!(iQzRCnQ_xbpT7d+u9x z2oEUKa8^ zJ$}Mluz8Zzqi*SmOXqxg@NDfnrp8?_V(w^OE3J{+y{PQj$rZnz+b;5D+J5lG-p<>r z*5z*NJzP@9W%ScnMNVb^%w@52vfnuDxYtw3^3GPa_Q<)pZ=5^ic0NonH}X>9`|{oM zPXCVwe9S?Z)TU3XW4i3KXQJZI+0*V8KfYzVeAn8G8hSI2 zL_F}5(7sn(De^G&=U-dyU!nb9XlAVJ zbkoC=Kdp`W6aISgr``XbE~;ld;(WS$RTsx}U}oYfag8W(Ni8VJ0A*1Q%^#U|Acm)_ KpUXO@geCyAGr(y8 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/91c8f1f7113f6cef68a960258d62da05fa5a431e.png b/documentation/user_manual/_images/math/91c8f1f7113f6cef68a960258d62da05fa5a431e.png new file mode 100644 index 0000000000000000000000000000000000000000..cc99d78acd93b76fd679fa6e06c20a9c2fa2a5cd GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^ML;aX!VDx!*{6#DDT4r?5ZC|z{{xxC1O2-Ts)4Hc zOM?7@86;lrUw`+rQG!Rw-0t7!kAIyV29)9~@Q5sCVBk9f!i-b3`J@>b80UGqIEGl9 zo;!KJ_hAQ#w)+vcOfqve?bOh;C=oj9y0W?1ibF|OmZh-RSivGx>a~hSN@=Gk`$7@H zm^(aHD_M8{)Q`|G5%jthaAddlR(lQIXUm>Etp2oj=Wc$E4Hn)`8$^S4SUSv&TfP3x z|MN8rT?ZH!`L?u+d3niuFeD#fHFeFMA^G0emAfO-V%f3;9^?C>1yXAN?j2szKS7QE zh|8KS3TM*t*2mdT%$^d&CKxpRE7o5it}ae1@7Et;}8b>}qa zD<&CEI2-Q7soAUZ@}<+&3jMr*b3V7tE5!w4ukB#OL$&+6e2(kCG0c45r@4b?MT+#~HOn8)-WG3h_3L~UmMwiJ zFF7eM%lTmTGFQ;p+xJ*?S;l0U@9SSGPD*cB63TmVW6EQ`>WU?qlK*W*3;$`eWFtU2OIEHAP zPflQ9KE}ir!=QLrw;_0<$qj+0`iwm01*;MhSf!6XJ8)n^L$shv-+qQwQtUhUlzyoj zeDFE2_yuprg`|WBOhFH&9?pC5Y0ve;3X^zlCd^7cw5Fl-MOUBk4u-kRpYDdUwVibE zHz;AWbW~WWtLN~5C1r!U!46{s1Lo%TugVdE%*|W^i`m4(84r7PDCIG;v1v{Suxgw9 z-2Xu|L!t0zHjcADCNFHf&Ukd!cb+}$QU@k|W|nYpym0hiW4=?P;;K@nBretk3IeC> z`AQNQ55L~AfP>BN%7o_uyRdOWk&z)n-O%XOo;Rz2p~O|<8d2hsT2KOt2?iDgKUGl>!_(EzWt~$(696>%lH>pY literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/98a96d47fc75f521c80193b9c030c717f8be9c67.png b/documentation/user_manual/_images/math/98a96d47fc75f521c80193b9c030c717f8be9c67.png new file mode 100644 index 0000000000000000000000000000000000000000..ab96b6b4aea2a227aae3774436665bcb5c3b14a3 GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^T0kts!VDzu9|>FmqznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsW?27u|6Pys2}a!#bAP`q3H9}y2$bS1@Q5sCVBk9f!i-b3`J{n@n><|{LnJOI zCp0upHDFU`7TVBcxP!-G$pv@E$qa1on0EZ}7C1QRz=40wjp7Tr&perb;J{buMGQee#HhuhI4CNHXULxRWWp`Wn^YpaZ6qA^p5^8pzFCxTq8R>BeEE%;0OpaPSxg<1`3|=ba4#PIG-$F zl%T*cOGH9s!cUpNNdj)}0nAPezJb=xayJt0v3(QgU^H=!mY8AIkfF!e<_zMgOH{}^ zLb%7m{zD{Gx%)fb7Huw z61>7$M&h070oDs5g=Pi~y-aLRR+}2uo=(q%~)W5`=*X;pyt=BLgYEzT literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/9a741b899afcd7fe00da0afec6c45b63a0028329.png b/documentation/user_manual/_images/math/9a741b899afcd7fe00da0afec6c45b63a0028329.png new file mode 100644 index 0000000000000000000000000000000000000000..f60ce00d59a4a4cdfb722b0932328d20735c8a52 GIT binary patch literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^Qa~)k!VDz0n_r#)qznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsW+>@4+Q0s#$M4S)bI&K-t=q_>36$b2@Q5sCVBk9f!i-b3`J{n@(>+}rLpZJ{ zCp0kWv8u7LBt#x~+VJ3QYJz~%2O$ZG8xLgMSlQU}SS9+;H#9c>Wef-jlsNI2fz#B$ zV29C?LlyE8GlV3#BwQqQ9QQ=>$SADkPOzzF-k_5<7;%)~5wqKt8aS{N(aI`)F;g?dQ|9O(%=SRQW7WBb*?c+=hCzFqxPu6{1-oD!MAqznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsW?27u|6Pys2}a!#bAP`q3H9}y2$bS1@Q5sCVBk9f!i-b3`J@>b7>{|nIEGl9 zZVj>ZW_A>)y(sftaIee38xjFW>{>aNaYSSas4VDcO*-;OCpe;OS!b)6dW1x-Qj|v1 zvdIEGKRGk6T;gcD!Lcam!m*os@BR?I_O#FZd)oawXU^SWERuEJ8?)5>TmP>GmUn+% zF`VI|u=Yw?;{U3hu{P-s{2kXXj{WNLMvcBXbsQiNz2r;F=`b+*w-Gv1la{K>)pnQitIUCnc$ z34t2NjBVcP&p3MM*|)6vgW21sPRlBe@pEXHl75df@VH*0+V#(JRx^$TO`llC;Ks|6 za@C0fn-+lJ^{r+wN~N*BnazJH(azmy|u ud*?IZefMe#H5aw*21YYiiEBiOOKL$$1}O2cDEO(0f*78zelF{r5}E)jng)je literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/a83153ca777e70a3b7f675b3a8f4fa620aab16c6.png b/documentation/user_manual/_images/math/a83153ca777e70a3b7f675b3a8f4fa620aab16c6.png new file mode 100644 index 0000000000000000000000000000000000000000..b1ad318c6d11541ea9c274db8bac01962d2b7abb GIT binary patch literal 323 zcmeAS@N?(olHy`uVBq!ia0vp^;y^6K!VDyxPDUsIDct~{5ZC|z{{xvsf&*5Y7XX#= zl?3?(GkEM@-+lgfg3;Z%5}!*-TKIPWWjG5wB8!0vj({-ZRBb+KpkR-ui(?4K_2h&G zCPij8i-bz{ry9(Ls~JMRND54MzA5vWOF|;S>c(ji2?>z}GV6Rfd1Uwk_=F!9vrS{u zXrHOb*|E@J&OyF|h6y*GUtoHyVNf9VpdWM&|XG*Rt|NZ)0LzCTZ{{wRHc1 z4evxJTxKqjVK!80!8=y|Xy`K^f>St`gUX5|`A1 Yk_;e+fq9?ShHW5*r>mdKI;Vst0AR^rg8%>k literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/a91c19d517b8ce8720b4bdc36551378c4ceb8d54.png b/documentation/user_manual/_images/math/a91c19d517b8ce8720b4bdc36551378c4ceb8d54.png new file mode 100644 index 0000000000000000000000000000000000000000..9c7c3b7fa226bce8733cb291d7397d6478412a10 GIT binary patch literal 1041 zcmeAS@N?(olHy`uVBq!ia0vp^uYp*Ng&9a*4=U^dQU(D&A+G=b{|7RO2l{sxR0CD> zmjw9*Gf1rOo@?|u;pP7GC3k;&tUFa(4wT|7@Q5sCVBk9f!i-b3`J@>bn8Q3>977@w zzn$irA?zq}{C?TwyGw2_$>wfK@H9CZ<{G?F@7S=p=to8UY3NGW z_Ta2HUsXU$+m;myQyf0#EOg&^t+^>DyjA2?_MvW^OEVgN)neo-uY-9dByshh1>MW1*;D+$V^`0Vzk$MUY$-won-KgcZIiP z9&L4Qo@(BEz{_f_huU+8_Oo+JYz02D{N4M2e}2kM6>d3Ut@a7W-gu}z)Y#cr!zJ%{ z{|j?NrR9RxPVz5}`C=@SoV}*`t~aW@eqKswQ+u`1WedZS8{AT6#;R9decSm;>3~;4 zQn5o@Y}bA-UB~4*+}vw)>nEq?8|oi0F$y#4mp!wSZ|*aLSIvQUf{$#jZ_)L3y12qZ zYr?9&>iINjr0Ppe3jeH7SO7q&vD zD}7UjpKy+!F>6^(TeNs8@0Oow4Lf_f3k0lWT8omDHwafX=ty6iyS7h7^v?7RKlrLv zn`%YPYxS*EZSg!Q_>Of(vB_~mcHPHj4>fr;E(#=0P8K!ONq+5kGri)}l{}C1)s_`= z&Zunb;!2*tCRw_*adK@)tmp05llI#Bb9v0S@3I#PVtnb^vAHRCy$5T^il$JW#AVV2 z?nl{ME%zFKUJ&&maoabMwd=G_XZ`TtbNY4f_}vrMx~H-gh?3b8K&CP4}Cs_HWv!RqA%3S&MS) zZ@N6KS^H;e=c=~XH=nDWI<~EP8}Gl{@fmL0R5zWn-+HoHH!#$6v+Az&=*=g0UOOSS zcG`(=x1_uOvnuYtU(}i*xC)r CZOu#o literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/abdb303f8421b8383c8ac4ac559259c60ac85205.png b/documentation/user_manual/_images/math/abdb303f8421b8383c8ac4ac559259c60ac85205.png new file mode 100644 index 0000000000000000000000000000000000000000..5764dcc4d8ad51c3dd93bf73b0c20ee194e9100a GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^wm{6s!VDzy7IEGLQU(D&A+G=b{|7RO2l{sxR0CD> zmjw9*Gjy+)xLe}!a=+2sg!7+&iweEn50v68@Q5r1T6F}38K-LVNdpCUdAc};NL)@% zP>|bD^=Rs|q9a;oG*<8|>)jLh)8N6W30#ZPWSpNKW(a09_V_!;PsQ#t3x~AJXLBwo zhM8stl>+linXeplsIm*uJyRoaUq*|6(OX`L83$#w!wpy;9WOKV@GR8(!gh)04JS`h zKzs9F_D7#{608pxYbKiU2iR{`x=|74e89^COo=n;Mv%C`O3uf z!z^n)$xO^-%`xWDu}~7;aB8!up$kWmZVcxV#^%GC%-<$9b_0FERpJ^^;*wfWk^$r} UFz?gaunolUboFyt=akR{01P2^1poj5 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/aefef4246fe548d6fcb1f8734af167930e5e5d4b.png b/documentation/user_manual/_images/math/aefef4246fe548d6fcb1f8734af167930e5e5d4b.png new file mode 100644 index 0000000000000000000000000000000000000000..5764dcc4d8ad51c3dd93bf73b0c20ee194e9100a GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^wm{6s!VDzy7IEGLQU(D&A+G=b{|7RO2l{sxR0CD> zmjw9*Gjy+)xLe}!a=+2sg!7+&iweEn50v68@Q5r1T6F}38K-LVNdpCUdAc};NL)@% zP>|bD^=Rs|q9a;oG*<8|>)jLh)8N6W30#ZPWSpNKW(a09_V_!;PsQ#t3x~AJXLBwo zhM8stl>+linXeplsIm*uJyRoaUq*|6(OX`L83$#w!wpy;9WOKV@GR8(!gh)04JS`h zKzs9F_D7#{608pxYbKiU2iR{`x=|74e89^COo=n;Mv%C`O3uf z!z^n)$xO^-%`xWDu}~7;aB8!up$kWmZVcxV#^%GC%-<$9b_0FERpJ^^;*wfWk^$r} UFz?gaunolUboFyt=akR{01P2^1poj5 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/af0b3b32d353682f471e1b2cc26eaa4edc29b19a.png b/documentation/user_manual/_images/math/af0b3b32d353682f471e1b2cc26eaa4edc29b19a.png new file mode 100644 index 0000000000000000000000000000000000000000..e608bf7f3914a732f319d6a85a21c3ebc87c158f GIT binary patch literal 429 zcmeAS@N?(olHy`uVBq!ia0vp^X+SK3*F~7erBG~UhpXG=13z?21 zw{|-e2#5OfwYjG`uE;-R@#pY?Vg~1awi!>%+|K3l01J8bX)_5 z8j}ZWI~!B;opxixLr#*xtd>Uq}v@_#l*1eo2}JMw`Yt%-*c6?MwGau Z7L;TFISecceyXA%hNr8a%Q~loCIE9=k#+z8 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/b0d068bd0fdbb1e76139d9d423ce5800e6812949.png b/documentation/user_manual/_images/math/b0d068bd0fdbb1e76139d9d423ce5800e6812949.png new file mode 100644 index 0000000000000000000000000000000000000000..aef3e34bf14d60a585a2e1525c518c00af085878 GIT binary patch literal 758 zcmeAS@N?(olHy`uVBq!ia0vp^Uw~MIg&9bCxbpe~DT4r?5ZC|z{{xxC1O2-Ts)4Hc zOM?7@8PF1`+}|%tLVZ0a0;M<$JR*x382FBWFymBhK4}I9CTmX@#}JR> z$q5IT#2$!8I0V?LJy3FDVUhX3Z6OfS&l$0yi=$S=gww&iD=8S?={lOnF}rbGW&QPE;5x!KU|d1-EeK4zQeYtw#LSvOl~P! z3}64cY@Aid%pA{@mzl}dmOf)&YiHAAsgB?33emN_jfwA>N>*3$Hk@<)YIKY={DglzwQcuW3vfsULtwQWAm){bDdpR zANFnvv!7$ZKYzXBrI;|4C|%8;4Z9T zmjw9*Gjy+)xLe}!a=%f+-1DD*U);4_6DY-5;1O92wCV^5Gfvg!lLiXz@pN$vk+__k zpuo38M`3}33qw20;sr)C_-^)`YG({@3*kuaRnwAZn!z!=D>@Y?+zodgB~sHQF{h z@}yjy#?T=rFh8B~Hsd@`&dn7Kzh;Ua{I~4YfY`f(>&eZfd1erag8W(Ni8VJ X0CE_Z_i1g|24Z-+`njxgN@xNAii30` literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/be3f6c55e39366ad7956d6cd4d84583e894b661a.png b/documentation/user_manual/_images/math/be3f6c55e39366ad7956d6cd4d84583e894b661a.png new file mode 100644 index 0000000000000000000000000000000000000000..949dd19d8cb195ca803c01c0637dcf61b26672f7 GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^LO{&N!VDxYd?A+G=b{|7RO1P81(F90g# zD+%%oW+>@4+Q0s#$M4S)bI;%1bxQjR>BeEE%;0OpaPSxg<1`1|+x;Tb#Tu)9& z2oXtZU7i nIT9V*1hkZ^#5JPCCAFX=1IS@uJLlkH31WD<`njxgN@xNA*GN*k literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/ccca2c46f98ac4992c543dcd5aab7c897d2c9f8a.png b/documentation/user_manual/_images/math/ccca2c46f98ac4992c543dcd5aab7c897d2c9f8a.png new file mode 100644 index 0000000000000000000000000000000000000000..98895ab9fc3e62daf32ea618a628e3cd2aa54e95 GIT binary patch literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W?!VDzuzEV64qznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsW=JSGFVX$mW3JKq&v*B~?7Yt)3Y6k3@Q5r1T6F}38K-LVNdpCoJY5_^IIbrr zgrp@HB``3DZ)1AK#u1!g#~jVrEXI78jjin{&u2*q83_qB1B0BTj>Lv0eV(u$88)6k zRtL58OGg@Xef+p3Bo;N5Fk8rP_@Lz=QpjrX!MDL+XRPFn1yMV#B(o$QEMPtt^+D*I w&C&x53{~?*BJK+O3kKTGRpJ^^;*wfWk^$r}u$^;ou>>(ZUHx3vIVCg!06y1F4*&oF literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/ce4588fd900d02afcbd260bc07f54cce49a7dc4a.png b/documentation/user_manual/_images/math/ce4588fd900d02afcbd260bc07f54cce49a7dc4a.png new file mode 100644 index 0000000000000000000000000000000000000000..c2335a5f907a43534b78bb71961170867a12a2d5 GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^U^WXgkd-9)$P7s72Ka=y{{R0U$RrXRu-d!;sFbfH z$S;^7;ib{}yAr><_j{Dg{k&c>L>VZO>_3{-Fggc+x5^GO2*9X(wfLpZJ{CnSWV zF))T3XijHPx+(3TlFrcaq>63Y3I@SDUJ6D1a~UVRJW$=xm?+GAo|WxbHqV)U$qI1; ug9;`F4hPN$diyggfQE6ExJHz?q!yH9067c{>dFs!K(wc;pUXO@geCxZ5jze5 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/d7bd61285bfa72545a81de040c02c01d2b6994a2.png b/documentation/user_manual/_images/math/d7bd61285bfa72545a81de040c02c01d2b6994a2.png new file mode 100644 index 0000000000000000000000000000000000000000..45456034379cea9430050ac6b529148314f64fc8 GIT binary patch literal 403 zcmeAS@N?(olHy`uVBq!ia0vp^?m#TW!VDyT-@TUsqznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsX1II4M8d;peRsmkx%+>AHv4Jg0+iw`@Q5sCVBk9f!i-b3`J{n@H#}V&Lp07O z3m7FRFw7E>5Rs6OVKz{)GBa(+?6aN3!=vE(@~`lPs~gNW{9!PTlK8}XpiAP1p~y!L zgBT{`c}51d2bOj;GG<-kW&6gzLv$mzgJgwIlHC)A)SuE1%sRhnO;J4Cu&5(cfW^!~ zwNz2$I~!Y!njx#f74?S5YdjXBjLh!xXCCAv++YYlvWzL5A!(EKhC686~!W^EYy!$B!1o(Sq{{H`8+rfwFx8%v+25dU7JcCVG7jhhXQsNNaGG!Id z1>cYa1+|Ne3|p%Wsw4WPGJ$^NDshb{aY-#G0R=7tYq+OqGl=2o>gTe~DWM4fqU3u( literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/da8115215e9ea837e8fd35830c27a3a6b53823c8.png b/documentation/user_manual/_images/math/da8115215e9ea837e8fd35830c27a3a6b53823c8.png new file mode 100644 index 0000000000000000000000000000000000000000..c6a708dfb78df9aa053df1b0bba51108ca34a6d5 GIT binary patch literal 319 zcmeAS@N?(olHy`uVBq!ia0vp^@<7bZ!VDx!k9)TRDT4r?5ZC|z{{xxC1O2-Ts)4Hc zOM?7@8A`g>C!9C>?eS7#|L3`Pt&}*~fl{0W9+AZi417mGm~pB$pEOXg#nZ(xgyVX0 zf`E}ASF!_>4(CfI(GyHPJnwjTVmJl(TFh9xtQ=Ageo0v zm#RC=IymK?_n~qa`-;*# zNwUHgo5a^V6U%PkD9A~h5cX(q39kn;!_CLi-=m$%8iDTNDshb{aY-#G$pCT~nD=RI Q*al*Fy85}Sb4q9e07kJ_82|tP literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/da8a2d6368b051ed1915a58813bec0cb785c0577.png b/documentation/user_manual/_images/math/da8a2d6368b051ed1915a58813bec0cb785c0577.png new file mode 100644 index 0000000000000000000000000000000000000000..e2851742194fbd51cd1d18f3e5f7b396fd5c9f25 GIT binary patch literal 404 zcmeAS@N?(olHy`uVBq!ia0vp^?m#TW!VDyT-@TUsqznRlLR|m<{|{sm5A^RYs0OO$ zFA4GsX1II4M8d;peRsmkxxe>+zFI#y5-7!4;1OBOz`%C|gc+x5^GO2*Z+f~ohG?8m z7BEUsV3;K$AtE6m!)&0kX197nX5ZdPJUj}nFaHW(xLUwvpu=h$CGm;(K$pZ1LlHwM z1CvHcf5{ng4}y-!A247)V{!1nKgQ{c{;;%FuyY7+_x#AIz{2*A%|&BUnnU0bEe z1J_s$&a_7y-`mKz{II&g4F~2P#^p-K*xGz2Fk~m(Uo1B`s>Ue?muoLW0Hr|NEJas0bvu-C#{Wv}m4*Yp_69f{!12iL1xT zQw(=qSlN<zopr00=pG AnE(I) literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/e0d1ad74677466d8ab0a46eb34e1e710d5be06d6.png b/documentation/user_manual/_images/math/e0d1ad74677466d8ab0a46eb34e1e710d5be06d6.png new file mode 100644 index 0000000000000000000000000000000000000000..1001ce18533d57785b98a045b31cc1de7c16c27f GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0vp^%0Mj1!VDxm#kv*(DT4r?5ZC|z{{xxC1O2-Ts)4Hc zOM?7@8Rm9-oG)3Q@Oi(4(cRxKZIzmQfl{0W9+AZi417mGm~pB$pEOW#xu=U`h{WaO z1O{fsL^hKHLKFJ*QW%dg@%V60XfcsAaNs=r$W?g8;R&rbT13kHA3SpmK9P1dp-t_` z%A*Q>nU5HG1l3tJn*ZBN{W#DdUXYuR@W9Qq-?pLAaV?{BhqTHO)s<5k)Q+6meL&K; z<6zrEOO1rEvvZ1zp0RaIP&%w%5*j>HNP)3gAS)!LfrZCo#V6$pg*?0)8WQyyb{=C? zPPn#5O~k-~fk8Lh_`llp<)E705CSngUHx3vIVCg!0N+k% A&;S4c literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/e2644f5aef88a86a47a52798705d58eb6ce1c6e0.png b/documentation/user_manual/_images/math/e2644f5aef88a86a47a52798705d58eb6ce1c6e0.png new file mode 100644 index 0000000000000000000000000000000000000000..493ef3a630679f84b3676bf186cac456cf346e21 GIT binary patch literal 1468 zcmV;t1w;CYP)tv0000mP)t-s|Ns90 z0000000000000000000000000000000000000000000000000000000FWsPf0000F zbW%=J02r_H&vfaFU^4r^-JM9GM0?->000SaNLh0L01m_e01m_fl`9S#000F6Nkl?%*!Q`6z(+LsL}m%h;vg0ayN zsh!}W$5FWKvK#~WWW+U}t*-nO_pfyd<$Djge71DEOTKugT=Jepm*qOZ%MsOOu(?DP zl&Lk(k@j%K^aob%MBU`_vh{++)kbU8%;suT*E>hj!~dotSWP}UcASuse|GB7-1P->!87dp zQoTBC2)SXEoXS;9z;`BJHxfhNkk?pgbL)lK!Xo!KG{O%M*Kn3Lgeny9)~n1S+=LlA z;yrnyiQJRZN)f(>5$3|yFFq|7<9%Yuc1j!aLZkUDoTg_?`+Rd;Nt^2}LlP{IEu1B7 zE~s+LBBUu)S*gf9bTp@+KrZ|Y1p^KWhFsxJbYIILSD^uml7!YRamD|AHd>*-++2=@ zaY3Zr_CPLS=o-23d2cRv4q8sQxGe0%xx-W}BU4-BQkyf)tGP@U7fbb+=5STvd80rs zD(9l?>u2j_a;^$Q?R9E0x5o7*#Jj7Y@Hon}M3`!_ZQ)#LCvM2K32CK7bZ||3%Rg426!-S zMlFq;4#G8xBkbl?wl(3}l=6xqOs&Z!Jp9iiwK|cxTwbD0Mo&Hu;?*`NFX_^mlk;Zu zX}!E;a?1x9EELL_-`jNN@m+ad@63OCj$CMd362io@!h}b=mhU~%^fN)X&~-7$@lK?zGEQf)F?oAHZ$R5<0cBVRe26Iq<1{}&Utc|i z<4Lw7mM3BxfI{#@~W+;eu+ye!K)tgV7-dwQrx%q zCdjob3&%a?C+gLz&+l`PD{1m3RU4ue1};a{l{+1&MS`} zxoD80wsrH)bIlp_aRw=B*|JqMY2-rokW_(5=*E;IMa^}y-&{;lGcHuoOe3f9kO_Dc zGz={&YFjt^5l0hFt;CTjSH^|nifLrh*1MzQkfj$7D8g;s>_gt`u6KAgm%>yb)5vM+ zv-dZQsc5oAxKDLLgCA=JSTp=8x<=Dw@&A(l1GwMW%o$RA1poj53UozSbVOxvbZ7to W0000dBjF4H0000V literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/e53c2367082d26bd66ac95de4aa8118daf060a6a.png b/documentation/user_manual/_images/math/e53c2367082d26bd66ac95de4aa8118daf060a6a.png new file mode 100644 index 0000000000000000000000000000000000000000..c2c7f7dfa5c06a936d3142753c49b95ff4efd17c GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^Za^%=!VDxu(JHhDp{=0KM7P?eF07`KdctjR6Fz_7#VaBQ2e9{aIjIy3Cjv*T7 zlM@=4jxiY=J=efq!0j+4?||CL6@OG2Jnl9W9S%&5T;HsEL&hP6DP#g~s4qYBZDxg| z{6(DEj3E=YGq|1&WGkB>_*b}r8 z^r4pJ(#-%<8R0b6HOUD-8rHPESLZM^s7p=YNSeV@BIGRYaYH~vKu=)-L-=7X6K{qk zZHWNtPT8M3F!R8HxC0-o^fZ_x+89?97KSn(O3+G3uxYT> zd(aR$r_quBRNnE0Z7%;c%K#1xPu6{1-oD!M<`1z6D literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/ed9c73a2e6e1b46d6d98a076aaf2cd9a0da3ab2a.png b/documentation/user_manual/_images/math/ed9c73a2e6e1b46d6d98a076aaf2cd9a0da3ab2a.png new file mode 100644 index 0000000000000000000000000000000000000000..e1ce916b1fb9ab66df253aa3215e01b54de44f3b GIT binary patch literal 589 zcmeAS@N?(olHy`uVBq!ia0vp^l|U@S!VDyPpXWRVQU(D&A+G=b{|7RO2l{sxR0CD> zmjw9*Gpzr-|E|aR1fy<=xxZhQg!+0;1WIuhctjR6Fz_7#VaBQ2e9{aIjFUWF978Nl zw@$J5Kja{BOrGUZ(3B=lpGg@V*3I2KXEtRB_Y__@ks|w>vFA}kvz}?8fK1DS1>Kv3 z7Clscu%Is~DJjOMN2*ir+dq+|ap%l4=b!$3Z+-szd)bWUev16jv(El;zJB!E3-6p$ z4mT!kQgzvLw4b+#Uu3}-1EuwIn5~-6T@zfp{Pu-?A5@lCJ6ukFIpv;?(vL<-O_?0# z&rVf5AIcvr3B0#D=>F9@jgy?xhn;@0?N}jXb%D3+rCY&YQO5MsC5nHqWkm)YZ&bA~D6_R}5TyyTK z+SSUIzIMqC=cY~DIOF1_o0GCNFZr9CefDA7lf@G=cb7(-agSZCXM5_+dx4*`AB4Fc tsA2A1UD*DKc~TWH%D75gBT8IS3raFT34ukyPgNAe@O1TaS?83{1OO6k@j?Iq literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/efd9ce9060e61f4b7c20694ba20f8430ab71cf51.png b/documentation/user_manual/_images/math/efd9ce9060e61f4b7c20694ba20f8430ab71cf51.png new file mode 100644 index 0000000000000000000000000000000000000000..7796093cbfd27c466c7a4fdf000ab77b8a94ae6e GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^MnEjY!3-qzd~3G?Da!z#5ZC|z{{xw10H&B~AD}j& zk|4iehLY~}3FnP|d%Tp`KlkqEpHE*`b*#?-%5oNXL>4nJ@ErkR#;MwT(m=seo-U3d z8t2yrz2rNrz#%5ERnEHRf4gi}`j*J+GkfH{bnY{Tto+~JeR{%^>MjY%^@6jiyn@~t zuj^=0X@AN(QEE4rLZ*Y6xYF5}RW5Fl+^2T&bIdr-+NjOSacY}*$3p4nLf?ye?%kO^ zGs-`*=YjY{p*uIOUgKIi+eCEf&RrY=BjLYhY&D+s%LM!;Tjo6V?4%4|Q$d>}QuR_4|z$^Pdlwla@RPcj@cCo*o}p qlFZWn`Ow-Pps%<}Tq8o)Re0;M<$JR*x382FBWFymBhK9F6 zG%)G08XP^+z@WpeV8q978SnfDw#kj{|0!JgriHWzcGj6!l! zE1$7}iGjfmqa{u)6}&nuw*Da!z#5ZC|z{{xw10H&B~AD}j& zk|4iehLY~}3FnP|d%Tp`|M~9RpHE*;?sqH#%5oNXL>4nJ@ErkR#;MwT(m=rzo-U3d z8t2yr-R3)_z>}-F{h-6s|Mp?8+QgJ4RlX%!PMGIy&G0YIuD;-#agWHoJ0WQtvsnDH z#TZ<=#WuVYUZB)_>V|fAfPnF)9@f_ach=h`OluEvjh(te=k2Yx_Z&=O%>QjknAm7N zX<}w?Sq|gwXDRMee0jxG`4<)}D0-xIHm_@sPieix#8X9IT-Hm5sQ&Ug)NuFM_In%8 zt@wG${foy5F-D~>zNP#A``TY$nB^YabIOx@PUX$at4iH-R;>TMQa<@v;$@$r3Z|>l pEXCUwipT@~#8u)NQR0$XP?7=UFtD9-aIpk2JYD@<);T3K0RVx>hTZ@G literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/f792693f4651f9955977b4130f938061c901d2e3.png b/documentation/user_manual/_images/math/f792693f4651f9955977b4130f938061c901d2e3.png new file mode 100644 index 0000000000000000000000000000000000000000..c1ec42132d33dcbf18826a1c40cfead85cebd7c5 GIT binary patch literal 1540 zcmV+f2K)JmP)A%l(-TO$c%6)eL000SaNLh0L01m_e01m_fl`9S#000F^Nklrf({5ph1y;4M+A+iMJUC! z+MoKdQN#}`7_5SbI;r}@7;Iry9>}xmam>gS#}(!0)ZvvP}Et1x&ooejx~1GC!Cq!sHjt8gNNq- z3Xki6ljoz2|8xQS_76VLZf{(BQkunP#D9BPQ2aO$fXQaZrD&A!ca!{(D^{1ZJo|o= zJuCT)S1!4u7_%s}2fSe8Z`zr1Z`_SPugNA{xiJV=$%|)yAHN>=8ONNmqW@ZN4@j~| zeeiGPB}EcLPk-nsbjE{BtmMVBcXv+0Z#}E-xb_1sp>)k>9#@l9pWPZDxW3IU zFUra4G`?q5Qdl?zNJFw0wAV)O$fxjwZ#n)xK;3Wu&Sfd^CO>o2y$>L`uCU$E??{^t zO(JzqF}ClYu<0wPEEIC;4mIvN(ZUNVyUmdzxyJ21l_h`+ey$0pD8^sHoX1rG2(Br< zY_or=ToWZvd0zi9)qp|w@v)2WhT`&dIM~7q+P@5g=5F#aZ~z}q1aSG#QdAk+RG62aK#T zd<{gw;5=XCOG9Cq8CU)Z^~I9wiB^7Z^-2O%wtt(0&uST zOm|k2WhH0nS0Kuery*Y70Y6DOYsF49tZ8E?hLgh+6Q==hc%WcAAc< z5ih^S$4>3$-Uvpw-r^vG9?p>Z1UMpb233&VGixM~7wPa8r#viX1FR$OV$=iWh2+kv`6E zKI^BNKI6MFZPAch7r^Aa;m^W+7&9RLnzdf&b;dE^Y8>YOBuuVbu=61088-F=(=s83Kp8s^ z>^=H>gO$SLYE+s>UBNyLk1ILA?ZT=)4tl94*dyU_O`1_`H%afI)p~*rhxor@lXM<{ zy+>DEJ=k!V^NO7Z_8wiufCmdP1|mh$c|3zHZ#>@}3|Lo7}w zCmdijQV3yIv~){db8ETd3ZJH;bvHsja5!YVV{5V4}tv|>$SQ<1n$_w%4C>);YkTc`{O#8zk z3s2=HJ=n=G`RB0>;xCx`!V~@*K9M$1+EQGbkYI3NO7fmIgBh=lo5k4k4{fVma3WFX zMS4V!Na1uz3mt=}72*a46;7cAKfXv9NEoV|NlSQ<@L<;7U#147^V%}Ezs@+U$C$eK>E`7#&u9i|(^WHVIu`^kW83fv97}(*+G*O+OYfOm@z3m!5yW6c|TbC9V-AE~y12 X8K9VFJLlkH31WD<`njxgN@xNACU(tZ literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/math/fb13547684600daec60944e8aadad3c60063047f.png b/documentation/user_manual/_images/math/fb13547684600daec60944e8aadad3c60063047f.png new file mode 100644 index 0000000000000000000000000000000000000000..bd7e28700a1f95fa3bd1605b94f75083d1e256cc GIT binary patch literal 514 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 TrFAa=0;&O1Ba|T(;S%*vKyWJ{ literal 0 HcmV?d00001 diff --git a/documentation/user_manual/_images/sequence_lns.png b/documentation/user_manual/_images/sequence_lns.png new file mode 100644 index 0000000000000000000000000000000000000000..b470227b2d4d6ca269c946ffd80f3c6db5126b14 GIT binary patch literal 33737 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 literal 0 HcmV?d00001 diff --git a/documentation/user_manual/genindex.html b/documentation/user_manual/genindex.html index 95490c1328..81ad6f2fa0 100644 --- a/documentation/user_manual/genindex.html +++ b/documentation/user_manual/genindex.html @@ -918,7 +918,7 @@ Search:
diff --git a/documentation/user_manual/index.html b/documentation/user_manual/index.html index ed877e20a0..9e64f32896 100644 --- a/documentation/user_manual/index.html +++ b/documentation/user_manual/index.html @@ -444,7 +444,7 @@ Search:
diff --git a/documentation/user_manual/manual/LS.html b/documentation/user_manual/manual/LS.html index 3eda701cee..7f291ff3ee 100644 --- a/documentation/user_manual/manual/LS.html +++ b/documentation/user_manual/manual/LS.html @@ -147,7 +147,7 @@ by them. In particular, Job-Shop instances with only one task per job are accept
  • 6.5. Basic working of the solver: Local Search
  • @@ -318,7 +318,7 @@ Search:
    diff --git a/documentation/user_manual/manual/TSP.html b/documentation/user_manual/manual/TSP.html index 0f77fdc0a9..71bd6dd745 100644 --- a/documentation/user_manual/manual/TSP.html +++ b/documentation/user_manual/manual/TSP.html @@ -28,7 +28,7 @@ - + diff --git a/documentation/user_manual/manual/VRP.html b/documentation/user_manual/manual/VRP.html index 13c4fd6559..60259bcd95 100644 --- a/documentation/user_manual/manual/VRP.html +++ b/documentation/user_manual/manual/VRP.html @@ -293,7 +293,7 @@ Search: diff --git a/documentation/user_manual/manual/custom_constraints.html b/documentation/user_manual/manual/custom_constraints.html index 9f2e6f6a2a..e91d6c8d61 100644 --- a/documentation/user_manual/manual/custom_constraints.html +++ b/documentation/user_manual/manual/custom_constraints.html @@ -27,8 +27,8 @@ - - + + @@ -191,16 +224,16 @@ Search: index
  • - next |
  • - previous |
  • or-tools User's Manual »
  • diff --git a/documentation/user_manual/manual/custom_constraints/all_different_except_zero_definition.html b/documentation/user_manual/manual/custom_constraints/all_different_except_zero_definition.html new file mode 100644 index 0000000000..b6d5cd77fb --- /dev/null +++ b/documentation/user_manual/manual/custom_constraints/all_different_except_zero_definition.html @@ -0,0 +1,204 @@ + + + + + + + + + + 8.1. The alldifferent_except_0 constraint — or-tools User's Manual + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +

    8.1. The alldifferent_except_0 constraint

    +
    +

    8.1.1. Definition

    +
    +
    +

    8.1.2. The implemented AllDifferentExcept

    +
    Constraint* Solver::MakeAllDifferentExcept(const std::vector<IntVar*>&
    +                                           vars,
    +                                           int64 escape_value) {
    +  int escape_candidates = 0;
    +  for (int i = 0; i < vars.size(); ++i) {
    +    escape_candidates += (vars[i]->Contains(escape_value));
    +  }
    +  if (escape_candidates <= 1) {
    +    return MakeAllDifferent(vars);
    +  } else {
    +    return RevAlloc(new AllDifferentExcept(this, vars, escape_value));
    +  }
    +}
    +
    +
    +
    +
    +

    8.1.3. Use

    +
    +
    +

    8.1.4. A first example: The Partial Latin Square Extension Problem (PLSE)

    +

    Footnote

    +
    + + +
    +
    +
    +
    +
    + + + + + +

    Google or-tools
    open source library

    +

    User's Manual

    + + + + + + +

    Google search

    + +
    + +
    + + + +
    + +
    +Search: + +
    +
    + +
    +
    + + + + +

    Welcome

    + + + + + + + +

    Tutorial examples

    + + + + + + +

    Current chapter

    +

    8. Custom constraints: the alldifferent_except_0 constraint

    +

    Previous section

    +

    8. Custom constraints: the alldifferent_except_0 constraint

    +

    Next section

    +

    8.2. Basic working of the solver: constraints

    +

    Current section

    + + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/documentation/user_manual/manual/custom_constraints/all_different_except_zero_model.html b/documentation/user_manual/manual/custom_constraints/all_different_except_zero_model.html new file mode 100644 index 0000000000..22990b0d3e --- /dev/null +++ b/documentation/user_manual/manual/custom_constraints/all_different_except_zero_model.html @@ -0,0 +1,177 @@ + + + + + + + + + + 8.5. First approach: model the constraint — or-tools User's Manual + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +

    8.5. First approach: model the constraint

    +
    +

    8.5.1. First model

    +
    +
    + + +
    +
    +
    +
    +
    + + + + + +

    Google or-tools
    open source library

    +

    User's Manual

    + + + + + + +

    Google search

    + +
    + +
    + + + +
    + +
    +Search: + +
    +
    + +
    +
    + + + + +

    Welcome

    + + + + + + + +

    Tutorial examples

    + + + + + + +

    Current chapter

    +

    8. Custom constraints: the alldifferent_except_0 constraint

    +

    Previous section

    +

    8.4. A basic Constraint example: the XXX Constraint

    +

    Next section

    +

    8.6. The AllDifferent constraint in more details

    +

    Current section

    + + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/documentation/user_manual/manual/custom_constraints/alldifferent.html b/documentation/user_manual/manual/custom_constraints/alldifferent_constraint.html similarity index 75% rename from documentation/user_manual/manual/custom_constraints/alldifferent.html rename to documentation/user_manual/manual/custom_constraints/alldifferent_constraint.html index 976f3bbd91..4d1f078a95 100644 --- a/documentation/user_manual/manual/custom_constraints/alldifferent.html +++ b/documentation/user_manual/manual/custom_constraints/alldifferent_constraint.html @@ -8,7 +8,7 @@ - 8.3. The AllDifferent constraint — or-tools User's Manual + 8.6. The AllDifferent constraint in more details — or-tools User's Manual @@ -28,8 +28,8 @@ - - + + @@ -54,8 +54,8 @@ diff --git a/documentation/user_manual/manual/custom_constraints/dynamic_improvements.html b/documentation/user_manual/manual/custom_constraints/alldifferent_except_zero_constraint.html similarity index 74% rename from documentation/user_manual/manual/custom_constraints/dynamic_improvements.html rename to documentation/user_manual/manual/custom_constraints/alldifferent_except_zero_constraint.html index fa4ef87b61..a3df1aae0f 100644 --- a/documentation/user_manual/manual/custom_constraints/dynamic_improvements.html +++ b/documentation/user_manual/manual/custom_constraints/alldifferent_except_zero_constraint.html @@ -8,7 +8,7 @@ - 8.4. Changing dynamically the improvement step with a SearchMonitor — or-tools User's Manual + 8.7. Second approach: a custom Constraint — or-tools User's Manual @@ -28,8 +28,8 @@ - - + + @@ -54,10 +54,13 @@
    -
    -

    8.4. Changing dynamically the improvement step with a SearchMonitor

    -


























    -


























    +
    +

    8.7. Second approach: a custom Constraint

    +
    +

    8.7.1. Well

    +

    alldifferent_except_0

    +
    +
    @@ -134,11 +137,19 @@ Search:

    8. Custom constraints: the alldifferent_except_0 constraint

    Previous section

    -

    8.3. The AllDifferent constraint

    +

    8.6. The AllDifferent constraint in more details

    Next section

    8.5. Summary

    + title="next chapter">8.8. Summary

    +

    Current section

    + +
    @@ -150,17 +161,17 @@ Search: index
  • - next |
  • - previous |
  • or-tools User's Manual »
  • -
  • 8. Custom constraints: the alldifferent_except_0 constraint »
  • +
  • 8. Custom constraints: the alldifferent_except_0 constraint »
  • diff --git a/documentation/user_manual/manual/custom_constraints/basic_constraint_example.html b/documentation/user_manual/manual/custom_constraints/basic_constraint_example.html new file mode 100644 index 0000000000..8ff519bb4e --- /dev/null +++ b/documentation/user_manual/manual/custom_constraints/basic_constraint_example.html @@ -0,0 +1,232 @@ + + + + + + + + + + 8.4. A basic Constraint example: the XXX Constraint — or-tools User's Manual + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +

    8.4. A basic Constraint example: the XXX Constraint

    +
    +

    8.4.1. The AllDifferentExcept constraint more in details

    +
    class AllDifferentExcept : public Constraint {
    + public:
    +  AllDifferentExcept(Solver* const s, std::vector<IntVar*> vars, int64 escape_value)
    +      : Constraint(s), vars_(vars), escape_value_(escape_value) {}
    +
    +  virtual ~AllDifferentExcept() {}
    +
    +  virtual void Post() {
    +    for (int i = 0; i < vars_.size(); ++i) {
    +      IntVar* const var = vars_[i];
    +      Demon* const d = MakeConstraintDemon1(
    +          solver(), this, &AllDifferentExcept::Propagate, "Propagate", i);
    +      var->WhenBound(d);
    +    }
    +  }
    +
    +  virtual void InitialPropagate() {
    +    for (int i = 0; i < vars_.size(); ++i) {
    +      if (vars_[i]->Bound()) {
    +        Propagate(i);
    +      }
    +    }
    +  }
    +
    +  void Propagate(int index) {
    +    const int64 val = vars_[index]->Value();
    +    if (val != escape_value_) {
    +      for (int j = 0; j < vars_.size(); ++j) {
    +        if (index != j) {
    +          vars_[j]->RemoveValue(val);
    +        }
    +      }
    +    }
    +  }
    +
    +  virtual std::string DebugString() const {
    +    return StringPrintf("AllDifferentExcept([%s], %" GG_LL_FORMAT "d",
    +                        JoinDebugStringPtr(vars_, ", ").c_str(), escape_value_);
    +  }
    +
    +  virtual void Accept(ModelVisitor* const visitor) const {
    +    visitor->BeginVisitConstraint(ModelVisitor::kAllDifferent, this);
    +    visitor->VisitIntegerVariableArrayArgument(ModelVisitor::kVarsArgument,
    +                                               vars_);
    +    visitor->VisitIntegerArgument(ModelVisitor::kValueArgument, escape_value_);
    +    visitor->EndVisitConstraint(ModelVisitor::kAllDifferent, this);
    +  }
    +
    + private:
    +  std::vector<IntVar*> vars_;
    +  const int64 escape_value_;
    +};
    +
    +
    +

    basic_constraint_example

    +
    +
    + + +
    +
    +
    +
    +
    + + + + + +

    Google or-tools
    open source library

    +

    User's Manual

    + + + + + + +

    Google search

    + +
    + +
    + + + +
    + +
    +Search: + +
    +
    + +
    +
    + + + + +

    Welcome

    + + + + + + + +

    Tutorial examples

    + + + + + + +

    Current chapter

    +

    8. Custom constraints: the alldifferent_except_0 constraint

    +

    Previous section

    +

    8.3. Consistency in a nutshell

    +

    Next section

    +

    8.5. First approach: model the constraint

    +

    Current section

    + + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/documentation/user_manual/manual/custom_constraints/basic_working_constraints.html b/documentation/user_manual/manual/custom_constraints/basic_working_constraints.html index e8bad78938..f9c8e4069c 100644 --- a/documentation/user_manual/manual/custom_constraints/basic_working_constraints.html +++ b/documentation/user_manual/manual/custom_constraints/basic_working_constraints.html @@ -8,7 +8,7 @@ - 8.1. Basic working of the solver: constraints — or-tools User's Manual + 8.2. Basic working of the solver: constraints — or-tools User's Manual @@ -28,8 +28,8 @@ - - + + @@ -55,9 +55,33 @@
    -

    8.1. Basic working of the solver: constraints

    -
    -

    8.1.1. Description

    +

    8.2. Basic working of the solver: constraints

    +
    +

    8.2.1. BaseObject

    +
    +
    +

    8.2.2. Demons

    +
    +
    +

    8.2.3. The BaseObject and PropagationBaseObject classes

    +
    +

    8.2.3.1. PropagationBaseObject

    +
    +
    +
    +

    8.2.4. The propagation queue

    +
    +
    +

    8.2.5. The Constraint class

    +
    +

    8.2.5.1. The Post() method

    +
    +
    +

    8.2.5.2. The InitialPropagate() method

    +
    +
    +
    +

    8.2.6. Propagation in action: the PropagationMonitor class























































    @@ -137,15 +161,27 @@ Search:

    8. Custom constraints: the alldifferent_except_0 constraint

    Previous section

    -

    8. Custom constraints: the alldifferent_except_0 constraint

    +

    8.1. The alldifferent_except_0 constraint

    Next section

    8.2. Consistency

    + title="next chapter">8.3. Consistency in a nutshell

    Current section

    diff --git a/documentation/user_manual/manual/custom_constraints/consistency.html b/documentation/user_manual/manual/custom_constraints/consistency.html index 6254c1f286..d0d84bd877 100644 --- a/documentation/user_manual/manual/custom_constraints/consistency.html +++ b/documentation/user_manual/manual/custom_constraints/consistency.html @@ -8,7 +8,7 @@ - 8.2. Consistency — or-tools User's Manual + 8.3. Consistency in a nutshell — or-tools User's Manual @@ -28,8 +28,8 @@ - - + + @@ -54,8 +54,8 @@
    -
    -

    8.2. Consistency

    +
    +

    8.3. Consistency in a nutshell























































    @@ -135,10 +135,10 @@ Search: title="previous chapter">8. Custom constraints: the alldifferent_except_0 constraint

    Previous section

    8.1. Basic working of the solver: constraints

    + title="previous chapter">8.2. Basic working of the solver: constraints

    Next section

    -

    8.3. The AllDifferent constraint

    +

    8.4. A basic Constraint example: the XXX Constraint

    @@ -150,17 +150,17 @@ Search: index
  • - next |
  • - previous |
  • or-tools User's Manual »
  • -
  • 8. Custom constraints: the alldifferent_except_0 constraint »
  • +
  • 8. Custom constraints: the alldifferent_except_0 constraint »
  • diff --git a/documentation/user_manual/manual/custom_constraints/summary.html b/documentation/user_manual/manual/custom_constraints/summary.html index d8b6024169..6035c3d08d 100644 --- a/documentation/user_manual/manual/custom_constraints/summary.html +++ b/documentation/user_manual/manual/custom_constraints/summary.html @@ -8,7 +8,7 @@ - 8.5. Summary — or-tools User's Manual + 8.8. Summary — or-tools User's Manual @@ -29,7 +29,7 @@ - + @@ -55,7 +55,7 @@ diff --git a/documentation/user_manual/manual/first_steps.html b/documentation/user_manual/manual/first_steps.html index 5b235dc873..3c15b53bc9 100644 --- a/documentation/user_manual/manual/first_steps.html +++ b/documentation/user_manual/manual/first_steps.html @@ -252,7 +252,7 @@ Search:
    diff --git a/documentation/user_manual/manual/first_steps/anatomy.html b/documentation/user_manual/manual/first_steps/anatomy.html index 0fe67325b0..f99ab8291f 100644 --- a/documentation/user_manual/manual/first_steps/anatomy.html +++ b/documentation/user_manual/manual/first_steps/anatomy.html @@ -523,7 +523,7 @@ Search: diff --git a/documentation/user_manual/manual/first_steps/cryptarithmetic.html b/documentation/user_manual/manual/first_steps/cryptarithmetic.html index c15279bcf9..37fa59388f 100644 --- a/documentation/user_manual/manual/first_steps/cryptarithmetic.html +++ b/documentation/user_manual/manual/first_steps/cryptarithmetic.html @@ -295,7 +295,7 @@ Search: diff --git a/documentation/user_manual/manual/first_steps/getting_started.html b/documentation/user_manual/manual/first_steps/getting_started.html index 6a3f51dac2..366ed23891 100644 --- a/documentation/user_manual/manual/first_steps/getting_started.html +++ b/documentation/user_manual/manual/first_steps/getting_started.html @@ -250,7 +250,7 @@ Search: diff --git a/documentation/user_manual/manual/first_steps/monitors.html b/documentation/user_manual/manual/first_steps/monitors.html index 47ac9c4fd3..67f66da71c 100644 --- a/documentation/user_manual/manual/first_steps/monitors.html +++ b/documentation/user_manual/manual/first_steps/monitors.html @@ -345,7 +345,7 @@ Search: diff --git a/documentation/user_manual/manual/first_steps/parameters.html b/documentation/user_manual/manual/first_steps/parameters.html index a55aacae9f..d9378c2f0e 100644 --- a/documentation/user_manual/manual/first_steps/parameters.html +++ b/documentation/user_manual/manual/first_steps/parameters.html @@ -68,7 +68,7 @@ command line flag library. Second, we explain how to pass parameters to the CP solver.

    2.6.1. Google’s gflags

    -

    The Google’s flags library is quite similar to other command flags libraries with the noticeable +

    The Google’s flags library is quite similar to other command line flags libraries with the noticeable difference that the flag definitions may be scattered in different files.

    To define a flag, we use the corresponding macro. Google’s flags library supports six types:

      @@ -293,7 +293,7 @@ Search:
    diff --git a/documentation/user_manual/manual/first_steps/summary.html b/documentation/user_manual/manual/first_steps/summary.html index 5f0dbcbee2..e956bc8aa9 100644 --- a/documentation/user_manual/manual/first_steps/summary.html +++ b/documentation/user_manual/manual/first_steps/summary.html @@ -28,7 +28,7 @@ - + @@ -39,7 +39,7 @@ index
  • - next |
  • 2.7. Other supported languages

    Next section

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="next chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    @@ -161,7 +161,7 @@ Search: index
  • - next |
  • diff --git a/documentation/user_manual/manual/first_steps/supported_languages.html b/documentation/user_manual/manual/first_steps/supported_languages.html index d763870471..f395522dcc 100644 --- a/documentation/user_manual/manual/first_steps/supported_languages.html +++ b/documentation/user_manual/manual/first_steps/supported_languages.html @@ -180,7 +180,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction.html b/documentation/user_manual/manual/introduction.html index 9da9f8cc0f..e9fd329fb4 100644 --- a/documentation/user_manual/manual/introduction.html +++ b/documentation/user_manual/manual/introduction.html @@ -243,7 +243,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction/4queens.html b/documentation/user_manual/manual/introduction/4queens.html index 84a8e14e91..ea614b22d1 100644 --- a/documentation/user_manual/manual/introduction/4queens.html +++ b/documentation/user_manual/manual/introduction/4queens.html @@ -313,7 +313,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction/manual_content.html b/documentation/user_manual/manual/introduction/manual_content.html index 1a379b59c0..4077a7991d 100644 --- a/documentation/user_manual/manual/introduction/manual_content.html +++ b/documentation/user_manual/manual/introduction/manual_content.html @@ -89,7 +89,7 @@ words about the way to pass read-only parameters to the solver and about the oth in or-tools (Python, Java and C#). Although this chapter is a gentle introduction to the basic use of the library, it also focuses on some basic but important manipulations needed to get things right. Don’t miss them! -
    Chapter 3: Using objectives in constraint programming: the Golomb ruler problem:
    +
    Chapter 3: Using objectives in constraint programming: the Golomb Ruler Problem:
    In this chapter, we not only look for a feasible solution but for an optimal solution, i.e. a solution that optimizes an objective function. To solve the Golomb Ruler Problem, we’ll try five different models and compare them two by two. To have an intuition of the models passed to the solver and the progress of the search, we show you how to inspect @@ -307,7 +307,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction/or_tools.html b/documentation/user_manual/manual/introduction/or_tools.html index 4f38ebccfe..d8a4ac493f 100644 --- a/documentation/user_manual/manual/introduction/or_tools.html +++ b/documentation/user_manual/manual/introduction/or_tools.html @@ -191,7 +191,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction/real_examples.html b/documentation/user_manual/manual/introduction/real_examples.html index 1654c74744..66925e1544 100644 --- a/documentation/user_manual/manual/introduction/real_examples.html +++ b/documentation/user_manual/manual/introduction/real_examples.html @@ -144,7 +144,7 @@ Until now, all these attempts have been vain. That said, CP - because of its par - +
    [3]You can learn more about Simulated Annealing (SA) in the section Simulated annealing (SA).
    [3]You can learn more about Simulated Annealing (SA) in the section Simulated Annealing (SA).
    @@ -313,7 +313,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction/summary.html b/documentation/user_manual/manual/introduction/summary.html index 4faa6191ca..3f8d252e4b 100644 --- a/documentation/user_manual/manual/introduction/summary.html +++ b/documentation/user_manual/manual/introduction/summary.html @@ -161,7 +161,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction/theory.html b/documentation/user_manual/manual/introduction/theory.html index d6ceb8bb2b..8428a8cd52 100644 --- a/documentation/user_manual/manual/introduction/theory.html +++ b/documentation/user_manual/manual/introduction/theory.html @@ -659,7 +659,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction/three_stages.html b/documentation/user_manual/manual/introduction/three_stages.html index ccea9f925e..a563b63144 100644 --- a/documentation/user_manual/manual/introduction/three_stages.html +++ b/documentation/user_manual/manual/introduction/three_stages.html @@ -233,7 +233,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction/tradeoffs.html b/documentation/user_manual/manual/introduction/tradeoffs.html index 9e3e475c9d..c50fbd5c27 100644 --- a/documentation/user_manual/manual/introduction/tradeoffs.html +++ b/documentation/user_manual/manual/introduction/tradeoffs.html @@ -202,7 +202,7 @@ Search: diff --git a/documentation/user_manual/manual/introduction/what_is_cp.html b/documentation/user_manual/manual/introduction/what_is_cp.html index 3098d8db07..1800558d1f 100644 --- a/documentation/user_manual/manual/introduction/what_is_cp.html +++ b/documentation/user_manual/manual/introduction/what_is_cp.html @@ -446,7 +446,7 @@ Search: diff --git a/documentation/user_manual/manual/ls/basic_working_local_search.html b/documentation/user_manual/manual/ls/basic_working_local_search.html index f0215d7dc7..cd44060b69 100644 --- a/documentation/user_manual/manual/ls/basic_working_local_search.html +++ b/documentation/user_manual/manual/ls/basic_working_local_search.html @@ -122,8 +122,8 @@ by FindOneNeighbor to

    We will not discuss the filtering mechanism here (see the dedicated section Filtering).

    -
    -

    6.5.2. Overview of the Local Search Mechanism in or-tools

    +
    +

    6.5.2. Overview of the Local Search Mechanism in or-tools

    The next figure illustrates the basic mechanism of Local Search in or-tools:

    ../../_images/lsn_mechanism.png

    We start with an initial feasible solution. The MakeOneNeighbor() callback method @@ -240,7 +240,7 @@ By default, the CP solver stops the neighborhood search as soon as it finds a fi If you add an OptimizeVar to your model, once the solver finds this good candidate solution, it changes the model to exclude solutions with the same objective value. The second solution found can only be better than the first one. See the section How does the solver optimize? -to refresh your memory if needed. When the solver +to refresh your memory if needed. In our example, when the solver finds 2 solutions (or when the whole neighborhood is explored), it stops and starts over again with the best solution.

  • LocalSearchFilters: these filters speed up the search by bypassing the solver checking mechanism if you know that the @@ -272,7 +272,7 @@ At least you need to declare a Lo DecisionBuilder will complete the candidate solutions if some of the variables are not assigned.

    A handy way to create a DecisionBuilder to assist the Local Search operator(s) is to limit one -with MakeSolveOnce(). MakeSolveOnce is a DecisionBuilder that takes another DecisionBuilder db +with MakeSolveOnce(). MakeSolveOnce returns a DecisionBuilder that takes another DecisionBuilder db and SearchMonitors:

    DecisionBuilder * const db = ...
     SearchLimit* const limit = solver.MakeLimit(...);
    @@ -285,9 +285,6 @@ and SearchMonitors:Next() method of) SolveOnce will
     fail.

    -

    If you know for sure that your LocalSearchOperator will return feasible -solutions, you don’t have to provide a DecisionBuilder to assist: just submit NULL as argument -for the DecisionBuilder pointer.

    @@ -542,7 +539,7 @@ you must take care of applying and reverting the if statement on line 38, we have a new candidate solution and we update the candidate solution counter accordingly. It is now time to test this new candidate solution. The first test comes from the SearchMonitors in their -AcceptDelta() methods. If only one SearchMonitor rejects this solution, it is rejected. In or-tools, we +AcceptDelta() methods. If only one SearchMonitor rejects this solution, it is rejected. In or-tools, we implement (meta-)heuristics with SearchMonitors. See the chapter Meta-heuristics: several previous problems for more.

    The AcceptDelta() function is the global utility function we mentioned above. We’ll meet LocalOptimumReached() and AcceptNeighbor() a few lines below.

    @@ -604,7 +601,7 @@ or SolveAndCommit() s
  • The three states are defined in the NestedSolveDecision StateType enum.

    We are now ready to assemble all the pieces of the puzzle together to understand the (simplified) -Local Search algorithm in or-tools.

    +Local Search algorithm in or-tools.

    6.5.3.3. The LocalSearch DecisionBuilder

    @@ -771,7 +768,7 @@ first and discuss it next:

    const int state = decision->state(); switch (state) { case NestedSolveDecision::DECISION_FAILED: { - // SEARCHMONITOR CALLBACK + // SEARCHMONITOR CALLBACK: LocalOptimum() if (!LocalOptimumReached(solver->ActiveSearch())) { // Stop the current search ... @@ -854,7 +851,8 @@ section ObjectiveVar, the next feasible solution will be a solution that beats the current best solution. -You can change this behaviour with a SearchLimit. Read on. +You can change this behaviour with a SearchLimit. Read on. The LocalSearch class is also deeply coupled to the Metaheuristic class or more generally to a SearchMonitor. This is the subject +of next chapter. @@ -955,7 +953,7 @@ Search:
  • 6.5.1.1. The main actors
  • -
  • 6.5.2. Overview of the Local Search Mechanism in or-tools diff --git a/documentation/user_manual/manual/ls/jobshop_def_data.html b/documentation/user_manual/manual/ls/jobshop_def_data.html index cc3ee204fe..3e0b8523c6 100644 --- a/documentation/user_manual/manual/ls/jobshop_def_data.html +++ b/documentation/user_manual/manual/ls/jobshop_def_data.html @@ -599,7 +599,7 @@ Search: diff --git a/documentation/user_manual/manual/ls/jobshop_implementation.html b/documentation/user_manual/manual/ls/jobshop_implementation.html index cd7b097079..d1b3c2bb76 100644 --- a/documentation/user_manual/manual/ls/jobshop_implementation.html +++ b/documentation/user_manual/manual/ls/jobshop_implementation.html @@ -490,7 +490,7 @@ Search: diff --git a/documentation/user_manual/manual/ls/jobshop_ls.html b/documentation/user_manual/manual/ls/jobshop_ls.html index 7d07df4eed..3565d71876 100644 --- a/documentation/user_manual/manual/ls/jobshop_ls.html +++ b/documentation/user_manual/manual/ls/jobshop_ls.html @@ -560,8 +560,8 @@ solution quicker with shuffle_len

    You should know by now that whenever we ask this question in this manual, the answer is yes. To find a better solution, we’ll first investigate how important the initial solution is and then we’ll enlarge our definition of a neighborhood by combining our two LocalSearchOperators.

    -
    -

    6.7.5.1. The initial solution

    +
    +

    6.7.5.1. The initial solution

    Local search is strongly dependent on the initial solution. Investing time in finding a good solution is a good idea. We’ll use... Local Search to find an initial solution to get the real Local Search started! The idea is that maybe we can @@ -654,7 +654,7 @@ solution has been found.

    The rest of the code is similar to that in the file jobshop_ls2.cc.

    -

    6.7.5.3. Results

    +

    6.7.5.3. Results

    If we solve our problem instance (file first_example_jssp.txt), we still get the optimal solution. No surprise here. What about the abz9 instance?

    With our default value of

    @@ -884,7 +884,7 @@ Search:
  • 6.7.3. Exchanging two IntervalVars on a SequenceVar
  • 6.7.4. Exchanging an arbitrary number of contiguous IntervalVars on a SequenceVar
  • 6.7.5. Can we do better? @@ -914,7 +914,7 @@ Search: diff --git a/documentation/user_manual/manual/ls/local_search.html b/documentation/user_manual/manual/ls/local_search.html index d3aa6ac0f1..05396d0cf5 100644 --- a/documentation/user_manual/manual/ls/local_search.html +++ b/documentation/user_manual/manual/ls/local_search.html @@ -500,7 +500,7 @@ Search: diff --git a/documentation/user_manual/manual/ls/ls_filtering.html b/documentation/user_manual/manual/ls/ls_filtering.html index 212013f9d3..4b6d8d33b9 100644 --- a/documentation/user_manual/manual/ls/ls_filtering.html +++ b/documentation/user_manual/manual/ls/ls_filtering.html @@ -380,7 +380,7 @@ Search: diff --git a/documentation/user_manual/manual/ls/ls_operators.html b/documentation/user_manual/manual/ls/ls_operators.html index a337b61d61..77dc75dc03 100644 --- a/documentation/user_manual/manual/ls/ls_operators.html +++ b/documentation/user_manual/manual/ls/ls_operators.html @@ -95,7 +95,7 @@ LS Operators hierarchy.

    ../../_images/lsn_hierarchy2.png

    These classes are declared in the header constraint_solver/constraint_solveri.h.

    The PathOperator class is itself the base class of several other path specialized -LS Operators. We will review them in the subsection Local Search PathOperators.[2]

    +LS Operators. We will review them in the subsection Local Search PathOperators[2].

    IntVarLocalSearchOperator is a specialization[3] of LocalSearchOperator built for an array of IntVars while SequenceVarLocalSearchOperator is a specialization of LocalSearchOperator built for an array of SequenceVars[4].

    @@ -441,8 +441,8 @@ the variables are changed from the beginning of the vector anew.

    6.6.4.4. Large Neighborhood Search (LNS)

    -

    And last but not least, in or-tools, Large Neighborhood Search is implemented with LocalSearchOperators but -this is the topic of the section Large neighborhood search (LNS): the job-shop problem.

    +

    And last but not least, in or-tools, Large Neighborhood Search is implemented with LocalSearchOperators but +this is the topic of the section Large neighborhood search (LNS): the Job-Shop Problem.

    Footnotes

  • @@ -612,7 +612,7 @@ Search: diff --git a/documentation/user_manual/manual/ls/ls_summary.html b/documentation/user_manual/manual/ls/ls_summary.html index 7701dc0f24..2a14b77cba 100644 --- a/documentation/user_manual/manual/ls/ls_summary.html +++ b/documentation/user_manual/manual/ls/ls_summary.html @@ -170,7 +170,7 @@ Search: diff --git a/documentation/user_manual/manual/ls/scheduling_or_tools.html b/documentation/user_manual/manual/ls/scheduling_or_tools.html index d122b8acc8..254d2e6e3b 100644 --- a/documentation/user_manual/manual/ls/scheduling_or_tools.html +++ b/documentation/user_manual/manual/ls/scheduling_or_tools.html @@ -70,7 +70,7 @@ expect more to come. We summarize most of the

    This part of the CP Solver is not quite settled yet. In case of doubt, check the code.

    -

    6.3.1. Variables

    +

    6.3.1. Variables

    Two new types of variables are added to our arsenal: IntervalVars model tasks and SequenceVars model sequences of tasks on one machine. Once you master these variables, you can use them in a variety of different contexts but for the moment keep in mind this modelling association.

    @@ -869,7 +869,7 @@ Search:
    diff --git a/documentation/user_manual/manual/metaheuristics.html b/documentation/user_manual/manual/metaheuristics.html index 78b61ca9b6..db56adfdc2 100644 --- a/documentation/user_manual/manual/metaheuristics.html +++ b/documentation/user_manual/manual/metaheuristics.html @@ -54,82 +54,174 @@

    7. Meta-heuristics: several previous problems

    +

    A meta-heuristic is a canvas or framework to construct a heuristic adapted to your specific problem. Dozens of meta-heuristics have been invented. +Let’s set aside the unproductive debate about what meta-heuristic is best and who invented it and let’s dive right into the topic. Meta-heuristics:

    +
      +
    • are quite new: the first ones were designed in the 1960s;
    • +
    • are not very well understood: why are some efficient for some problems but not for others?;
    • +
    • are somehow all interrelated: you can express one meta-heuristic with another;
    • +
    • have their efficiency heavily depending on the quality of the code as well as the knowledge of the problem.
    • +
    • are often based on very simple ideas.
    • +
    +

    One could write books on meta-heuristics and indeed lots of books, articles and reports have been written. There are +even scientific communities that only swear by this meta-heuristic and each meta-heuristic comes with its +own vocabulary[1]. In this manual, we only scratch the surface of this fascinating subject.

    +

    Many meta-heuristics are based on Local Search[2]: they start with an initial solution and improve it +little by little. From now on and for the rest of this chapter, we only talk about meta-heuristics using Local Search.

    +
    +

    Warning

    +

    The whole chapter is about meta-heuristics using Local Search.

    +
    +

    Among them, we present three well-known meta-heuristics:

    +
      +
    • Tabu Search: one of the most efficient meta-heuristic on the market!
    • +
    • Simulated Annealing: one of the first available meta-heuristic.
    • +
    • Guided Local Search: well suited for some problems like Routing Problems.
    • +
    +

    These three meta-heuristics are implemented in or-tools and are used in the routing library (through flags).

    +

    In or-tools, we implement meta-heuristics with SearchMonitors guiding Local Search (and thus we also use the +LocalSearch, LocalSearchOperator, LocalSearchFilter classes). +This is quite “natural” as SearchMonitors allow to... monitor the +search and the implemented meta-heuristics are based on Local Search.

    +

    When you implement a (meta)-heuristic, you have to take decisions. Our implementations are one possibility among others. We would like to warn the reader on an important choice +we have made in our implementation: we only use meta-heuristic when we have reached a local optimum, not before!

    +
    +

    Warning

    +

    Our meta-heuristics only kick in when we already have reached a local optimum with the Local Search.

    +
    +

    A last word of advice. In this chapter, we decided to show you the code in full details. Real code, especially in optimization, is complicated and has to deal with lots of subtleties. While we try to explain most of the intricate details (but not all), you only see the end results. The code we show has been polished and is used in production (and changes sometimes!). Some parts have a long history of trial and error and there is no way we can explain all the details. We cannot emphasize enough that you’ll learn how to use the or-tools by actually coding with it, not only reading existing code. That said, the exposed code gives you good examples on what can be done and how the makers of the or-tools library decided to do it. Beside, you’ll have a pretty good idea of what actually our meta-heuristic implementations do and how to adapt the code to solve your problems.

    Overview:

    +

    Before diving in the heart of the matter, we address two ways to improve the search. First, because our meta-heuristics never stop, we detail how SearchLimits can be used to stop a search. +Second, we discuss restarting a search. Recent research shows that restarting a search might improve the overall search. +Next, we detail our Metaheuristic class and our three specialized TabuSearch, SimulatedAnnealing and GuidedLocalSearch implementations. To finish this chapter, we present another +meta-heuristic, Large Neighborhood Search[3], that is not implemented with a SearchMonitor but with a LocalSearchOperator. Next we answer the question “What do you do when you have no idea of a (good) search strategy?”. +Answer? You throw in lots of heuristic and random behavior in your top-level search. In or-tools, we implemented the DefaultIntegerSearch DecisionBuilder to do just that.

    Prerequisites:

    -

    xxxxx

    -

    Classes under scrutiny:

    -

    SearchMonitor, Metaheuristic, TabuSearch, SimulatedAnnealing, GuidedLocalSearch.

    +

    SearchLimit, SearchMonitor, Metaheuristic, TabuSearch, SimulatedAnnealing, GuidedLocalSearch, LocalSearchOperator and DefaultIntegerSearch.

    Files:

    -

    You can find the code in the directory documentation/tutorials/cplusplus/chap7.

    -

    The files inside this directory are:

    +

    The files used in this chapter are:

    +
      +
    • limits.h: This header file contains several search limits. This file is used +throughout most of the examples of this chapter.
    • +
    • jobshop_ts1.cc: The same as file jobshop_ls1.cc from the previous chapter but with Tabu Search added.
    • +
    • jobshop_ts2.cc: The same as file jobshop_ls3.cc from the previous chapter but with Tabu Search added.
    • +
    • jobshop_sa1.cc: The same as file jobshop_ls1.cc from the previous chapter but with Simulated Annealing added.
    • +
    • jobshop_sa2.cc: The same as file jobshop_ls3.cc from the previous chapter but with Simulated Annealing added.
    • +
    • dummy_lns.cc: The basic example solved with Large Neighborhood Search.
    • +
    • jobshop_lns.h: a basic SequenceLns LocalSearchOperator to solve the Job-Shop Problem with Large Neighborhood Search.
    • +
    • jobshop_lns.cc: A basic implementation of Large Neighborhood Search with the SequenceLns LocalSearchOperator to solve the Job-Shop Problem.
    • +
    • jobshop_heuristic.cc: We use all the previous ingredients to solve approximately the Job-Shop Problem.
    • +
    • golomb_default_search1.cc: We solve the Golomb Ruler Problem with Default Search.
    • +
    • golomb_default_search2.cc: Same as golomb_default_search1.cc but with customized DefaultPhaseParameters parameters.
    • +

    Content:

    -


























    -


























    +

    Footnotes

    + + + + +
    [1]

    In order to “sell” your (meta-)heuristic to the scientific community, +it is also good to give it a snappy name. +We don’t resist to name a few:

    +
      +
    • artificial bee colony algorithm,
    • +
    • honey-bee mating optimization,
    • +
    • intelligent water drops,
    • +
    • firefly algorithm,
    • +
    • monkey search,
    • +
    • league championship algorithm,
    • +
    • cuckoo search,
    • +
    • virus optimization algorithm,
    • +
    • galaxy-based search algorithm,
    • +
    • ...
    • +
    +

    and our favorite: the imperialist competitive algorithm.

    +
    + + + + + + + + + + + +
    [3]Large Neighborhood Search (LNS) can be seen as a meta-heuristic (same for Local Search) and is in a way an extension of Local Search. In or-tools, we implement LNS +with special LocalSearchOperators.
    +
    @@ -257,7 +349,7 @@ Search: diff --git a/documentation/user_manual/manual/metaheuristics/GLS.html b/documentation/user_manual/manual/metaheuristics/GLS.html index 56dbdacb66..b0e564b31c 100644 --- a/documentation/user_manual/manual/metaheuristics/GLS.html +++ b/documentation/user_manual/manual/metaheuristics/GLS.html @@ -8,7 +8,7 @@ - 7.7. Guided local search (GLS) — or-tools User's Manual + 7.6. Guided Local Search (GLS) — or-tools User's Manual @@ -28,8 +28,8 @@ - - + +
  • - next |
  • - previous |
  • or-tools User's Manual »
  • 7. Meta-heuristics: several previous problems »
  • @@ -55,17 +55,824 @@
    -

    7.7. Guided local search (GLS)

    +

    7.6. Guided Local Search (GLS)

    +

    Guided Local Search is another successful meta-heuristic that emerged in the ‘90. It has been successfully applied to a large number of difficult problems and has been particularly successful in +Routing Problems.

    +

    Our Guided Local Search implementation is especially tailored for the Routing Library. It uses a callback to a cost function that takes two int64 indices corresponding to 2 nodes[1], i.e. the cost +of traversing an arc. If you can successfully translate the cost of using two variables i and j one after the other in your objective function for your specific problem, then you can use +our implementation out of the box. Otherwise, you’ll have to create your own version. We hope that after reading this section you’ll have a better idea on how you can do it. The last sub-section gives you some hints if you want to adapt our implementation to solve your problem.

    +
    +

    Warning

    +

    Our Guided Local Search implementation is especially tailored for the Routing Library

    +
    +

    Along the way, we’ll give you enough information to fully understand (almost) all the code and understand the Routing Library (RL) conventions[2].

    +

    Among the three implemented meta-heuristics implemented in or-tools, GLS has certainly the most refined and efficient (and thus complicated) implementation.

    -

    7.7.1. The basic idea

    +

    7.6.1. The basic idea

    +

    The GLS is a penalty-based method that sits on top of a Local Search. Its originality and efficiency stems from the way it penalizes some features of a solution along the search. We assume minimization.

    +
    +

    7.6.1.1. The augmented objective function

    +

    Denote by I_i the following +indicator function:

    +
    +

    I_i(x) =
+\begin{cases}
+  1 & \text{if solution } x \text{ has feature } i \\
+  0  & \text{otherwise}.
+\end{cases}

    +

    The GLS meta-heuristic penalizes some features of a local optimum. Let p_i be a penalty attached to a feature i and f denote the original objective function. +The GLS meta-heuristic uses the following augmented objective function g:

    +
    +

    g(x) = f(x) + \lambda \sum_i \left( I_i(x) \cdot p_i \right)

    +

    The idea is to let the Local Search find solutions with this new augmented objective function. \lambda is called the penalty factor and can be used to tune the search to find similar solutions (a low \lambda value, +intensification) +or completely different solutions (a high \lambda value, diversification).

    -
    -

    7.7.2. The implementation

    +
    +

    7.6.1.2. The penalties and their modifications

    +

    Penalties usually start with a 0 value and are incremented by 1 with each local optimum. The originality and efficiency of the GLS is that a feature is only penalized if its utility is large enough. +The idea is to penalize costly features but not penalize them too much if they often show up. The utility function for a feature i in a solution x is defined as follows:

    +
    +

    u_i(x) = I_i(x) \frac{c_i(x)}{1 + p_i}.

    +

    where c_i() denotes the cost associated with feature i in solution x. +If a feature i is not present in a solution x, its utility for this solution is 0 (I_i(x) = 0). Otherwise, the utility is proportional to the cost c_i(x) of this feature in the solution +x but tends +to disappear whenever this feature i is often penalized. A feature that shows up regularly in local optima might be part of a good solution.

    +
    +
    +
    +

    7.6.2. Our implementation

    +

    Our implementation is at the same time specific for Routing Problems but also generic for any Routing Problem. The chosen features of a solution is the fact that an arc (i,j) is traversed or not for +this solution. So, we will speak of a (i,j) Arc feature (and talk about c_{ij}, u_{ij} and p_{ij}).

    +

    Our implementation is practically following the basic GLS guidelines by the book.

    +

    Let’s denote by d_{ij} the cost of traversing an arc (i,j) in a given solution. In our case, this is given by the cost of the objective function for that arc and we have c_{ij} = d_{ij}. This cost can depend on the type of vehicle used if we use different types of vehicles.

    +

    Our augmented objective function is given by

    +
    +

    g(x) = \sum_{(i,j)} d_{ij}(x) + \lambda \sum_{(i.j)} \left( I_{ij}(x) \cdot p_{ij} \cdot c_{ij}(x) \right)

    +

    Within the Routing Library, the penalty factor \lambda is given by the gflags command line flag routing_guided_local_search_lambda_coefficient and is set to the value 0,1 by default.

    +
    +
    +

    7.6.3. GuidedLocalSearchPenalties

    +

    Penalties are stored in a GuidedLocalSearchPenalties class. This class is abstract and two implementations exist depending on the data structure to store the penalties:

    +
      +
    • GuidedLocalSearchPenaltiesTable: for dense GLS penalties using a matrix std::vector<std::vector<int64> > and
    • +
    • GuidedLocalSearchPenaltiesMap: for sparse GLS penalties using a hash_map[3].
    • +
    +

    By default, the dense version is used but you can switch to the sparse version by setting the cp_use_sparse_gls_penalties flag to true on the command line.

    +

    Here is the skeleton of the abstract class:

    +
    class GuidedLocalSearchPenalties {
    + public:
    +  virtual ~GuidedLocalSearchPenalties() {}
    +  virtual bool HasValues() const = 0;
    +  virtual void Increment(const Arc& arc) = 0;
    +  virtual int64 Value(const Arc& arc) const = 0;
    +  virtual void Reset() = 0;
    +};
    +
    +
    +

    An Arc is simply a (from,to) pair:

    +
    typedef std::pair<int64, int64> Arc;
    +
    +
    +
    +
    +

    7.6.4. The abstract GuidedLocalSearch class

    +

    The GuidedLocalSearch class is a pure abstract base class. Two specialized implementations exist:

    +
      +
    • BinaryGuidedLocalSearch (2-indices version): when all vehicles have the same cost to traverse any arc (i,j) and
    • +
    • TernaryGuidedLocalSearch (3-indices version): when the cost of traversing an arc (i,j) also depends on the type of vehicle k.
    • +
    +

    We discuss these two classes in details later on.

    +
    +

    7.6.4.1. To compare two Arcs

    +

    To compare two arcs, we use the following comparator:

    +
    struct Comparator {
    +  bool operator()(const std::pair<Arc, double>& i,
    +                  const std::pair<Arc, double>& j) {
    +    return i.second > j.second;
    +  }
    +};
    +
    +
    +

    This struct is called a functor (or function object) and is basically a function call encapsulated in a class (or a struct). This is done by overloading the +function call operator (operator()) of the class (or struct)[4].

    +

    Notice that we compare the double values attached to each Arcs in the (Arc, double) pairs. +We’ll use this Comparator struct to compare +utilities attached to Arcs.

    +
    +
    +

    7.6.4.2. The variables and the constructor

    +

    Let’s start with the (protected) variables of the GuidedLocalSearch class:

    +
    IntVar* penalized_objective_;
    +Assignment assignment_;
    +int64 assignment_penalized_value_;
    +int64 old_penalized_value_;
    +const std::vector<IntVar*> vars_;
    +hash_map<const IntVar*, int64> indices_;
    +const double penalty_factor_;
    +std::unique_ptr<GuidedLocalSearchPenalties> penalties_;
    +std::unique_ptr<int64[]> current_penalized_values_;
    +std::unique_ptr<int64[]> delta_cache_;
    +bool incremental_;
    +
    +
    +

    We cover the most interesting variables.

    +

    The penalized_objective_ IntVar represents the penalized part of the penalized objective function: \lambda \sum_{(i.j)} \left( I_{ij}(x) \cdot p_{ij} \cdot c_{ij}(x) \right). When there are no +penalties, the pointer penalized_objective_ is set to nullptr. Actually, the expression of penalized_objective_ is a little bit more complicated than that because of our choice of added constraints. +See the ApplyDecision() method below for more details.

    +

    We keep the current solution in assignment_ as usual.

    +

    assignment_penalized_value_ is the value of the expression \lambda \sum_{(i.j)} \left( I_{ij}(x) \cdot p_{ij} \cdot c_{ij}(x) \right) for the current solution. +old_penalized_value_ is used to update the penalized value incrementally in the AcceptDelta method.

    +

    vars_ is an std::vector with our node variables.

    +

    indices_ is a hash_map to quickly find the index of a variable given as IntVar*.

    +

    penalty_factor is the penalty factor \lambda.

    +

    The penalties computed during the search are stored in a GuidedLocalSearchPenalties object pointed to by the penalties_ variable: for an Arc arc, penalties_->Value(arc) returns its current penalty.

    +

    Finally, the three last variables are used to update the penalized costs incrementally in the AcceptDelta() method. We’ll discuss this method in details below.

    +

    The constructor is quite straightforward:

    +
    GuidedLocalSearch(Solver* const s, IntVar* objective, bool maximize,
    +                  int64 step, const std::vector<IntVar*>& vars,
    +                  double penalty_factor);
    +
    +
    +

    where step is the usual step used to force the objective function to improve.

    +
    +
    +

    7.6.4.3. The pure virtual methods and the helpers

    +

    The pure virtual methods that must be defined in a specialized GuidedLocalSearch class are:

    +
    virtual int64 AssignmentElementPenalty(const Assignment& assignment,
    +                                       int index) = 0;
    +virtual int64 AssignmentPenalty(const Assignment& assignment, int index,
    +                                int64 next) = 0;
    +virtual bool EvaluateElementValue(const Assignment::IntContainer&
    +                                                              container,
    +                                  int64 index, int* container_index,
    +                                  int64* penalty) = 0;
    +virtual IntExpr* MakeElementPenalty(int index) = 0;
    +
    +
    +

    The used of 2 indices (in the signature of AssignmentPenalty) indicates that our GuidedLocalSearch class is really tailored to deal with arcs. The best way to understand what these methods are supposed to do is to study their implementations in details.

    +
      +
    • AssignmentElementPenalty() returns the penalized value associated to the arc leaving node i in a given solution assignment. +This penalized value is (for minimization) equal to \lambda \cdot p_{ij}(x) \cdot c_{ij}(x) for a given solution x.

      +

      We need to do do a little incursion in the Routing Library (RL) before we can go on.

      +

      The RL (Routing Library) encodes the traversing of an arc (i,j) in a solution with vars_[i] = j, i.e. from node i go to node j where vars_[i] denotes the IntVar +variable corresponding +to node i and vars_ is an std::vector of such variables. Back to AssignmentElementPenalty.

      +

      Here is the implementation of this method for the BinaryGuidedLocalSearch class:

      +
      int64 AssignmentElementPenalty(
      +    const Assignment& assignment, int index) {
      +  return PenalizedValue(index, assignment.Value(vars_[index]));
      +}
      +
      +
      +

      where the PenalizedValue(int64 i, int64 j) helper method computes the penalized value for a given arc (i,j):

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      int64 PenalizedValue(int64 i, int64 j) {
      +  const Arc arc(i, j);
      +  const int64 penalty = penalties_->Value(arc);
      +  if (penalty != 0) {
      +    const int64 penalized_value =
      +        penalty_factor_ * penalty * objective_function_->Run(i, j);
      +    if (maximize_) {
      +      return -penalized_value;
      +    } else {
      +      return penalized_value;
      +    }
      +  } else {
      +    return 0;
      +  }
      +}
      +
      +
      +

      The test if (penalty != 0) on line 4 is simply to avoid costly objective_function_->Run(i, j) calls.

      +
    • +
    • AssignmentPenalty() returns the cost of traversing an arc (i,j) in a given solution assignment. It is the cost c_{ij} for a solution to have arc (feature) (i,j). +i is given by the index +of the IntVar variable corresponding to node i and next is the node index corresponding to node j. For the BinaryGuidedLocalSearch, this method is defined as:

      +
      int64 AssignmentPenalty(const Assignment& assignment,
      +                                          int index, int64 next) {
      +  return objective_function_->Run(index, next);
      +}
      +
      +
      +

      This cost is the same for all vehicles. In the case of the TernaryGuidedLocalSearch class, we need to take the type of vehicle traversing the arc (i,j) into account. +We added a reference to a given Assignment assignment to induce from this solution assignment what the type of vehicle +traversing arc (i,j) is. The type of vehicle traversing from node i is given by the secondary_vars_[i] variable:

      +
      int64 AssignmentPenalty(const Assignment& assignment,
      +                                          int index, int64 next) {
      +  return objective_function_->Run(index, next,
      +                       assignment.Value(secondary_vars_[index]));
      +}
      +
      +
      +
    • +
    • EvaluateElementValue() evaluates the penalized value of a given arc (i,j). It does so by using a shortcut to Assignment::IntContainers instead of Assignments and IntVarElements +instead of IntVars for efficiency. It also tests if a node is part of a solution. In the Routing Library, one can disable a node, i.e. make this node disappear as it never existed. If the node is +not disabled, i.e. active, the penalized value is stored in a variable pointed to by penalty and the method returns true, otherwise it returns false.

      +

      Here is the implementation for the BinaryGuidedLocalSearch class:

      +
      bool EvaluateElementValue(
      +    const Assignment::IntContainer& container,
      +    int64 index,
      +    int* container_index,
      +    int64* penalty) {
      +const IntVarElement& element = container.Element(*container_index);
      +  if (element.Activated()) {
      +    *penalty = PenalizedValue(index, element.Value());
      +    return true;
      +  }
      +  return false;
      +}
      +
      +
      +

      The EvaluateElementValue() method is only used in the Evaluate() helper of the GuidedLocalSearch:

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      +22
      +23
      +24
      +25
      +26
      +27
      +28
      int64 Evaluate(const Assignment* delta,
      +               int64 current_penalty,
      +               const int64* const out_values,
      +               bool cache_delta_values) {
      +  int64 penalty = current_penalty;
      +  const Assignment::IntContainer& container =
      +                                       delta->IntVarContainer();
      +  const int size = container.Size();
      +  for (int i = 0; i < size; ++i) {
      +    const IntVarElement& new_element = container.Element(i);
      +    IntVar* var = new_element.Var();
      +    int64 index = -1;
      +    if (FindCopy(indices_, var, &index)) {
      +      penalty -= out_values[index];
      +      int64 new_penalty = 0;
      +      if (EvaluateElementValue(container,
      +                               index,
      +                               &i,
      +                               &new_penalty)) {
      +        penalty += new_penalty;
      +        if (cache_delta_values) {
      +          delta_cache_[index] = new_penalty;
      +        }
      +      }
      +    }
      +  }
      +  return penalty;
      +}
      +
      +
      +

      This method updates the penalty of the whole solution given by a delta Assignment and is only called in AcceptDelta(). Recall that this delta is the difference between the last accepted solution x_i of the Local Search +and the candidate solution we are currently testing. We will not go into all the details. Just notice how the penalized value (variable penalty) is updated on lines 14 and 20.

      +
    • +
    • MakeElementPenalty() returns an IntExpr (pointer) to an Element expression (pointer) that can be casted to an IntVar (pointer). We use these variables to +compute the penalized part of the augmented objective function in such a way that we can add constraints with this expression.

      +

      For the BinaryGuidedLocalSearch the Element variable is computed as follows:

      +
      IntExpr* MakeElementPenalty(int index) {
      +  return solver()->MakeElement(
      +  NewPermanentCallback(this,
      +                       &BinaryGuidedLocalSearch::PenalizedValue,
      +                       static_cast<int64>(index)),
      +  vars_[index]);
      +}
      +
      +
      +

      In MakeElementPenalty(), NewPermanentCallback() with its second parameter static_cast<int64>(index) sets the first parameter of PenalizedValue() to index, i.e. we use +a callback that returns the cost associated to have an arc outgoing from node i in a solution. The generated expression ensures that we compute the right penalized value for a given solution.

      +
    • +
    +

    Let’s now review the implemented SearchMonitor callbacks for the GuidedLocalSearch class. The chosen order of presentation is pedagogical. Remember that the code is generic and is used for the 2- and 3-indices versions.

    +
    +
    +

    7.6.4.4. EnterSearch()

    +

    This is where you initialize your code before a search is launched.

    +
    void EnterSearch() {
    +  Metaheuristic::EnterSearch();
    +  penalized_objective_ = nullptr;
    +  assignment_penalized_value_ = 0;
    +  old_penalized_value_ = 0;
    +  ...
    +  penalties_->Reset();
    +}
    +
    +
    +

    This is a basic initialization. Of particular interest, notice how we set penalized_objective_ to nullptr. We do this each time there are no penalties and later we can test if (penalized_objective_ != nullptr).

    +
    +
    +

    7.6.4.5. LocalOptimum()

    +

    The LocalOptimum() method is called whenever a nested Local Search has finished. If one SearchMonitor returns true in its LocalOptimum() callback, the Local Search is restarted and the search continues. +In this method, we penalize the features of the local optimum solution according to their utility. Recall that the feature used here is whether the solution traverses an arc (i,j) or not. +We use the utility function described earlier:

    +
    +

    u_{ij}(x) = \begin{cases} \frac{c_{ij}(x)}{1 + p_{ij}} &\text{if arc } (i,j) \text{ is used;} \\
+         0 & \text{otherwise}. \end{cases}

    +

    and penalize the most expensive used arcs (i,j) according to their utility.

    +

    Let’s recall the way the RL (Routing Library) encodes the traversing of an arc (i,j) in a solution with vars_[i] = j, i.e. from node i go to node j where vars_[i] denotes the IntVar variable corresponding to node i. If no arc is traversed from node i (for instance node i is an arrival depot or is not visited at all in a solution), RL’s convention is to set vars_[i] = i.

    +

    Because we only update the penalties in this callback, notice that the GLS is only triggered after a local optimum has been found.

    +

    We are now ready to read the code:

    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    bool LocalOptimum() {
    +  std::vector<std::pair<Arc, double> > utility(vars_.size());
    +  for (int i = 0; i < vars_.size(); ++i) {
    +    if (!assignment_.Bound(vars_[i])) {
    +      // Never synced with a solution, problem infeasible.
    +      return false;
    +    }
    +    const int64 var_value = assignment_.Value(vars_[i]);
    +    const int64 value =
    +    (var_value != i) ? AssignmentPenalty(assignment_, i, var_value) : 0;
    +    const Arc arc(i, var_value);
    +    const int64 penalty = penalties_->Value(arc);
    +    utility[i] = std::pair<Arc, double>(arc, value / (penalty + 1.0));
    +  }
    +  Comparator comparator;
    +  std::stable_sort(utility.begin(), utility.end(), comparator);
    +  int64 utility_value = utility[0].second;
    +  penalties_->Increment(utility[0].first);
    +  for (int i = 1; i < utility.size() &&
    +                              utility_value == utility[i].second; ++i) {
    +    penalties_->Increment(utility[i].first);
    +  }
    +  if (maximize_) {
    +    current_ = kint64min;
    +  } else {
    +    current_ = kint64max;
    +  }
    +  return true;
    +}
    +
    +
    +

    The method is divided in 3 sections: lines 2 to 14 to compute the utilities, lines 15 to 22 to penalize the arcs according to their utilities and finally lines 23 to 28 to reset the value of the +current_ variable that we use to bound our solutions in the Local Search.

    +

    In the first section (lines 2 to 14), we compute the utilities as follow. The utility of each variable vars_[i] is stored in the std::vector<std::pair<Arc, double> > utility array. +As you can read, we have to test if the solution if feasible, i.e. if each of its variable is bounded or not. This is done on lines 4 to 7. +For an arc (i,j) ((i, var_value)), we compute its cost value: 0 if the arc is not traversed in the solution or AssignmentPenalty(assignment_, i, var_value) otherwise, i.e. the cost +to traverse arc (i,j) in the solution. On line 13, the utility \frac{c_{ij}}{p_{ij} + 1} is computed for the outgoing arc (i,j).

    +

    In the second section (lines 15 to 22), we only penalize arcs with the highest utility. First, we sort the utilities in descending order with the help of our Comparator in lines 15 and 16. On lines +17 and 18, we penalize the arc with the highest utility. The for loop on lines 19 to 22, penalize only the arcs with the same utility (utility_value == utility[i].second).

    +

    The third section (lines 23 to 28) is by now no surprise. We reset the value of the current_ variable such that we can bound the solutions in the Local Search by a higher value than for instance +the value of the best solution: this allows the meta-heuristic to escape local optima.

    +
    +
    +

    7.6.4.6. AtSolution()

    +

    The AtSolution() method is called whenever a solution is found and accepted in the Local Search

    +
    bool AtSolution() {
    +  if (!Metaheuristic::AtSolution()) {
    +    return false;
    +  }
    +  if (penalized_objective_ != nullptr) {  // no move has been found
    +    current_ += penalized_objective_->Value();
    +  }
    +  assignment_.Store();
    +  return true;
    +}
    +
    +
    +

    We update the best solution (Metaheuristic::AtSolution()) and the augmented objective function g. This is done as follow: first we update the current_ variable with the current objective value (again in +Metaheuristic::AtSolution()) and then we add the “penalized part” \lambda \sum_{(i.j)} \left( I_{ij}(x) \cdot p_{ij} \cdot c_{ij}(x) \right) from penalized_objective_->Value(). We also store the current solution.

    +
    +
    +

    7.6.4.7. ApplyDecision()

    +

    The ApplyDecision() method is called when a Decision is about to be applied. This is the place to add the constraints.

    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    void ApplyDecision(Decision* const d) {
    +  if (d == solver()->balancing_decision()) {
    +    return;
    +  }
    +  std::vector<IntVar*> elements;
    +  assignment_penalized_value_ = 0;
    +  if (penalties_->HasValues()) {
    +    for (int i = 0; i < vars_.size(); ++i) {
    +      IntExpr* expr = MakeElementPenalty(i);
    +      elements.push_back(expr->Var());
    +      const int64 penalty = AssignmentElementPenalty(assignment_, i);
    +      current_penalized_values_[i] = penalty;
    +      delta_cache_[i] = penalty;
    +      assignment_penalized_value_ += penalty;
    +    }
    +    old_penalized_value_ = assignment_penalized_value_;
    +    incremental_ = false;
    +    penalized_objective_ = solver()->MakeSum(elements)->Var();
    +    if (maximize_) {
    +      IntExpr* min_pen_exp =
    +      solver()->MakeDifference(current_ + step_, penalized_objective_);
    +      IntVar* min_exp =
    +                   solver()->MakeMin(min_pen_exp, best_ + step_)->Var();
    +      solver()->AddConstraint(
    +          solver()->MakeGreaterOrEqual(objective_, min_exp));
    +    } else {
    +      IntExpr* max_pen_exp =
    +      solver()->MakeDifference(current_ - step_, penalized_objective_);
    +      IntVar* max_exp =
    +                   solver()->MakeMax(max_pen_exp, best_ - step_)->Var();
    +      solver()->AddConstraint(solver()
    +                                ->MakeLessOrEqual(objective_, max_exp));
    +    }
    +  } else {
    +    penalized_objective_ = nullptr;
    +    if (maximize_) {
    +      const int64 bound =
    +                   (current_ > kint64min) ? current_ + step_ : current_;
    +      objective_->SetMin(bound);
    +    } else {
    +      const int64 bound =
    +                   (current_ < kint64max) ? current_ - step_ : current_;
    +      objective_->SetMax(bound);
    +    }
    +  }
    +}
    +
    +
    +

    Basically, this method adds the following constraint:

    +
      +
    • +
      when minimizing:
      +
      +
      objective <= Max(current penalized cost - penalized_objective - step,
      +

      best solution cost - step)

      +
      +
      +
      +
      +
    • +
    • +
      when maximizing:
      +
      +
      objective >= Min(current penalized cost - penalized_objective + step,
      +

      best solution cost + step)

      +
      +
      +
      +
      +
    • +
    +

    where “current penalized cost” is the augmented objective function value g(x) of the current solution x and “penalized_objective” - despite its name - corresponds to the penalized part of the +augmented objective function but expressed as an IntExpr.

    +

    Let’s dig into the code. As usual, we have to disregard the BalancingDecision on lines 2 to 4. Then we test if we have penalties on line 7. If not (lines 34 to 45), we simply add - in case of minimization - +the constraint objective <= current_ - step_ but we do it like an ObjectiveVar by modifying the upper bound on the domain of the objective variable. This avoids one more constraint and is perfectly in line with our aspiration criterion to accept better solution.

    +

    The test penalties_->HasValues() on line 7 is true if there is at least one arc with a positive penalty.

    +

    If there is one or more penalties, we enter the code on the lines 8 to 32. For each arc (i,j) ((i, assignment_.Value(vars_[i]))) , we create an Element expression corresponding to the Element constraint for the corresponding penalty on line 9. All these Element expressions are collected into a sum stored in the variable penalized_objective_ on line 18. Lines 11 to 14 compute and store the +penalized part of the augmented objective function individually for each node. We skip lines 16 and 17 as they update variables to use with the deltas. Finally, we add the constraint mentioned right after the code in lines 19 to 33. Notice that the part “objective <= current penalized cost - penalized_objective - step” of this constraint for the current solution reduces to “objective <= objective - step” and that the second part allows us to accept better solutions (aspiration criterion).

    +
    +
    +

    7.6.4.8. AcceptDelta()

    +

    This meta-heuristic is coded efficiently and uses the delta and deltadelta of the LocalSearchOperators. A quick reminder:

    +
      +
    • delta: the difference between the initial solution that defines the neighborhood and the current candidate solution.
    • +
    • deltadelta: the difference between the current candidate solution and the previous candidate solution. We say that the LocalSearchOperator is incremental.
    • +
    +

    The AcceptDelta() method of a SearchMonitor can accept or refuse a candidate solution. It is filtering the solutions and the result of this callback in the main Local Search algorithm +(see the sub-section The basic Local Search algorithm and the callback hooks for the SearchMonitors) is stored in a variable that has a very interesting name: meta_heuristics_filter.

    +

    The AcceptDelta() callback from the GuidedLocalSearch class computes the penalized value corresponding to the deltas and modifies their objective bound accordingly.

    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    bool AcceptDelta(Assignment* delta, Assignment* deltadelta) {
    +  if ((delta != nullptr || deltadelta != nullptr) &&
    +                                              penalties_->HasValues()) {
    +    int64 penalty = 0;
    +    if (!deltadelta->Empty()) {
    +      if (!incremental_) {
    +        penalty = Evaluate(delta,
    +                           assignment_penalized_value_,
    +                           current_penalized_values_.get(),
    +                           true);
    +      } else {
    +        penalty = Evaluate(deltadelta,
    +                           old_penalized_value_,
    +                           delta_cache_.get(),
    +                           true);
    +      }
    +      incremental_ = true;
    +    } else {
    +      if (incremental_) {
    +        for (int i = 0; i < vars_.size(); ++i) {
    +          delta_cache_[i] = current_penalized_values_[i];
    +        }
    +        old_penalized_value_ = assignment_penalized_value_;
    +      }
    +      incremental_ = false;
    +      penalty = Evaluate(delta,
    +                         assignment_penalized_value_,
    +                         current_penalized_values_.get(),
    +                         false);
    +    }
    +    old_penalized_value_ = penalty;
    +    if (!delta->HasObjective()) {
    +      delta->AddObjective(objective_);
    +    }
    +    if (delta->Objective() == objective_) {
    +      if (maximize_) {
    +        delta->SetObjectiveMin(
    +          std::max(std::min(current_ + step_ - penalty, best_ + step_),
    +                delta->ObjectiveMin()));
    +      } else {
    +        delta->SetObjectiveMax(
    +          std::min(std::max(current_ - step_ - penalty, best_ - step_),
    +                delta->ObjectiveMax()));
    +      }
    +    }
    +  }
    +  return true;
    +}
    +
    +
    +

    This method returns true on line 47 as it accepts every delta. The whole update can only be applied if at least a delta is present and if penalties exist. This is precisely the test on lines 2 and 3. +The code on lines 4 to 31 updates the penalized value of the candidate solution. The code is a little bit intricate because it has to be generic: we test the presence of the deltadelta and delta data structures and update the incremental_ parameter accordingly. When then use the best (aka most efficient) method to update this penalized value with a call to Evaluate(). On lines 35 to 45, we update +the bound of delta: this can speed up the process to accept or reject this candidate solution.

    +
    +
    +
    +

    7.6.5. The real classes

    +

    GuidedLocalSearch classes come in two flavors:

    +
      +
    • BinaryGuidedLocalSearch:
    • +
    • TernaryGuidedLocalSearch:
    • +
    +
    +

    7.6.5.1. BinaryGuidedLocalSearch

    +

    The BinaryGuidedLocalSearch class is used for Routing Problems where the traversing of an edge doesn’t depend on the type of vehicles, i.e. the cost is the same for all vehicles.

    +

    Here is the constructor:

    +
    BinaryGuidedLocalSearch::BinaryGuidedLocalSearch(
    +    Solver* const solver,
    +    IntVar* const objective,
    +    Solver::IndexEvaluator2* objective_function,
    +    bool maximize,
    +    int64 step,
    +    const std::vector<IntVar*>& vars,
    +    double penalty_factor)
    +    : GuidedLocalSearch(solver,
    +                        objective,
    +                        maximize,
    +                        step,
    +                        vars,
    +                        penalty_factor),
    +      objective_function_(objective_function) {
    +  objective_function_->CheckIsRepeatable();
    +}
    +
    +
    +

    The variables vars are the main variables corresponding to the nodes. The objective function is a callback that takes two int64 and returns an int64. Basically, it’s the cost of traversing +the arc (i,j).

    +

    The corresponding factory method is:

    +
    SearchMonitor* Solver::MakeGuidedLocalSearch(
    +    bool maximize,
    +    IntVar* const objective,
    +    ResultCallback2<int64, int64, int64>* objective_function,
    +    int64 step,
    +    const std::vector<IntVar*>& vars,
    +    double penalty_factor) {
    +  return RevAlloc(new BinaryGuidedLocalSearch(this,
    +                                              objective,
    +                                              objective_function,
    +                                              maximize,
    +                                              step,
    +                                              vars,
    +                                              penalty_factor));
    +}
    +
    +
    +
    +
    +

    7.6.5.2. TernaryGuidedLocalSearch

    +

    This version was especially made to deal with heterogeneous costs for different vehicles in the Routing Library: the cost of an arc also depends on the +vehicle used. At the initialization of the Routing Solver, the GuidedLocalSearch meta-heuristic is created as follow:

    +
    ...
    +switch (metaheuristic) {
    +  case ROUTING_GUIDED_LOCAL_SEARCH:
    +    if (CostsAreHomogeneousAcrossVehicles()) {
    +      optimize = solver_->MakeGuidedLocalSearch(
    +        false, cost_,
    +        NewPermanentCallback(this, &RoutingModel::GetHomogeneousCost),
    +        FLAGS_routing_optimization_step, nexts_,
    +        FLAGS_routing_guided_local_search_lambda_coefficient);
    +    } else {
    +      optimize = solver_->MakeGuidedLocalSearch(
    +        false, cost_,
    +        NewPermanentCallback(this, &RoutingModel::GetArcCostForVehicle),
    +        FLAGS_routing_optimization_step, nexts_, vehicle_vars_,
    +        FLAGS_routing_guided_local_search_lambda_coefficient);
    +    }
    +    break;
    +    ...
    +}
    +
    +
    +

    If the costs are the same for all vehicles, we use the int64 RoutingModel::GetHomogeneousCost(int64 i, int64 j) costs. +This method takes two int64: the index of the first node i and the index of the second node j. If on the contrary, the costs depend on the vehicle traversing an arc (i, j), we use the +int64 RoutingModel::GetArcCostForVehicle(int64 i, int64 j, int64 k) costs: the third int64 k corresponds to the index of the vehicle type used to traverse the arc (i, j).

    +

    The corresponding factory method is:

    +
    SearchMonitor* Solver::MakeGuidedLocalSearch(
    +    bool maximize,
    +    IntVar* const objective,
    +    ResultCallback3<int64, int64, int64, int64>* objective_function,
    +    int64 step,
    +    const std::vector<IntVar*>& vars,
    +    const std::vector<IntVar*>& secondary_vars,
    +    double penalty_factor) {
    +  return RevAlloc(new TernaryGuidedLocalSearch(this,
    +                                               objective,
    +                                               objective_function,
    +                                               maximize,
    +                                               step,
    +                                               vars,
    +                                               secondary_vars,
    +                                               penalty_factor));
    +}
    +
    +
    +

    The secondary secondary_vars variables are simply the variables corresponding to the vehicles.

    +
    +
    +
    +

    7.6.6. Guidelines to write your own GLS

    +

    GLS is a good meta-heuristic and it might be worth to give it a try to solve your problem.

    +

    As we have seen, our implementation of the GLS is heavily optimized: not only do we use GLS filtering (AcceptDelta()) but the implementation is especially tailored +for Routing Problems and objective functions of the form \sum_{(i,j)} c_{ij}. What if you have a problem that doesn’t fit into this canvas? Create your own version of the GLS!

    +

    We give you some hints on how to do that in this sub-section.

    +

    First, you have to change the call to a 2-indices or 3-indices callbacks to compute the objective function value.

    +

    Second, if you look carefully at the code of the abstract GuidedLocalSearch class, +you’ll find that the only method that really depends on 2 indices is the AssignmentPenalty() method. This method is only used in the LocalOptimum() callback.

    +

    Third, you have to adapt all 2- and 3-indices data structures such as for instance the GuidedLocalSearchPenalties classes.

    +

    Finally, you have to decide if you need GLS filtering or not.

    +

    All in all, the GuidedLocalSearch, BinaryGuidedLocalSearch, TernaryGuidedLocalSearch and GuidedLocalSearchPenalties, GuidedLocalSearchPenaltiesTable, GuidedLocalSearchPenaltiesMap classes +give you a good example on how to implement your own GLS.

    +

    Footnotes

    + + + + +
    [1]There is also a version with 3 indices i, j and k where the cost function returns the cost of traversing an arc (i,j) with a vehicle k, i.e. +the cost of traversing an arc depends on the type of vehicles used. Read on.
    + + + + + +
    [2]

    See the sections The model behind the scene: overview and The Routing Library (RL) to understand the juicy details. We omit these details here as they are not important to understand the GLS algorithm.

    +
    + + + + + +
    [3]The hash_map data structure is compiler dependent but it is exactly what its name says: a hash map.
    + + + + + +
    [4]This is a very common idiom in C++. Not only does it allow to construct more robust code (you can use functions and/or classes) and use the STL +(Standard Template Library) but it also allows you to use states (variables) and most compilers can do some tricks to speed up the code. See http://en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B for more.
    -
    -

    7.7.3. First results

    -


























    -


























    @@ -144,16 +951,37 @@ Search: title="previous chapter">7. Meta-heuristics: several previous problems

    Previous section

    7.6. Simulated annealing (SA)

    + title="previous chapter">7.5. Simulated Annealing (SA)

    Next section

    -

    7.8. Variable Neigborhood Search (VNS)

    +

    7.7. Large neighborhood search (LNS): the Job-Shop Problem

    Current section

    diff --git a/documentation/user_manual/manual/metaheuristics/SA.html b/documentation/user_manual/manual/metaheuristics/SA.html index 4b075f16c4..01adac51d9 100644 --- a/documentation/user_manual/manual/metaheuristics/SA.html +++ b/documentation/user_manual/manual/metaheuristics/SA.html @@ -8,7 +8,7 @@ - 7.6. Simulated annealing (SA) — or-tools User's Manual + 7.5. Simulated Annealing (SA) — or-tools User's Manual @@ -28,8 +28,8 @@ - - + + @@ -231,7 +231,7 @@ Search:

    Current chapter

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="previous chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    Previous section

    3.2. The Golomb ruler problem and a first model

    @@ -264,11 +264,11 @@ Search: previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • diff --git a/documentation/user_manual/manual/objectives/golomb_first_model.html b/documentation/user_manual/manual/objectives/golomb_first_model.html index 629479aad9..d8fdf77966 100644 --- a/documentation/user_manual/manual/objectives/golomb_first_model.html +++ b/documentation/user_manual/manual/objectives/golomb_first_model.html @@ -27,7 +27,7 @@ - + @@ -45,7 +45,7 @@ previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • @@ -299,7 +299,7 @@ Search:

    Current chapter

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="previous chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    Previous section

    3.1. Objective functions and how to compare search strategies

    @@ -337,11 +337,11 @@ Search: previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • diff --git a/documentation/user_manual/manual/objectives/objective_functions.html b/documentation/user_manual/manual/objectives/objective_functions.html index 003ff4b122..0eaddcc7b3 100644 --- a/documentation/user_manual/manual/objectives/objective_functions.html +++ b/documentation/user_manual/manual/objectives/objective_functions.html @@ -27,9 +27,9 @@ - + - + @@ -159,10 +159,10 @@ Search:

    Current chapter

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="previous chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    Previous section

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="previous chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    Next section

    3.2. The Golomb ruler problem and a first model

    @@ -180,14 +180,14 @@ Search: next |
  • - previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • diff --git a/documentation/user_manual/manual/objectives/optimization_how.html b/documentation/user_manual/manual/objectives/optimization_how.html index 9735cd09c3..f35765267b 100644 --- a/documentation/user_manual/manual/objectives/optimization_how.html +++ b/documentation/user_manual/manual/objectives/optimization_how.html @@ -27,7 +27,7 @@ - + @@ -45,7 +45,7 @@ previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • @@ -164,7 +164,7 @@ Search:

    Current chapter

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="previous chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    Previous section

    3.8. How to tighten the model?

    @@ -188,11 +188,11 @@ Search: previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • diff --git a/documentation/user_manual/manual/objectives/second_implementation.html b/documentation/user_manual/manual/objectives/second_implementation.html index 89083b40e1..a852561317 100644 --- a/documentation/user_manual/manual/objectives/second_implementation.html +++ b/documentation/user_manual/manual/objectives/second_implementation.html @@ -27,7 +27,7 @@ - + @@ -45,7 +45,7 @@ previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • @@ -366,7 +366,7 @@ Search:

    Current chapter

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="previous chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    Previous section

    3.5. Some global statistics about the search and how to limit the search

    @@ -400,11 +400,11 @@ Search: previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • diff --git a/documentation/user_manual/manual/objectives/summary.html b/documentation/user_manual/manual/objectives/summary.html index cde6d814e4..d8ae6214d3 100644 --- a/documentation/user_manual/manual/objectives/summary.html +++ b/documentation/user_manual/manual/objectives/summary.html @@ -27,7 +27,7 @@ - + @@ -45,7 +45,7 @@ previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • @@ -149,7 +149,7 @@ Search:

    Current chapter

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="previous chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    Previous section

    3.9. How does the solver optimize?

    @@ -173,11 +173,11 @@ Search: previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • diff --git a/documentation/user_manual/manual/objectives/third_implementation.html b/documentation/user_manual/manual/objectives/third_implementation.html index a3a87f9174..48598c5c7e 100644 --- a/documentation/user_manual/manual/objectives/third_implementation.html +++ b/documentation/user_manual/manual/objectives/third_implementation.html @@ -27,7 +27,7 @@ - + @@ -45,7 +45,7 @@ previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • @@ -198,7 +198,7 @@ Search:

    Current chapter

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="previous chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    Previous section

    3.6. A second model and its implementation

    @@ -222,11 +222,11 @@ Search: previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • diff --git a/documentation/user_manual/manual/objectives/tighten_model.html b/documentation/user_manual/manual/objectives/tighten_model.html index 82135009c5..db32bbfb0b 100644 --- a/documentation/user_manual/manual/objectives/tighten_model.html +++ b/documentation/user_manual/manual/objectives/tighten_model.html @@ -27,7 +27,7 @@ - + @@ -45,7 +45,7 @@ previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • @@ -340,7 +340,7 @@ Search:

    Current chapter

    3. Using objectives in constraint programming: the Golomb ruler problem

    + title="previous chapter">3. Using objectives in constraint programming: the Golomb Ruler Problem

    Previous section

    3.7. A third model and its implementation

    @@ -373,11 +373,11 @@ Search: previous |
  • or-tools User's Manual »
  • -
  • 3. Using objectives in constraint programming: the Golomb ruler problem »
  • +
  • 3. Using objectives in constraint programming: the Golomb Ruler Problem »
  • diff --git a/documentation/user_manual/manual/reification.html b/documentation/user_manual/manual/reification.html index 383ed176f5..136e0ecb43 100644 --- a/documentation/user_manual/manual/reification.html +++ b/documentation/user_manual/manual/reification.html @@ -196,7 +196,7 @@ Search: diff --git a/documentation/user_manual/manual/reification/reification.html b/documentation/user_manual/manual/reification/reification.html index eab9450cfa..5e2bc3b9b3 100644 --- a/documentation/user_manual/manual/reification/reification.html +++ b/documentation/user_manual/manual/reification/reification.html @@ -161,7 +161,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives.html b/documentation/user_manual/manual/search_primitives.html index 0dd4589f17..20570d7df7 100644 --- a/documentation/user_manual/manual/search_primitives.html +++ b/documentation/user_manual/manual/search_primitives.html @@ -301,7 +301,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives/basic_model_implementation.html b/documentation/user_manual/manual/search_primitives/basic_model_implementation.html index d738f4db25..b4079d128c 100644 --- a/documentation/user_manual/manual/search_primitives/basic_model_implementation.html +++ b/documentation/user_manual/manual/search_primitives/basic_model_implementation.html @@ -508,7 +508,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives/basic_working_phases.html b/documentation/user_manual/manual/search_primitives/basic_working_phases.html index eaf12fe0d2..9923cce0c8 100644 --- a/documentation/user_manual/manual/search_primitives/basic_working_phases.html +++ b/documentation/user_manual/manual/search_primitives/basic_working_phases.html @@ -603,7 +603,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives/basic_working_search_algorithm.html b/documentation/user_manual/manual/search_primitives/basic_working_search_algorithm.html index afc44072b6..575c59c37c 100644 --- a/documentation/user_manual/manual/search_primitives/basic_working_search_algorithm.html +++ b/documentation/user_manual/manual/search_primitives/basic_working_search_algorithm.html @@ -932,7 +932,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives/breaking_symmetry.html b/documentation/user_manual/manual/search_primitives/breaking_symmetry.html index af798f3486..252e4c4897 100644 --- a/documentation/user_manual/manual/search_primitives/breaking_symmetry.html +++ b/documentation/user_manual/manual/search_primitives/breaking_symmetry.html @@ -452,7 +452,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives/cpviz.html b/documentation/user_manual/manual/search_primitives/cpviz.html index fc447ed0f0..56ad2f29eb 100644 --- a/documentation/user_manual/manual/search_primitives/cpviz.html +++ b/documentation/user_manual/manual/search_primitives/cpviz.html @@ -722,7 +722,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives/customized_search_primitives.html b/documentation/user_manual/manual/search_primitives/customized_search_primitives.html index fe2f972a53..f7fd399be5 100644 --- a/documentation/user_manual/manual/search_primitives/customized_search_primitives.html +++ b/documentation/user_manual/manual/search_primitives/customized_search_primitives.html @@ -844,7 +844,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives/nqueens.html b/documentation/user_manual/manual/search_primitives/nqueens.html index 9e3f044f8e..260025e4d1 100644 --- a/documentation/user_manual/manual/search_primitives/nqueens.html +++ b/documentation/user_manual/manual/search_primitives/nqueens.html @@ -404,7 +404,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives/out_of_the_box_search_primitives.html b/documentation/user_manual/manual/search_primitives/out_of_the_box_search_primitives.html index 8b02c1de6d..4091b8b928 100644 --- a/documentation/user_manual/manual/search_primitives/out_of_the_box_search_primitives.html +++ b/documentation/user_manual/manual/search_primitives/out_of_the_box_search_primitives.html @@ -351,7 +351,7 @@ Search: diff --git a/documentation/user_manual/manual/search_primitives/summary.html b/documentation/user_manual/manual/search_primitives/summary.html index 08eb113490..e01e10274d 100644 --- a/documentation/user_manual/manual/search_primitives/summary.html +++ b/documentation/user_manual/manual/search_primitives/summary.html @@ -313,7 +313,7 @@ Search: diff --git a/documentation/user_manual/manual/tsp/first_tsp_implementation.html b/documentation/user_manual/manual/tsp/first_tsp_implementation.html index 1235b4d840..fec6bea99a 100644 --- a/documentation/user_manual/manual/tsp/first_tsp_implementation.html +++ b/documentation/user_manual/manual/tsp/first_tsp_implementation.html @@ -29,7 +29,7 @@ - + diff --git a/documentation/user_manual/manual/tsp/first_tsptw_implementation.html b/documentation/user_manual/manual/tsp/first_tsptw_implementation.html index 9286058c6d..9df706f623 100644 --- a/documentation/user_manual/manual/tsp/first_tsptw_implementation.html +++ b/documentation/user_manual/manual/tsp/first_tsptw_implementation.html @@ -349,7 +349,7 @@ Search: diff --git a/documentation/user_manual/manual/tsp/model_behind_scenes.html b/documentation/user_manual/manual/tsp/model_behind_scenes.html index fc896ad7e0..84eaf14207 100644 --- a/documentation/user_manual/manual/tsp/model_behind_scenes.html +++ b/documentation/user_manual/manual/tsp/model_behind_scenes.html @@ -28,7 +28,7 @@ - + @@ -39,7 +39,7 @@ index
  • - next |
  • 9.3. The Travelling Salesman Problem (TSP)

    Next section

    9.5. The model behind the scenes: overview

    + title="next chapter">9.5. The model behind the scene: overview

    Current section

    • 9.4. The model behind the scenes: the main decision variables
        @@ -441,7 +441,7 @@ Search: index
      • - next |
      • diff --git a/documentation/user_manual/manual/tsp/model_behind_scenes_overview.html b/documentation/user_manual/manual/tsp/model_behind_scenes_overview.html index 523df13b35..245dc2802f 100644 --- a/documentation/user_manual/manual/tsp/model_behind_scenes_overview.html +++ b/documentation/user_manual/manual/tsp/model_behind_scenes_overview.html @@ -8,7 +8,7 @@ - 9.5. The model behind the scenes: overview — or-tools User's Manual + 9.5. The model behind the scene: overview — or-tools User's Manual @@ -54,8 +54,8 @@
        -
        -

        9.5. The model behind the scenes: overview

        +
        +

        9.5. The model behind the scene: overview

        In this section, we give an overview of the main basic components of our model. Most of these components will be detailed in this chapter and the next two chapters. In the section The Routing Library (RL) of the chapter Under the hood, @@ -601,7 +601,7 @@ Search: title="next chapter">9.6. The TSP in or-tools

        Current section

        diff --git a/documentation/user_manual/manual/tsp/routing_library.html b/documentation/user_manual/manual/tsp/routing_library.html index 50c1c6e9f7..36623ea028 100644 --- a/documentation/user_manual/manual/tsp/routing_library.html +++ b/documentation/user_manual/manual/tsp/routing_library.html @@ -375,7 +375,7 @@ Search:
      diff --git a/documentation/user_manual/manual/tsp/tsp.html b/documentation/user_manual/manual/tsp/tsp.html index ee3a14b1e9..3100af9639 100644 --- a/documentation/user_manual/manual/tsp/tsp.html +++ b/documentation/user_manual/manual/tsp/tsp.html @@ -473,7 +473,7 @@ Search:
    diff --git a/documentation/user_manual/manual/tsp/tsptw.html b/documentation/user_manual/manual/tsp/tsptw.html index 0de18211c6..524ecfc588 100644 --- a/documentation/user_manual/manual/tsp/tsptw.html +++ b/documentation/user_manual/manual/tsp/tsptw.html @@ -677,7 +677,7 @@ Search: diff --git a/documentation/user_manual/manual/tsp/tsptw_summary.html b/documentation/user_manual/manual/tsp/tsptw_summary.html index 5ca503477c..06c24f62eb 100644 --- a/documentation/user_manual/manual/tsp/tsptw_summary.html +++ b/documentation/user_manual/manual/tsp/tsptw_summary.html @@ -161,7 +161,7 @@ Search: diff --git a/documentation/user_manual/manual/tsp/two_phases_approaches.html b/documentation/user_manual/manual/tsp/two_phases_approaches.html index 9b69d9cd5a..8658c964ab 100644 --- a/documentation/user_manual/manual/tsp/two_phases_approaches.html +++ b/documentation/user_manual/manual/tsp/two_phases_approaches.html @@ -258,7 +258,7 @@ Search: diff --git a/documentation/user_manual/manual/tsp/zoo_routing_problems.html b/documentation/user_manual/manual/tsp/zoo_routing_problems.html index 0c554c5ecc..5950923653 100644 --- a/documentation/user_manual/manual/tsp/zoo_routing_problems.html +++ b/documentation/user_manual/manual/tsp/zoo_routing_problems.html @@ -396,7 +396,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood.html b/documentation/user_manual/manual/under_the_hood.html index 0dcc952ad1..40e6254c55 100644 --- a/documentation/user_manual/manual/under_the_hood.html +++ b/documentation/user_manual/manual/under_the_hood.html @@ -28,7 +28,7 @@ - +
  • - previous |
  • or-tools User's Manual »
  • @@ -222,13 +222,13 @@ Search: next |
  • - previous |
  • or-tools User's Manual »
  • diff --git a/documentation/user_manual/manual/under_the_hood/assignment.html b/documentation/user_manual/manual/under_the_hood/assignment.html index d46d03c023..7aeae0a6c2 100644 --- a/documentation/user_manual/manual/under_the_hood/assignment.html +++ b/documentation/user_manual/manual/under_the_hood/assignment.html @@ -187,7 +187,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood/classes.html b/documentation/user_manual/manual/under_the_hood/classes.html index 5620afbb92..a050291869 100644 --- a/documentation/user_manual/manual/under_the_hood/classes.html +++ b/documentation/user_manual/manual/under_the_hood/classes.html @@ -185,7 +185,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood/conventions.html b/documentation/user_manual/manual/under_the_hood/conventions.html index 7b8103a618..2a9bff5a87 100644 --- a/documentation/user_manual/manual/under_the_hood/conventions.html +++ b/documentation/user_manual/manual/under_the_hood/conventions.html @@ -78,6 +78,9 @@

    13.2.2.4. Visitors

    +
    +
    +

    13.2.2.5. Listeners























































    @@ -176,6 +179,7 @@ Search:
  • 13.2.2.2. Caches
  • 13.2.2.3. Callbacks
  • 13.2.2.4. Visitors
  • +
  • 13.2.2.5. Listeners
  • @@ -203,7 +207,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood/files.html b/documentation/user_manual/manual/under_the_hood/files.html index 3eb39181df..598dc5d2d4 100644 --- a/documentation/user_manual/manual/under_the_hood/files.html +++ b/documentation/user_manual/manual/under_the_hood/files.html @@ -160,7 +160,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood/ls.html b/documentation/user_manual/manual/under_the_hood/ls.html index a9d86b7275..905894de53 100644 --- a/documentation/user_manual/manual/under_the_hood/ls.html +++ b/documentation/user_manual/manual/under_the_hood/ls.html @@ -159,7 +159,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood/metaheuristics.html b/documentation/user_manual/manual/under_the_hood/metaheuristics.html index dc436ae7e3..3b3c4da6ac 100644 --- a/documentation/user_manual/manual/under_the_hood/metaheuristics.html +++ b/documentation/user_manual/manual/under_the_hood/metaheuristics.html @@ -175,7 +175,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood/queue.html b/documentation/user_manual/manual/under_the_hood/queue.html index da19e021f1..869716d446 100644 --- a/documentation/user_manual/manual/under_the_hood/queue.html +++ b/documentation/user_manual/manual/under_the_hood/queue.html @@ -160,7 +160,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood/rl.html b/documentation/user_manual/manual/under_the_hood/rl.html index edd27ec3e8..f6b8b2655b 100644 --- a/documentation/user_manual/manual/under_the_hood/rl.html +++ b/documentation/user_manual/manual/under_the_hood/rl.html @@ -66,7 +66,7 @@

    Each node has a unique identifier of type RoutingModel::NodeIndex but we use internally a unique index of type int64 (see the section The model behind the scenes: the main decision variables). -The model is explained in broad terms in the section The model behind the scenes: overview.

    +The model is explained in broad terms in the section The model behind the scene: overview.

    All components are defined or accessible within the RoutingModel class. To use this class, include the mandatory constraint_solver/routing.h header.

    @@ -428,7 +428,7 @@ Search:
    diff --git a/documentation/user_manual/manual/under_the_hood/search_monitors.html b/documentation/user_manual/manual/under_the_hood/search_monitors.html index 533ca91413..351d107fde 100644 --- a/documentation/user_manual/manual/under_the_hood/search_monitors.html +++ b/documentation/user_manual/manual/under_the_hood/search_monitors.html @@ -245,7 +245,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood/summary.html b/documentation/user_manual/manual/under_the_hood/summary.html index ba3f1dcd9c..4bb3c69e72 100644 --- a/documentation/user_manual/manual/under_the_hood/summary.html +++ b/documentation/user_manual/manual/under_the_hood/summary.html @@ -150,7 +150,7 @@ Search: diff --git a/documentation/user_manual/manual/under_the_hood/trail.html b/documentation/user_manual/manual/under_the_hood/trail.html index be5a59d97f..6d95e4f005 100644 --- a/documentation/user_manual/manual/under_the_hood/trail.html +++ b/documentation/user_manual/manual/under_the_hood/trail.html @@ -160,7 +160,7 @@ Search: diff --git a/documentation/user_manual/manual/utilities.html b/documentation/user_manual/manual/utilities.html index ec820b6c6c..41fef7221f 100644 --- a/documentation/user_manual/manual/utilities.html +++ b/documentation/user_manual/manual/utilities.html @@ -54,6 +54,7 @@

    11. Utilities

    +

    Classes under scrutiny:

    diff --git a/documentation/user_manual/manual/utilities/asserting.html b/documentation/user_manual/manual/utilities/asserting.html index e4af1978a8..f4f2ba17d1 100644 --- a/documentation/user_manual/manual/utilities/asserting.html +++ b/documentation/user_manual/manual/utilities/asserting.html @@ -56,66 +56,6 @@

    11.2. Asserting

    -

    We provide several assert-like macros in the header base/logging.h.

    -

    Remember that the variable NDEBUG (“NO DEBUG”) is defined by the standard. By default, -the assert debugging mechanism defined in assert.h or the C++ equivalent cassert is on. You have -to explicitly turn it off by defining the variable NDEBUG.

    -

    Two types of assert-like macros are provided:

    -
      -
    • Debug-only checking and
    • -
    • Always-on checking.
    • -
    -

    Debug-only macros are only triggered in DEBUG mode (i.e. when the variable NDEBUG is not defined) and start with the letter -D. In NON DEBUG mode (the variable NDEBUG is defined), the code inside Debug-only macros vanishes. Always-on macros -are always on duty. For instance, DCHECK(x) is Debug-only while CHECK() is Always-on.

    -

    Here are the macros listed:

    - ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTests if
    (D)CHECK(x)(x)
    (D)CHECK_GE(x,y)(x) >= (y)
    (D)CHECK_LT(x, y)(x) < (y)
    (D)CHECK_GT(x, y)(x) > (y)
    (D)CHECK_LE(x, y)(x) <= (y)
    (D)CHECK_EQ(x, y)(x) == (y)
    (D)CHECK_NE(x, y)(x) != (y)
    -

    There is also the Always-on CHECK_NOTNULL(x) macro that tests if (x) != NULL.

    -
    -
    are also defined. Note that these macros are always functional. If you -prefer to use safeguards that vanish in the release code, use their -equivalent[1] starting with a D: DCHECK_LT(x, y), etc. and -compile with the NDEBUG variable set to 1.
    - - - - - -
    [1]There is no equivalent for CHECK_NOTNULL(x).
    -

    These macros are defined in the header logging.h:

    @@ -219,7 +159,7 @@ Search:
    diff --git a/documentation/user_manual/manual/utilities/debugging.html b/documentation/user_manual/manual/utilities/debugging.html index 451a0763c2..8c78241857 100644 --- a/documentation/user_manual/manual/utilities/debugging.html +++ b/documentation/user_manual/manual/utilities/debugging.html @@ -55,9 +55,30 @@
    -

    11.5. Debugging

    +

    11.5. Debugging

    +
    +

    11.5.1. The DebugString() method

    +
    +

    11.5.1.1. Naming variables

    +
    +
    +

    11.5.1.2. The convenient operator<<

    +

    Footnotes

    + + + + +
    [1]This is for obvious efficiency reasons.
    + + + + + +
    [2]So even if you pass an empty string (""), your variables will be named 0, 1, ... size-1.






















































    +
    +
    @@ -139,6 +160,18 @@ Search:

    Next section

    11.6. Serializing

    +

    Current section

    + +
    @@ -160,7 +193,7 @@ Search: diff --git a/documentation/user_manual/manual/utilities/flatzinc.html b/documentation/user_manual/manual/utilities/flatzinc.html index d9a6932887..7178be1b1b 100644 --- a/documentation/user_manual/manual/utilities/flatzinc.html +++ b/documentation/user_manual/manual/utilities/flatzinc.html @@ -160,7 +160,7 @@ Search: diff --git a/documentation/user_manual/manual/utilities/logging.html b/documentation/user_manual/manual/utilities/logging.html index cdd5e1e76e..c78d8763a2 100644 --- a/documentation/user_manual/manual/utilities/logging.html +++ b/documentation/user_manual/manual/utilities/logging.html @@ -55,69 +55,21 @@
    -

    11.1. Logging

    -

    [TO BE REREAD]

    -

    We provide very basic logging tools: macros replaced by some basic logging objects. They are defined -in the header base/logging.h.

    -

    LG or LOG(INFO) is always working. You can print messages to std:cerr like this

    -
    LG << "This is my important message with " << var << " pancakes.";
    -
    +

    11.1. Logging

    +
    +

    11.1.1. Logging based on severity

    -

    Of course, var must overwrite the << operator. The message is automatically followed by a \n -that adds a new line.

    -

    If you didn’t change the value of the gflags flag log_prefix to false, you’ll see the following message:

    -
    [20:47:47] my_file.cc:42: This is my important message with 3 pancakes.
    -
    +
    +

    11.1.2. Logging in DEBUG mode only

    -

    Your message is prefixed by the hour, the file name and the line number of the code source where your message was defined. -You can disable this prefix by setting log_prefix to false.

    -

    We provide different levels of logging:

    -
      -
    • First, depending on the severity:

      -
        -
      • INFO;
      • -
      • WARNING;
      • -
      • ERROR;
      • -
      • FATAL.
      • -
      -

      To use them, just write LOG(severity) as in:

      -
      LOG(FATAL) << "This message will kill you!";
      -
      +
      +

      11.1.3. Customized logging levels

      -

      For the moment, -INFO, ERROR and -WARNING are treated the same way. FATAL works as expected and the program aborts (calls abort()) after printing the message.

      -
    • -
    -
      -
    • Second, depending on the debug or release mode. When debugging, you can use DLOG(severity) with the same -levels (and the same results). If NDEBUG is defined, you are in release mode and DLOG(severity) doesn’t -do anything except for FATAL where it becomes a LOG(ERROR).
    • -
    -
      -
    • Finally, you can also use VLOG(level) with different levels. The higher the level, the more detailed -the information. -By default, the level is set to 0. You can change this by setting the right level value to the gflags flag -log_level.

      -

      So, if FLAGS_log_level = 1 the following message is printed:

      -
      VLOG(1) << "He, he, you can see me!";
      -
      +
      +

      11.1.4. Conditional logging

      -

      but not this one:

      -
      VLOG(2) << "This information is too detailed for you to see with
      -                                                  your log level...";
      -
      -
      -

      We rarely (understand never) go over level 4.

      -
    • -
    -

    There is also a conditional logging: LOG_IF(severity, condition) and for debugging DLOG_IF(severity, condition) -that vanishes when NDEBUG is defined.

    -
    -

    Warning

    -

    A little word of advice.

    -

    When logging is allowed, you create each time a logging object so this can be costly. -When logging is disallowed, you don’t pay anything.

    +
    +

    11.1.5. The logging classes

    @@ -201,6 +153,18 @@ Search:

    Next section

    11.2. Asserting

    +

    Current section

    + +
    @@ -222,7 +186,7 @@ Search:
    diff --git a/documentation/user_manual/manual/utilities/profiling.html b/documentation/user_manual/manual/utilities/profiling.html index c00c345d9c..078a441d73 100644 --- a/documentation/user_manual/manual/utilities/profiling.html +++ b/documentation/user_manual/manual/utilities/profiling.html @@ -56,6 +56,44 @@

    11.4. Profiling

    +

    // This struct holds all parameters for the Solver object. +// SolverParameters is only used by the Solver constructor to define solving +// parameters such as the trail compression or the profile level. +// Note this is for advanced users only. +struct SolverParameters {

    +

    enum ProfileLevel { NO_PROFILING, NORMAL_PROFILING };

    +
    +
    enum TraceLevel { NO_TRACE, NORMAL_TRACE }
    +
    +
    // Support for profiling propagation. LIGHT supports only a reduced
    +

    // version of the summary. COMPLETE supports the full version of the +// summary, as well as the csv export. +ProfileLevel profile_level;

    +

    // Support for full trace of propagation. +TraceLevel trace_level;

    +
    +
    DEFINE_bool(cp_trace_propagation, false,
    +
    “Trace propagation events (constraint and demon executions,” +” variable modifications).”);
    +
    +

    DEFINE_bool(cp_trace_search, false, “Trace search events”); +DEFINE_bool(cp_show_constraints, false,

    +
    +
    “show all constraints added to the solver.”);
    +
    +
    DEFINE_bool(cp_print_model, false,
    +
    “use PrintModelVisitor on model before solving.”);
    +
    DEFINE_bool(cp_model_stats, false,
    +
    “use StatisticsModelVisitor on model before solving.”);
    +
    +

    DEFINE_string(cp_export_file, “”, “Export model to file using CPModelProto.”); +DEFINE_bool(cp_no_solve, false, “Force failure at the beginning of a search.”); +DEFINE_string(cp_profile_file, “”, “Export profiling overview to file.”); +DEFINE_bool(cp_verbose_fail, false, “Verbose output when failing.”); +DEFINE_bool(cp_name_variables, false, “Force all variables to have names.”); +DEFINE_bool(cp_name_cast_variables, false,

    +
    +
    “Name variables casted from expressions”);






















































    @@ -160,7 +198,7 @@ Search:
    diff --git a/documentation/user_manual/manual/utilities/randomness.html b/documentation/user_manual/manual/utilities/randomness.html index 9264bea4b7..d7d0459fd9 100644 --- a/documentation/user_manual/manual/utilities/randomness.html +++ b/documentation/user_manual/manual/utilities/randomness.html @@ -160,7 +160,7 @@ Search: diff --git a/documentation/user_manual/manual/utilities/serializing.html b/documentation/user_manual/manual/utilities/serializing.html index 8db7db6584..a03ed12bc9 100644 --- a/documentation/user_manual/manual/utilities/serializing.html +++ b/documentation/user_manual/manual/utilities/serializing.html @@ -160,7 +160,7 @@ Search: diff --git a/documentation/user_manual/manual/utilities/timing.html b/documentation/user_manual/manual/utilities/timing.html index 05a670198d..831a084363 100644 --- a/documentation/user_manual/manual/utilities/timing.html +++ b/documentation/user_manual/manual/utilities/timing.html @@ -282,7 +282,7 @@ Search: diff --git a/documentation/user_manual/manual/utilities/visualizing.html b/documentation/user_manual/manual/utilities/visualizing.html index 64715c55f9..7a63abd103 100644 --- a/documentation/user_manual/manual/utilities/visualizing.html +++ b/documentation/user_manual/manual/utilities/visualizing.html @@ -56,8 +56,26 @@

    11.7. Visualizing

    +
    +

    11.7.1. Visualizing the model

    +
    +

    11.7.1.1. DebugString for small parts

    +
    +
    +

    11.7.1.2. ModelVisitors for the whole model

    +
    +
    + +
    @@ -139,6 +157,23 @@ Search:

    Next section

    11.8. Randomizing

    +

    Current section

    + +
    @@ -160,7 +195,7 @@ Search: diff --git a/documentation/user_manual/manual/vrp/cvrp.html b/documentation/user_manual/manual/vrp/cvrp.html index b0fd825348..1b2f249151 100644 --- a/documentation/user_manual/manual/vrp/cvrp.html +++ b/documentation/user_manual/manual/vrp/cvrp.html @@ -313,7 +313,7 @@ Search: diff --git a/documentation/user_manual/manual/vrp/cvrp_summary.html b/documentation/user_manual/manual/vrp/cvrp_summary.html index caa0dea922..4f8befb503 100644 --- a/documentation/user_manual/manual/vrp/cvrp_summary.html +++ b/documentation/user_manual/manual/vrp/cvrp_summary.html @@ -160,7 +160,7 @@ Search: diff --git a/documentation/user_manual/manual/vrp/first_cvrp_implementation.html b/documentation/user_manual/manual/vrp/first_cvrp_implementation.html index fb91784f2a..ec7500d89d 100644 --- a/documentation/user_manual/manual/vrp/first_cvrp_implementation.html +++ b/documentation/user_manual/manual/vrp/first_cvrp_implementation.html @@ -428,7 +428,7 @@ Search: diff --git a/documentation/user_manual/manual/vrp/first_vrp_implementation.html b/documentation/user_manual/manual/vrp/first_vrp_implementation.html index 66b9f42236..4d7b225240 100644 --- a/documentation/user_manual/manual/vrp/first_vrp_implementation.html +++ b/documentation/user_manual/manual/vrp/first_vrp_implementation.html @@ -395,7 +395,7 @@ Search: diff --git a/documentation/user_manual/manual/vrp/multi_depots.html b/documentation/user_manual/manual/vrp/multi_depots.html index 6b3cdb876e..5996a88eb6 100644 --- a/documentation/user_manual/manual/vrp/multi_depots.html +++ b/documentation/user_manual/manual/vrp/multi_depots.html @@ -310,7 +310,7 @@ Search: diff --git a/documentation/user_manual/manual/vrp/partial_routes.html b/documentation/user_manual/manual/vrp/partial_routes.html index 0dc98764c8..185d9e193b 100644 --- a/documentation/user_manual/manual/vrp/partial_routes.html +++ b/documentation/user_manual/manual/vrp/partial_routes.html @@ -489,7 +489,7 @@ Search: diff --git a/documentation/user_manual/manual/vrp/vrp.html b/documentation/user_manual/manual/vrp/vrp.html index 97acb922b4..ba980833a7 100644 --- a/documentation/user_manual/manual/vrp/vrp.html +++ b/documentation/user_manual/manual/vrp/vrp.html @@ -565,7 +565,7 @@ Search: