{"id":13696806,"url":"https://github.com/d-krupke/cpsat-primer","last_synced_at":"2025-04-06T16:15:30.893Z","repository":{"id":45030735,"uuid":"512378088","full_name":"d-krupke/cpsat-primer","owner":"d-krupke","description":"The CP-SAT Primer: Using and Understanding Google OR-Tools' CP-SAT Solver","archived":false,"fork":false,"pushed_at":"2024-10-24T19:01:13.000Z","size":22242,"stargazers_count":367,"open_issues_count":7,"forks_count":35,"subscribers_count":14,"default_branch":"main","last_synced_at":"2024-10-30T03:42:41.468Z","etag":null,"topics":["combinatorial-optimization","cp-sat","operations-research","optimization","ortools"],"latest_commit_sha":null,"homepage":"https://d-krupke.github.io/cpsat-primer/","language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc-by-4.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/d-krupke.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-10T08:00:40.000Z","updated_at":"2024-10-28T08:47:40.000Z","dependencies_parsed_at":"2024-04-03T15:56:39.222Z","dependency_job_id":"efc288d2-a349-44bc-9b7b-547799f9c544","html_url":"https://github.com/d-krupke/cpsat-primer","commit_stats":{"total_commits":445,"total_committers":12,"mean_commits":"37.083333333333336","dds":0.251685393258427,"last_synced_commit":"13e820707d957d34de1f0ad2980475893df50e98"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-krupke%2Fcpsat-primer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-krupke%2Fcpsat-primer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-krupke%2Fcpsat-primer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-krupke%2Fcpsat-primer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d-krupke","download_url":"https://codeload.github.com/d-krupke/cpsat-primer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247509238,"owners_count":20950232,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["combinatorial-optimization","cp-sat","operations-research","optimization","ortools"],"created_at":"2024-08-02T18:00:47.579Z","updated_at":"2025-04-06T16:15:30.855Z","avatar_url":"https://github.com/d-krupke.png","language":"Jupyter Notebook","funding_links":[],"categories":["Table of Contents"],"sub_categories":["Internal"],"readme":"*A book-style version of this primer is available at [https://d-krupke.github.io/cpsat-primer/](https://d-krupke.github.io/cpsat-primer/).*\n\n\u003c!-- This file was generated by the `build.py` script. Do not edit it manually. --\u003e\n\u003c!-- 00_intro.md --\u003e\n\u003c!-- EDIT THIS PART VIA 00_intro.md --\u003e\n\n# The CP-SAT Primer: Using and Understanding Google OR-Tools' CP-SAT Solver\n\n\n_By [Dominik Krupke](https://krupke.cc), TU Braunschweig, with contributions\nfrom Leon Lan, Michael Perk, and\n[others](https://github.com/d-krupke/cpsat-primer/graphs/contributors)._\n\n\u003c!-- Introduction Paragraph ---\u003e\n\nMany [combinatorially difficult](https://en.wikipedia.org/wiki/NP-hardness)\noptimization problems can, despite their proven theoretical hardness, be solved\nreasonably well in practice. The most successful approach is to use\n[Mixed Integer Linear Programming](https://en.wikipedia.org/wiki/Integer_programming)\n(MIP) to model the problem and then use a solver to find a solution. The most\nsuccessful solvers for MIPs are, e.g., [Gurobi](https://www.gurobi.com/),\n[CPLEX](https://www.ibm.com/analytics/cplex-optimizer),\n[COPT Cardinal Solver](https://www.copt.de/), and\n[FICO Xpress Optimization](https://www.fico.com/en/products/fico-xpress-optimization),\nwhich are all commercial and expensive (though, mostly free for academics).\nThere are also some open source solvers (e.g., [SCIP](https://www.scipopt.org/)\nand [HiGHS](https://highs.dev/)), but they are often not as powerful as the\ncommercial ones (yet). However, even when investing in such a solver, the\nunderlying techniques\n([Branch and Bound](https://en.wikipedia.org/wiki/Branch_and_bound) \u0026\n[Cut](https://en.wikipedia.org/wiki/Branch_and_cut) on\n[Linear Relaxations](https://en.wikipedia.org/wiki/Linear_programming_relaxation))\nstruggle with some optimization problems, especially if the problem contains a\nlot of logical constraints that a solution has to satisfy. In this case, the\n[Constraint Programming](https://en.wikipedia.org/wiki/Constraint_programming)\n(CP) approach may be more successful. For Constraint Programming, there are many\nopen source solvers, but they usually do not scale as well as MIP-solvers and\nare worse in optimizing objective functions. While MIP-solvers are frequently\nable to optimize problems with hundreds of thousands of variables and\nconstraints, the classical CP-solvers often struggle with problems with more\nthan a few thousand variables and constraints. However, the relatively new\n[CP-SAT](https://developers.google.com/optimization/cp/cp_solver) of Google's\n[OR-Tools](https://github.com/google/or-tools/) suite shows to overcome many of\nthe weaknesses and provides a viable alternative to MIP-solvers, being\ncompetitive for many problems and sometimes even superior.\n\nAs a quick demonstration of CP-SAT's capabilities - particularly for those less\nfamiliar with optimization frameworks - let us solve an instance of the NP-hard\nKnapsack Problem. This classic optimization problem requires selecting a subset\nof items, each with a specific weight and value, to maximize the total value\nwithout exceeding a weight limit. Although a recursive algorithm is easy to\nimplement, 100 items yield approximately $2^{100} \\approx 10^{30}$ possible\nsolutions. Even with a supercomputer performing $10^{18}$ operations per second,\nit would take more than 31,000 years to evaluate all possibilities.\n\nHere is how you can solve it using CP-SAT:\n\n```python\nfrom ortools.sat.python import cp_model  # pip install -U ortools\n\n# Specifying the input\nweights = [395, 658, 113, 185, 336, 494, 294, 295, 256, 530, 311, 321, 602, 855, 209, 647, 520, 387, 743, 26, 54, 420, 667, 971, 171, 354, 962, 454, 589, 131, 342, 449, 648, 14, 201, 150, 602, 831, 941, 747, 444, 982, 732, 350, 683, 279, 667, 400, 441, 786, 309, 887, 189, 119, 209, 532, 461, 420, 14, 788, 691, 510, 961, 528, 538, 476, 49, 404, 761, 435, 729, 245, 204, 401, 347, 674, 75, 40, 882, 520, 692, 104, 512, 97, 713, 779, 224, 357, 193, 431, 442, 816, 920, 28, 143, 388, 23, 374, 905, 942]\nvalues = [71, 15, 100, 37, 77, 28, 71, 30, 40, 22, 28, 39, 43, 61, 57, 100, 28, 47, 32, 66, 79, 70, 86, 86, 22, 57, 29, 38, 83, 73, 91, 54, 61, 63, 45, 30, 51, 5, 83, 18, 72, 89, 27, 66, 43, 64, 22, 23, 22, 72, 10, 29, 59, 45, 65, 38, 22, 68, 23, 13, 45, 34, 63, 34, 38, 30, 82, 33, 64, 100, 26, 50, 66, 40, 85, 71, 54, 25, 100, 74, 96, 62, 58, 21, 35, 36, 91, 7, 19, 32, 77, 70, 23, 43, 78, 98, 30, 12, 76, 38]\ncapacity = 2000\n\n# Now we solve the problem\nmodel = cp_model.CpModel()\nxs = [model.new_bool_var(f\"x_{i}\") for i in range(len(weights))]\n\nmodel.add(sum(x * w for x, w in zip(xs, weights)) \u003c= capacity)\nmodel.maximize(sum(x * v for x, v in zip(xs, values)))\n\nsolver = cp_model.CpSolver()\nsolver.solve(model)\n\nprint(\"Optimal selection:\", [i for i, x in enumerate(xs) if solver.value(x)])\nprint(\"Total packed value:\", solver.objective_value)\n```\n\n```\nOptimal selection: [2, 14, 19, 20, 29, 33, 52, 53, 54, 58, 66, 72, 76, 77, 81, 86, 93, 94, 96]\nTotal packed value: 1161.0\n```\n\nHow long did CP-SAT take? On my machine, it found the provably best solution\nfrom $2^{100}$ possibilities in just 0.01 seconds. Feel free to try it on yours.\nCP-SAT does not evaluate all solutions; it uses advanced techniques to make\ndeductions and prune the search space. While more efficient approaches than a\nnaive recursive algorithm exist, matching CP-SAT’s performance would require\nsignificant time and effort. And this is just the beginning - CP-SAT can tackle\nmuch more complex problems, as we will see in this primer.\n\n\u003e [!TIP]\n\u003e\n\u003e Not convinced yet of why tools like CP-SAT are amazing? Maybe Marco Lübbecke\n\u003e can convince you in his 12-minute TEDx talk\n\u003e [Anything you can do I can do better](https://www.youtube.com/watch?v=Dc38La-Xvog)\n\u003e about mathematical optimization.\n\n### Content\n\nWhether you are from the MIP community seeking alternatives or CP-SAT is your\nfirst optimization solver, this book will guide you through the fundamentals of\nCP-SAT in the first part, demonstrating all its features. The second part will\nequip you with the skills needed to build and deploy optimization algorithms\nusing CP-SAT.\n\nThe first part introduces the fundamentals of CP-SAT, starting with a chapter on\ninstallation. This chapter guides you through setting up CP-SAT and outlines the\nnecessary hardware requirements. The next chapter provides a simple example of\nusing CP-SAT, explaining the mathematical notation and its approximation in\nPython with overloaded operators. You will then progress to basic modeling,\nlearning how to create variables, objectives, and fundamental constraints in\nCP-SAT.\n\nFollowing this, a chapter on advanced modeling will teach you how to handle\ncomplex constraints, such as circuit constraints and intervals, with practical\nexamples. Another chapter discusses specifying CP-SAT's behavior, including\nsetting time limits and using parallelization. You will also find a chapter on\ninterpreting CP-SAT logs, which helps you understand how well CP-SAT is managing\nyour problem. Additionally, there is an overview of the underlying techniques\nused in CP-SAT. The first part concludes with a chapter comparing CP-SAT with\nother optimization techniques and tools, providing a broader context.\n\nThe second part delves into more advanced topics, focusing on general skills\nlike coding patterns and benchmarking rather than specific CP-SAT features. A\nchapter on coding patterns offers basic design patterns for creating\nmaintainable algorithms with CP-SAT. Another chapter explains how to provide\nyour optimization algorithm as a service by building an optimization API. There\nis also a chapter on developing powerful heuristics using CP-SAT for\nparticularly difficult or large problems. The second part concludes with a\nchapter on benchmarking, offering guidance on how to scientifically benchmark\nyour model and interpret the results.\n\n### Target Audience\n\nI wrote this book for my computer science students at TU Braunschweig, and it is\nused as supplementary material in my algorithm engineering courses. Initially,\nwe focused on Mixed Integer Programming (MIP), with CP-SAT discussed as an\nalternative. However, we recently began using CP-SAT as the first optimization\nsolver due to its high-level interface, which is much easier for beginners to\ngrasp. Despite this shift, because MIP is more commonly used, the book includes\nnumerous comparisons to MIP. Thus, it is designed to be beginner-friendly while\nalso addressing the needs of MIP users seeking alternatives.\n\nUnlike other books aimed at mathematical optimization or operations research\nstudents, this one, aimed at computer science students, emphasizes coding over\nmathematics or business cases, providing a hands-on approach to learning\noptimization. The second part of the book can also be interesting for more\nadvanced users, providing content that I found missing in other books on\noptimization.\n\n### Table of Content\n\n**Part 1: The Basics**\n\n1. [Installation](#01-installation): Quick installation guide.\n2. [Example](#02-example): A short example, showing the usage of CP-SAT.\n3. [Basic Modeling](#04-modelling): An overview of variables, objectives, and\n   constraints.\n4. [Advanced Modeling](#04B-advanced-modelling): More complex constraints, such\n   as circuit constraints and intervals.\n5. [Parameters](#05-parameters): How to specify CP-SATs behavior, if needed.\n   Timelimits, hints, assumptions, parallelization, ...\n6. [Understanding the Log](#understanding-the-log): How to interpret the log\n7. [How does it work?](#07-under-the-hood): After we know what we can do with\n   CP-SAT, we look into how CP-SAT will do all these things.\n8. [Alternatives](#03-big-picture): An overview of the different optimization\n   techniques and tools available. Putting CP-SAT into context.\n\n**Part 2: Advanced Topics**\n\n7. [Coding Patterns](#06-coding-patterns): Basic design patterns for creating\n   maintainable algorithms.\n8. [(DRAFT) Building an Optimization API](#building_an_optimization_api) How to\n   build a scalable API for long running optimization jobs.\n9. [(DRAFT) CP-SAT vs. ML vs. QC](#chapters-machine-learning): A comparison of\n   CP-SAT with Machine Learning and Quantum Computing.\n10. [Large Neighborhood Search](#09-lns): The use of CP-SAT to create more\n    powerful heuristics.\n11. [Benchmarking your Model](#08-benchmarking): How to benchmark your model and\n    how to interpret the results.\n\n### Background\n\n\u003c!-- Background ---\u003e\n\nThis book assumes you are fluent in Python, and have already been introduced to\ncombinatorial optimization problems. In case you are just getting into\ncombinatorial optimization and are learning on your own, I recommend starting\nwith the free Coursera course,\n[Discrete Optimization](https://www.coursera.org/learn/discrete-optimization),\ntaught by Pascal Van Hentenryck and Carleton Coffrin. This course provides a\ncomprehensive introduction in a concise format.\n\nFor an engaging exploration of a classic problem within this domain,\n[In Pursuit of the Traveling Salesman by Bill Cook](https://press.princeton.edu/books/paperback/9780691163529/in-pursuit-of-the-traveling-salesman)\nis highly recommended. This book, along with this\n[YouTube talk](https://www.youtube.com/watch?v=5VjphFYQKj8) by the author that\nlasts about an hour, offers a practical case study of the well-known Traveling\nSalesman Problem. It not only introduces fundamental techniques but also delves\ninto the community and historical context of the field.\n\nAdditionally, the article\n[Mathematical Programming](https://www.gurobi.com/resources/math-programming-modeling-basics/)\nby CP-SAT's competitor Gurobi offers an insightful introduction to mathematical\nprogramming and modeling. In this context, the term \"Programming\" does not refer\nto coding; rather, it originates from an earlier usage of the word \"program\",\nwhich denoted a plan of action or a schedule. If this distinction is new to you,\nit is a strong indication that you would benefit from reading this article.\n\n\u003e **About the Lead Author:** [Dr. Dominik Krupke](https://krupke.cc) is a\n\u003e postdoctoral researcher with the\n\u003e [Algorithms Division](https://www.ibr.cs.tu-bs.de/alg) at TU Braunschweig. He\n\u003e specializes in practical solutions to NP-hard problems. Initially focused on\n\u003e theoretical computer science, he now applies his expertise to solve what was\n\u003e once deemed impossible, frequently with the help of CP-SAT. This primer on\n\u003e CP-SAT, first developed as course material for his students, has been extended\n\u003e in his spare time to cater to a wider audience.\n\u003e\n\u003e **Contributors:** This primer has been enriched by the contributions of\n\u003e [several individuals](https://github.com/d-krupke/cpsat-primer/graphs/contributors).\n\u003e Notably, Leon Lan played a key role in restructuring the content and offering\n\u003e critical feedback, while Michael Perk significantly enhanced the section on\n\u003e the reservoir constraint. I also extend my gratitude to all other contributors\n\u003e who identified and corrected errors, improved the text, and offered valuable\n\u003e insights.\n\n\u003e **Found a mistake?** Please open an issue or a pull request. You can also just\n\u003e write me a quick mail to `krupked@gmail.com`.\n\n\u003e **Want to contribute?** If you are interested in contributing, please open an\n\u003e issue or email me with a brief description of your proposal. We can then\n\u003e discuss the details. I welcome all assistance and am open to expanding the\n\u003e content. Contributors to any section or similar input will be recognized as\n\u003e coauthors.\n\n\u003e **Want to use/share this content?** This tutorial can be freely used under\n\u003e [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). Smaller parts can\n\u003e even be copied without any acknowledgement for non-commercial, educational\n\u003e purposes.\n\n\n---\n\n\u003c!-- This file was generated by the `build.py` script. Do not edit it manually. --\u003e\n\u003c!-- 01_installation.md --\u003e\n\u003c!-- EDIT THIS PART VIA 01_installation.md --\u003e\n\n# Part 1: The Basics\n\n\u003ca name=\"01-installation\"\u003e\u003c/a\u003e\n\n## Installation\n\nWe are using Python 3 in this primer and assume that you have a working Python 3\ninstallation as well as the basic knowledge to use it. There are also interfaces\nfor other languages, but Python 3 is, in my opinion, the most convenient one, as\nthe mathematical expressions in Python are very close to the mathematical\nnotation (allowing you to spot mathematical errors much faster). Only for huge\nmodels, you may need to use a compiled language such as C++ due to performance\nissues. For smaller models, you will not notice any performance difference.\n\nThe installation of CP-SAT, which is part of the OR-Tools package, is very easy\nand can be done via Python's package manager\n[pip](https://pip.pypa.io/en/stable/).\n\n```shell\npip3 install -U ortools\n```\n\nThis command will also update an existing installation of OR-Tools. As this tool\nis in active development, it is recommended to update it frequently. We actually\nencountered wrong behavior, i.e., bugs, in earlier versions that then have been\nfixed by updates (this was on some more advanced features, do not worry about\ncorrectness with basic usage).\n\nI personally like to use [Jupyter Notebooks](https://jupyter.org/) for\nexperimenting with CP-SAT.\n\n### What hardware do you need?\n\nIt is important to note that for CP-SAT usage, you do not need the capabilities\nof a supercomputer. A standard laptop is often sufficient for solving many\nproblems. The primary requirements are CPU power and memory bandwidth, with a\nGPU being unnecessary.\n\nIn terms of CPU power, the key is balancing the number of cores with the\nperformance of each individual core. CP-SAT leverages all available cores by\ndefault, implementing different strategies on each.\n[Depending on the number of cores, CP-SAT will behave differently](https://github.com/google/or-tools/blob/main/ortools/sat/docs/troubleshooting.md#improving-performance-with-multiple-workers).\nHowever, the effectiveness of these strategies can vary, and it is usually not\napparent which one will be most effective. A higher single-core performance\nmeans that your primary strategy will operate more swiftly. I recommend a\nminimum of 4 cores and 16GB of RAM.\n\nWhile CP-SAT is quite efficient in terms of memory usage, the amount of\navailable memory can still be a limiting factor in the size of problems you can\ntackle. When it came to setting up our lab for extensive benchmarking at TU\nBraunschweig, we faced a choice between desktop machines and more expensive\nworkstations or servers. We chose desktop machines equipped with AMD Ryzen 9\n7900 CPUs (Intel would be equally suitable) and 96GB of DDR5 RAM, managed using\nSlurm. This decision was driven by the fact that the performance gains from\nhigher-priced workstations or servers were relatively marginal compared to their\nsignificantly higher costs. When on the road, I am often still able to do stuff\nwith my old Intel Macbook Pro from 2018 with an i7 and only 16GB of RAM, but\nlarge models will overwhelm it. My workstation at home with AMD Ryzen 7 5700X\nand 32GB of RAM on the other hand rarely has any problems with the models I am\nworking on.\n\nFor further guidance, consider the\n[hardware recommendations for the Gurobi solver](https://support.gurobi.com/hc/en-us/articles/8172407217041-What-hardware-should-I-select-when-running-Gurobi-),\nwhich are likely to be similar. Since we frequently use Gurobi in addition to\nCP-SAT, our hardware choices were also influenced by their recommendations.\n\n\u003c!-- This file was generated by the `build.py` script. Do not edit it manually. --\u003e\n\u003c!-- 02_example.md --\u003e\n\u003c!-- EDIT THIS PART VIA 02_example.md --\u003e\n\n\u003ca name=\"02-example\"\u003e\u003c/a\u003e\n\n## A Simple Example\n\n\nBefore we dive into any internals, let us take a quick look at a simple\napplication of CP-SAT. This example is so simple that you could solve it by\nhand, but know that CP-SAT would (probably) be fine with you adding a thousand\n(maybe even ten- or hundred-thousand) variables and constraints more. The basic\nidea of using CP-SAT is, analogous to MIPs, to define an optimization problem in\nterms of variables, constraints, and objective function, and then let the solver\nfind a solution for it. We call such a formulation that can be understood by the\ncorresponding solver a _model_ for the problem. For people not familiar with\nthis\n[declarative approach](https://programiz.pro/resources/imperative-vs-declarative-programming/),\nyou can compare it to SQL, where you also just state what data you want, not how\nto get it. However, it is not purely declarative, because it can still make a\nhuge(!) difference how you model the problem and getting that right takes some\nexperience and understanding of the internals. You can still get lucky for\nsmaller problems (let us say a few hundred to thousands of variables) and obtain\noptimal solutions without having an idea of what is going on. The solvers can\nhandle more and more 'bad' problem models effectively with every year.\n\n\u003e [!NOTE]\n\u003e\n\u003e A **model** in mathematical programming refers to a mathematical description\n\u003e of a problem, consisting of variables, constraints, and optionally an\n\u003e objective function that can be understood by the corresponding solver class.\n\u003e _Modelling_ refers to transforming a problem (instance) into the corresponding\n\u003e framework, e.g., by making all constraints linear as required for Mixed\n\u003e Integer Linear Programming. Be aware that the\n\u003e [SAT](https://en.wikipedia.org/wiki/SAT_solver)-community uses the term\n\u003e _model_ to refer to a (feasible) variable assignment, i.e., solution of a\n\u003e SAT-formula. If you struggle with this terminology, maybe you want to read\n\u003e this short guide on\n\u003e [Math Programming Modelling Basics](https://www.gurobi.com/resources/math-programming-modeling-basics/).\n\nOur first problem has no deeper meaning, except for showing the basic workflow\nof creating the variables (x and y), adding the constraint $x+y\u003c=30$ on them,\nsetting the objective function (maximize $30x + 50y$), and obtaining a solution:\n\n```python\nfrom ortools.sat.python import cp_model\n\nmodel = cp_model.CpModel()\n\n# Variables\nx = model.new_int_var(0, 100, \"x\")\ny = model.new_int_var(0, 100, \"y\")\n\n# Constraints\nmodel.add(x + y \u003c= 30)\n\n# Objective\nmodel.maximize(30 * x + 50 * y)\n\n# Solve\nsolver = cp_model.CpSolver()\nstatus_code = solver.solve(model)\nstatus_name = solver.status_name()\n\n# Print the solver status and the optimal solution.\nprint(f\"{status_name} ({status_code})\")\nprint(f\"x={solver.value(x)},  y={solver.value(y)}\")\n```\n\n    OPTIMAL (4)\n    x=0,  y=30\n\nPretty easy, right? For solving a generic problem, not just one specific\ninstance, you would of course create a dictionary or list of variables and use\nsomething like `model.add(sum(vars)\u003c=n)`, because you do not want to create the\nmodel by hand for larger instances.\n\n\u003e [!TIP]\n\u003e\n\u003e The solver can return five different statuses:\n\u003e\n\u003e | Status          | Code | Description                                                                                                                                                                           |\n\u003e | --------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n\u003e | `UNKNOWN`       | 0    | The solver has not run for long enough.                                                                                                                                               |\n\u003e | `MODEL_INVALID` | 1    | The model is invalid. You will rarely see that status.                                                                                                                                |\n\u003e | `FEASIBLE`      | 2    | The model has a feasible, but not necessarily optimal, solution. If your model does not have an objective, every feasible model will return `OPTIMAL`, which may be counterintuitive. |\n\u003e | `INFEASIBLE`    | 3    | The model has no feasible solution. This means that your constraints are too restrictive.                                                                                             |\n\u003e | `OPTIMAL`       | 4    | The model has an optimal solution. If your model does not have an objective, `OPTIMAL` is returned instead of `FEASIBLE`.                                                             |\n\u003e\n\u003e The status `UNBOUNDED` does _not_ exist, as CP-SAT does not have unbounded\n\u003e variables.\n\nFor larger models, CP-SAT will unfortunately not always able to compute an\noptimal solution. However, the good news is that the solver will likely still\nfind a satisfactory solution and provide a bound on the optimal solution. Once\nyou reach this point, understanding how to interpret the solver's log becomes\ncrucial for analyzing the solver's performance. We will learn more about this\nlater.\n\n### Mathematical Model\n\nThe mathematical model of the code above would usually be written by experts\nsomething like this:\n\n```math\n\\max 30x + 50y\n```\n\n```math\n\\text{s.t. } x+y \\leq 30\n```\n\n```math\n\\quad 0\\leq x \\leq 100\n```\n\n```math\n\\quad 0\\leq y \\leq 100\n```\n\n```math\nx,y \\in \\mathbb{Z}\n```\n\nThe `s.t.` stands for `subject to`, sometimes also read as `such that`.\n\n### Overloading\n\nOne aspect of using CP-SAT solver that often poses challenges for learners is\nunderstanding operator overloading in Python and the distinction between the two\ntypes of variables involved. In this context, `x` and `y` serve as mathematical\nvariables. That is, they are placeholders that will only be assigned specific\nvalues during the solving phase. To illustrate this more clearly, let us explore\nan example within the Python shell:\n\n```pycon\n\u003e\u003e\u003e model = cp_model.CpModel()\n\u003e\u003e\u003e x = model.new_int_var(0, 100, \"x\")\n\u003e\u003e\u003e x\nx(0..100)\n\u003e\u003e\u003e type(x)\n\u003cclass 'ortools.sat.python.cp_model.IntVar'\u003e\n\u003e\u003e\u003e x + 1\nsum(x(0..100), 1)\n\u003e\u003e\u003e x + 1 \u003c= 1\n\u003cortools.sat.python.cp_model.BoundedLinearExpression object at 0x7d8d5a765df0\u003e\n```\n\nIn this example, `x` is not a conventional number but a placeholder defined to\npotentially assume any value between 0 and 100. When 1 is added to `x`, the\nresult is a new placeholder representing the sum of `x` and 1. Similarly,\ncomparing this sum to 1 produces another placeholder, which encapsulates the\ncomparison of the sum with 1. These placeholders do not hold concrete values at\nthis stage but are essential for defining constraints within the model.\nAttempting operations like `if x + 1 \u003c= 1: print(\"True\")` will trigger a\n`NotImplementedError`, as the condition `x+1\u003c=1` cannot be evaluated directly.\n\nAlthough this approach to defining models might initially seem perplexing, it\nfacilitates a closer alignment with mathematical notation, which in turn can\nmake it easier to identify and correct errors in the modeling process.\n\n### More examples\n\nIf you are not yet satisfied,\n[this folder contains many Jupyter Notebooks with examples from the developers](https://github.com/google/or-tools/tree/stable/examples/notebook/sat).\nFor example\n\n- [multiple_knapsack_sat.ipynb](https://github.com/google/or-tools/blob/stable/examples/notebook/sat/multiple_knapsack_sat.ipynb)\n  shows how to solve a multiple knapsack problem.\n- [nurses_sat.ipynb](https://github.com/google/or-tools/blob/stable/examples/notebook/sat/nurses_sat.ipynb)\n  shows how to schedule the shifts of nurses.\n- [bin_packing_sat.ipynb](https://github.com/google/or-tools/blob/stable/examples/notebook/sat/bin_packing_sat.ipynb)\n  shows how to solve a bin packing problem.\n- ... (if you know more good examples I should mention here, please let me\n  know!)\n\nFurther, you can find an extensive and beginner-friendly example on scheduling\nworkers\n[here](https://pganalyze.com/blog/a-practical-introduction-to-constraint-programming-using-cp-sat).\n\nNow that you have seen a minimal model, let us explore the various options\navailable for problem modeling. While an experienced optimizer might be able to\nhandle most problems using just the elements previously discussed, clearly\nexpressing your intentions can help CP-SAT optimize your problem more\neffectively.\n\n---\n\n\u003c!-- This file was generated by the `build.py` script. Do not edit it manually. --\u003e\n\u003c!-- 04_modelling.md --\u003e\n\u003c!--EDIT THIS PART VIA 04_modelling.md --\u003e\n\n\u003ca name=\"04-modelling\"\u003e\u003c/a\u003e\n\n## Basic Modeling\n\n\nIn this chapter, we dive into the basic modeling capabilities of CP-SAT. CP-SAT\nprovides an extensive set of constraints, closer to high-level modeling\nlanguages like MiniZinc than to traditional Mixed Integer Programming (MIP). For\nexample, it offers constraints like `all_different` and\n`add_multiplication_equality`. These advanced features reduce the need for\nmodeling complex logic strictly through linear constraints, though they also\nincrease the interface's complexity. However, not all constraints are equally\nefficient; linear and boolean constraints are generally most efficient, whereas\nconstraints like `add_multiplication_equality` can be significantly more\nresource-intensive.\n\n\u003e [!TIP]\n\u003e\n\u003e If you are transitioning from Mixed Integer Programming (MIP), you might be\n\u003e used to manually implementing higher-level constraints and optimizing Big-M\n\u003e parameters for better performance. With CP-SAT, such manual adjustments are\n\u003e generally unnecessary. CP-SAT operates differently from typical MIP solvers by\n\u003e relying less on linear relaxation and more on its underlying SAT-solver and\n\u003e propagators to efficiently manage logical constraints. Embrace the\n\u003e higher-level constraints—they are often more efficient in CP-SAT.\n\nThis primer has been expanded to cover all constraints across two chapters,\ncomplete with various examples to illustrate the contexts in which they can be\nused. However, mastering modeling involves much more than just an understanding\nof constraints. It requires a deep appreciation of the principles and techniques\nthat make models effective and applicable to real-world problems.\n\nFor a more detailed exploration of modeling, consider \"Model Building in\nMathematical Programming\" by H. Paul Williams, which offers extensive insight\ninto the subject, including practical applications. While this book is not\nspecific to CP-SAT, the foundational techniques and concepts are broadly\napplicable. Additionally, for those new to this area or transitioning from MIP\nsolutions, studying Gurobi's modeling approach through this\n[video course](https://www.youtube.com/playlist?list=PLHiHZENG6W8CezJLx_cw9mNqpmviq3lO9)\nmight prove helpful. While many principles overlap, some strategies unique to\nCP-SAT can better address cases where traditional MIP-solvers struggle.\n\nAdditional resources on mathematical modeling (not CP-SAT specific):\n\n- [Math Programming Modeling Basics by Gurobi](https://www.gurobi.com/resources/math-programming-modeling-basics/):\n  This resource provides a solid introduction to the basics of mathematical\n  modeling.\n- [Modeling with Gurobi Python](https://www.youtube.com/playlist?list=PLHiHZENG6W8CezJLx_cw9mNqpmviq3lO9):\n  A comprehensive video course on modeling with Gurobi, highlighting concepts\n  that are also applicable to CP-SAT.\n- [Model Building in Mathematical Programming by H. Paul Williams](https://www.wiley.com/en-us/Model+Building+in+Mathematical+Programming%2C+5th+Edition-p-9781118443330):\n  An extensive guide to mathematical modeling techniques.\n\n\u003e [!TIP]\n\u003e\n\u003e For getting started with implementing optimization models in general, I highly\n\u003e recommend the blog post\n\u003e [The Art Of Not Making It An Art](https://www.gurobi.com/resources/optimization-modeling-the-art-of-not-making-it-an-art/).\n\u003e It excellently summarizes the fundamental principles of successfully managing\n\u003e an optimization project, independent of the concrete language or solver.\n\n---\n\n**Elements:**\n\n- [Variables](#04-modelling-variables): `new_int_var`, `new_bool_var`,\n  `new_constant`, `new_int_var_series`, `new_bool_var_series`\n  - [Custom Domain Variables](#04-modelling-domain-variables):\n    `new_int_var_from_domain`\n- [Objectives](#04-modelling-objectives): `minimize`, `maximize`\n- [Linear Constraints](#04-modelling-linear-constraints): `add`,\n  `add_linear_constraint`\n- [Logical Constraints (Propositional Logic)](#04-modelling-logic-constraints):\n  `add_implication`, `add_bool_or`, `add_at_least_one`, `add_at_most_one`,\n  `add_exactly_one`, `add_bool_and`, `add_bool_xor`\n- [Conditional Constraints (Reification)](#04-modelling-conditional-constraints):\n  `only_enforce_if`\n- [Absolute Values and Max/Min](#04-modelling-absmaxmin): `add_min_equality`,\n  `add_max_equality`, `add_abs_equality`\n- [Multiplication, Division, and Modulo](#04-modelling-multdivmod):\n  `add_modulo_equality`, `add_multiplication_equality`, `add_division_equality`\n- [All Different](#04-modelling-alldifferent): `add_all_different`\n- [Domains and Combinations](#04-modelling-table): `add_allowed_assignments`,\n  `add_forbidden_assignments`\n- [Array/Element Constraints](#04-modelling-element): `add_element`,\n  `add_inverse`\n\nThe more advanced constraints `add_circuit`, `add_multiple_circuit`,\n`add_automaton`,`add_reservoir_constraint`,\n`add_reservoir_constraint_with_active`, `new_interval_var`,\n`new_interval_var_series`, `new_fixed_size_interval_var`,\n`new_optional_interval_var`, `new_optional_interval_var_series`,\n`new_optional_fixed_size_interval_var`,\n`new_optional_fixed_size_interval_var_series`, `add_no_overlap`,\n`add_no_overlap_2d`, and `add_cumulative` are discussed in the next chapter.\n\n---\n\n\u003ca name=\"04-modelling-variables\"\u003e\u003c/a\u003e\n\n### Variables\n\nThere are two important types of variables in CP-SAT: Booleans and Integers\n(which are actually converted to Booleans, but more on this later). There are\nalso, e.g.,\n[interval variables](https://developers.google.com/optimization/reference/python/sat/python/cp_model#intervalvar),\nbut they are actually rather a combination of integral variables and discussed\n[later](#04-modelling-intervals). For the integer variables, you have to specify\na lower and an upper bound.\n\n```python\nmodel = cp_model.CpModel()\n\n# Integer variable z with bounds -100 \u003c= z \u003c= 100\nz = model.new_int_var(-100, 100, \"z\")  # new syntax\nz_ = model.NewIntVar(-100, 100, \"z_\")  # old syntax\n\n# Boolean variable b\nb = model.new_bool_var(\"b\")  # new syntax\nb_ = model.NewBoolVar(\"b_\")  # old syntax\n\n# Implicitly available negation of b:\nnot_b = ~b  # will be 1 if b is 0 and 0 if b is 1\nnot_b_ = b.Not()  # old syntax\n```\n\nAdditionally, you can use `model.new_int_var_series` and\n`model.new_bool_var_series` to create multiple variables at once from a pandas\nIndex. This is especially useful if your data is given in a pandas DataFrame.\nHowever, there is no performance benefit in using this method, it is just more\nconvenient.\n\n```python\nmodel = cp_model.CpModel()\n\n# Create an Index from 0 to 9\nindex = pd.Index(range(10), name=\"index\")\n\n# Create a pandas Series with 10 integer variables matching the index\nxs = model.new_int_var_series(\"x\", index, 0, 100)\n\n# List of boolean variables\ndf = pd.DataFrame(\n    data={\"weight\": [1 for _ in range(10)], \"value\": [3 for _ in range(10)]},\n    index=[\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\"],\n)\nbs = model.new_bool_var_series(\"b\", df.index)  # noqa: F841\n\n# Using the dot product on the pandas DataFrame is actually a pretty\n# convenient way to create common linear expressions.\nmodel.add(bs @ df[\"weight\"] \u003c= 100)\nmodel.maximize(bs @ df[\"value\"])\n```\n\nAdditionally, there is the `new_constant`-method, which allows you to create a\nvariable that is constant. This allows you to safely replace variables by\nconstants. This is primarily useful for boolean variables, as constant integer\nvariables can in most cases be simply replaced by plain integers.\n\n\u003e [!TIP]\n\u003e\n\u003e In an older project, I observed that maintaining tight bounds on integer\n\u003e variables can significantly impact performance. Employing a heuristic to find\n\u003e a reasonable initial solution, which then allowed for tighter bounds, proved\n\u003e worthwhile, even though the bounds were just a few percent tighter. Although\n\u003e this project was several years ago and CP-SAT has advanced considerably since\n\u003e then, I still recommend keeping the bounds on the variables' ranges as tight\n\u003e as possible.\n\nThere are no continuous/floating point variables (or even constants) in CP-SAT:\nIf you need floating point numbers, you have to approximate them with integers\nby some resolution. For example, you could simply multiply all values by 100 for\na step size of 0.01. A value of 2.35 would then be represented by 235. This\n_could_ probably be implemented in CP-SAT directly, but doing it explicitly is\nnot difficult, and it has numerical implications that you should be aware of.\n\nThe absence of continuous variables may appear as a substantial limitation,\nespecially for those with a background in linear optimization where continuous\nvariables are typically regarded as the simpler component. However, if your\nproblem includes only a few continuous variables that must be approximated using\nlarge integers and involves complex constraints such as absolute values, while\nthe majority of the problem is dominated by logical constraints, CP-SAT can\noften outperform mixed-integer programming solvers. It is only when a problem\ncontains a substantial number of continuous variables and benefits significantly\nfrom strong linear relaxation that mixed-integer programming solvers will have a\ndistinct advantage, despite CP-SAT having a propagator based on the dual simplex\nmethod.\n\nI analyzed the impact of resolution (i.e., the factor by which floating point\nnumbers are multiplied) on the runtime of CP-SAT, finding that the effect varied\ndepending on the problem. For one problem, the runtime increased only\nlogarithmically with the resolution, allowing the use of a very high resolution\nof 100,000x without significant issues. In contrast, for another problem, the\nruntime increased roughly linearly with the resolution, making high resolutions\nimpractical. The runtime for different factors in this case was: 1x: 0.02s, 10x:\n0.7s, 100x: 7.6s, 1000x: 75s, and 10,000x: over 15 minutes, even though the\nsolution remained the same, merely scaled. Therefore, while high resolutions may\nbe feasible for some problems using CP-SAT, it is essential to verify their\ninfluence on runtime, as the impact can be considerable.\n\nIn my experience, boolean variables are crucial in many combinatorial\noptimization problems. For instance, the famous Traveling Salesman Problem\nconsists solely of boolean variables. Therefore, implementing a solver that\nspecializes in boolean variables using a SAT-solver as a foundation, such as\nCP-SAT, is a sensible approach. CP-SAT leverages the strengths of SAT-solving\ntechniques, which are highly effective for problems dominated by boolean\nvariables.\n\nYou may wonder why it is necessary to explicitly name the variables in CP-SAT.\nWhile there does not appear to be a technical reason for this requirement,\nnaming the variables can be extremely helpful for debugging purposes.\nUnderstanding the naming scheme of the variables allows you to more easily\ninterpret the internal representation of the model, facilitating the\nidentification and resolution of issues. To be fair, there have only been a few\ntimes when I actually needed to take a closer at the internal representation,\nand in most of the cases I would have preferred not to have to name the\nvariables.\n\n\u003ca name=\"04-modelling-domain-variables\"\u003e\u003c/a\u003e\n\n#### Custom Domain Variables\n\nWhen dealing with integer variables that you know will only need to take certain\nvalues, or when you wish to limit their possible values, custom domain variables\ncan become interesting. Unlike regular integer variables, which must have a\ndomain between a given range of values (e.g., $\\[ 1, 100 \\]$), domain variables\ncan specify a custom set of values as domain (e.g., $\\\\{1, 3, 5 \\\\}$). This\napproach can enhance efficiency when the domain - the range of sensible values -\nis small. However, it may not be the best choice for larger domains.\n\nCP-SAT works by converting all integer variables into boolean variables\n(warning: simplification). For each potential value, it creates two boolean\nvariables: one indicating whether the integer variable is equal to this value,\nand another indicating whether it is less than or equal to it. This is called an\n_order encoding_. At first glance, this might suggest that using domain\nvariables is always preferable, as it appears to reduce the number of boolean\nvariables needed.\n\nHowever, CP-SAT employs a lazy creation strategy for these boolean variables.\nThis means it only generates them as needed, based on the solver's\ndecision-making process. Therefore, an integer variable with a wide range - say,\nfrom 0 to 100 - will not immediately result in 200 boolean variables. It might\nlead to the creation of only a few, depending on the solver's requirements.\n\nLimiting the domain of a variable can have drawbacks. Firstly, defining a domain\nexplicitly can be computationally costly and increase the model size drastically\nas it now need to contain not just a lower and upper bound for a variable but an\nexplicit list of numbers (model size is often a limiting factor). Secondly, by\nnarrowing down the solution space, you might inadvertently make it more\nchallenging for the solver to find a viable solution. First, try to let CP-SAT\nhandle the domain of your variables itself and only intervene if you have a good\nreason to do so.\n\nIf you choose to utilize domain variables for their benefits in specific\nscenarios, here is how to define them:\n\n```python\nfrom ortools.sat.python import cp_model\n\nmodel = cp_model.CpModel()\n\n# Define a domain with selected values\ndomain = cp_model.Domain.from_values([2, 5, 8, 10, 20, 50, 90])\n\n# Can also be done via intervals\ndomain_2 = cp_model.Domain.from_intervals([[8, 12], [14, 20]])\n\n# There are also some operations available\ndomain_3 = domain.union_with(domain_2)\n\n# Create a domain variable within this defined domain\nx = model.new_int_var_from_domain(domain, \"x\")\n```\n\nThis example illustrates the process of creating a domain variable `x` that can\nonly take on the values specified in `domain`. This method is particularly\nuseful when you are working with variables that only have a meaningful range of\npossible values within your problem's context.\n\n\u003ca name=\"04-modelling-objectives\"\u003e\u003c/a\u003e\n\n### Objectives\n\nNot every problem necessitates an objective; sometimes, finding a feasible\nsolution is sufficient. CP-SAT excels at finding feasible solutions, a task at\nwhich mixed-integer programming (MIP) solvers often do not perform as well.\nHowever, CP-SAT is also capable of effective optimization, which is an area\nwhere older constraint programming solvers may lag, based on my experience.\n\nCP-SAT allows for the minimization or maximization of a linear expression. You\ncan model more complex expressions by using auxiliary variables and additional\nconstraints. To specify an objective function, you can use the `model.minimize`\nor `model.maximize` commands with a linear expression. This flexibility makes\nCP-SAT a robust tool for a variety of optimization tasks.\n\n```python\n# Basic model with variables and constraints\nmodel = cp_model.CpModel()\nx = model.new_int_var(-100, 100, \"x\")\ny = model.new_int_var(-100, 100, \"y\")\nmodel.add(x + 10 * y \u003c= 100)\n\n# Minimize 30x + 50y\nmodel.maximize(30 * x + 50 * y)\n```\n\nLet us look on how to model more complicated expressions, using boolean\nvariables and generators.\n\n```python\nmodel = cp_model.CpModel()\nx_vars = [model.new_bool_var(f\"x{i}\") for i in range(10)]\nmodel.minimize(sum(i * x_vars[i] if i % 2 == 0 else i * ~x_vars[i] for i in range(10)))\n```\n\nThis objective evaluates to\n\n```math\n\\min \\sum_{i=0}^{9} i\\cdot x_i \\text{ if } i \\text{ is even else } i\\cdot \\neg x_i\n```\n\nTo implement a\n[lexicographic optimization](https://en.wikipedia.org/wiki/Lexicographic_optimization),\nyou can do multiple rounds and always fix the previous objective as constraint.\n\n```python\n# some basic model\nmodel = cp_model.CpModel()\nx = model.new_int_var(-100, 100, \"x\")\ny = model.new_int_var(-100, 100, \"y\")\nz = model.new_int_var(-100, 100, \"z\")\nmodel.add(x + 10 * y - 2 * z \u003c= 100)\n\n# Define the objectives\nfirst_objective = 30 * x + 50 * y\nsecond_objective = 10 * x + 20 * y + 30 * z\n\n# Optimize for the first objective\nmodel.maximize(first_objective)\nsolver = cp_model.CpSolver()\nsolver.solve(model)\n\n# Fix the first objective and optimize for the second\nmodel.add(first_objective == int(solver.objective_value))  # fix previous objective\nmodel.minimize(second_objective)  # optimize for second objective\nsolver.solve(model)\n```\n\n\u003e [!TIP]\n\u003e\n\u003e You can find a more efficient implementation of lexicographic optimization in\n\u003e the _Coding Patterns_ chapter.\n\nTo handle non-linear objectives in CP-SAT, you can employ auxiliary variables\nand constraints. For instance, to incorporate the absolute value of a variable\ninto your objective, you first create a new variable representing this absolute\nvalue. Shortly, you will learn more about setting up these types of constraints.\nBelow is a Python example demonstrating how to model and minimize the absolute\nvalue of a variable `x`:\n\n```python\n# Assuming x is already defined in your model\nabs_x = model.new_int_var(\n    0, 100, \"|x|\"\n)  # Create a variable to represent the absolute value of x\nmodel.add_abs_equality(target=abs_x, expr=x)  # Define abs_x as the absolute value of x\nmodel.minimize(abs_x)  # Set the objective to minimize abs_x\n```\n\nThe constraints available to define your feasible solution space will be\ndiscussed in the following section.\n\n\u003ca name=\"04-modelling-linear-constraints\"\u003e\u003c/a\u003e\n\n### Linear Constraints\n\nThese are the classical constraints also used in linear optimization. Remember\nthat you are still not allowed to use floating point numbers within it. Same as\nfor linear optimization: You are not allowed to multiply a variable with\nanything else than a constant and also not to apply any further mathematical\noperations.\n\n```python\nmodel.add(10 * x + 15 * y \u003c= 10)\nmodel.add(x + z == 2 * y)\n\n# This one actually is not linear but still works.\nmodel.add(x + y != z)\n\n# Because we are working on integers, the true smaller or greater constraints\n# are trivial to implement as x \u003c z is equivalent to x \u003c= z-1\nmodel.add(x \u003c y + z)\nmodel.add(y \u003e 300 - 4 * z)\n```\n\nNote that `!=` can be slower than the other (`\u003c=`, `\u003e=`, `==`) constraints,\nbecause it is not a linear constraint. If you have a set of mutually `!=`\nvariables, it is better to use `all_different` (see below) than to use the\nexplicit `!=` constraints.\n\n\u003e [!WARNING]\n\u003e\n\u003e If you use intersecting linear constraints, you may get problems because the\n\u003e intersection point needs to be integral. There is no such thing as a\n\u003e feasibility tolerance as in Mixed Integer Programming-solvers, where small\n\u003e deviations are allowed. The feasibility tolerance in MIP-solvers allows, e.g.,\n\u003e 0.763445 == 0.763439 to still be considered equal to counter numerical issues\n\u003e of floating point arithmetic. In CP-SAT, you have to make sure that values can\n\u003e match exactly.\n\nLet us look at the following example with two linear equality constraints:\n\n```math\nx - y = 0\n```\n\n```math\n4-y = 2y\n```\n\n```math\nx, y \\geq 0\n```\n\nYou can verify that $x=4/3$ and $y=4/3$ is a feasible solution. However, coding\nthis in CP-SAT results in an infeasible solution:\n\n```python\nmodel = cp_model.CpModel()\nx = model.new_int_var(-100, 100, \"x\")\ny = model.new_int_var(-100, 100, \"y\")\n\nmodel.add(x - y == 0)\nmodel.add(4 - x == 2 * y)\n\nsolver = cp_model.CpSolver()\nstatus = solver.solve(model)\nassert status == cp_model.INFEASIBLE\n```\n\nEven using scaling techniques, such as multiplying integer variables by\n1,000,000 to increase the resolution, would not render the model feasible. While\ncommon linear programming solvers would handle this model without issue, CP-SAT\nstruggles unless modifications are made to eliminate fractions, such as\nmultiplying all terms by 3. However, this requires manual intervention, which\nundermines the idea of using a solver. These limitations are important to\nconsider, although such scenarios are rare in practical applications.\n\n\u003e [!TIP]\n\u003e\n\u003e If you have long sums of variables and coefficients, it can be more efficient\n\u003e to use the sum-methods of LinearExpr than to use Python's sum-function. Note\n\u003e that this function does currently not support generators.\n\u003e\n\u003e ```python\n\u003e xs = [model.NewIntVar(0, 10, f\"x{i}\") for i in range(5)]\n\u003e weights = [i for i in range(5)]\n\u003e model.add(cp_model.LinearExpr.sum(xs) \u003e= 1)\n\u003e model.minimize(cp_model.LinearExpr.weighted_sum(xs, weights))\n\u003e ```\n\nIf you have a lower and an upper bound for a linear expression, you can also use\nthe `add_linear_constraint`-method, which allows you to specify both bounds in\none go.\n\n```python\nmodel.add_linear_constraint(linear_expr=10 * x + 15 * y, lb=-100, ub=10)\n```\n\nThe similar sounding `AddLinearExpressionInDomain` is discussed later.\n\n\u003ca name=\"04-modelling-logic-constraints\"\u003e\u003c/a\u003e\n\n### Logical Constraints (Propositional Logic)\n\nPropositional logic allows us to describe relationships between true or false\nstatements using logical operators. Consider a simple scenario where we define\nthree Boolean variables:\n\n```python\nb1 = model.new_bool_var(\"b1\")\nb2 = model.new_bool_var(\"b2\")\nb3 = model.new_bool_var(\"b3\")\n```\n\nThese variables, `b1`, `b2`, and `b3`, represent distinct propositions whose\ntruth values are to be determined by the model.\n\nYou can obtain the negation of a Boolean variable by using `~` or the\n`.Not()`-method. The resulting variable can be used just like the original\nvariable:\n\n```python\nnot_b1 = ~b1  # Negation of b1\nnot_b2 = b2.Not()  # Alternative notation for negation\n```\n\nNote that you can use more than three variables in all of the following\nexamples, except for `add_implication` which is only defined for two variables.\n\n\u003e [!WARNING]\n\u003e\n\u003e Boolean variables are essentially special integer variables restricted to the\n\u003e domain of 0 and 1. Therefore, you can incorporate them into linear constraints\n\u003e as well. However, it is important to note that integer variables, unlike\n\u003e Boolean variables, cannot be used in Boolean constraints. This is a\n\u003e distinction from some programming languages, like Python, where integers can\n\u003e sometimes substitute for Booleans.\n\n#### Adding Logical OR Constraints\n\nThe logical OR operation ensures that at least one of the specified conditions\nholds true. To model this, you can use:\n\n```python\nmodel.add_bool_or(b1, b2, b3)  # b1 or b2 or b3 must be true\nmodel.add_at_least_one([b1, b2, b3])  # Alternative notation\nmodel.add(b1 + b2 + b3 \u003e= 1)  # Alternative linear notation using '+' for OR\n```\n\nBoth lines ensure that at least one of `b1`, `b2`, or `b3` is true.\n\n#### Adding Logical AND Constraints\n\nThe logical AND operation specifies that all conditions must be true\nsimultaneously. To model conditions where `b1` is true and both `b2` and `b3`\nare false, you can use:\n\n```python\nmodel.add_bool_and(b1, b2.Not(), b3.Not())  # b1 and not b2 and not b3 must all be true\nmodel.add_bool_and(b1, ~b2, ~b3)  # Alternative notation using '~' for negation\n```\n\nThe `add_bool_and` method is most effective when used with the `only_enforce_if`\nmethod (discussed in\n[Conditional Constraints (Reification)](#04-modelling-conditional-constraints)).\nFor cases not utilizing `only_enforce_if` a simple AND-clause such as\n$\\left( b_1 \\land \\neg b_2 \\land \\neg b_3 \\right)$ becomes redundant by simply\nsubstituting $b_1$ with `1` and $b_2, b_3$ with `0`. In straightforward\nscenarios, consider substituting these variables with their constant values to\nreduce unnecessary complexity, especially in larger models where size and\nmanageability are concerns. In smaller or simpler models, CP-SAT efficiently\nhandles these redundancies, allowing you to focus on maintaining clarity and\nreadability in your model.\n\n#### Adding Logical XOR Constraints\n\nThe logical XOR (exclusive OR) operation ensures that an odd number of operands\nare true. It is crucial to understand this definition, as it has specific\nimplications when applied to more than two variables:\n\n- For two variables, such as `b1 XOR b2`, the operation returns true if exactly\n  one of these variables is true, which aligns with the \"exactly one\" constraint\n  for this specific case.\n- For three or more variables, such as in the expression `b1 XOR b2 XOR b3`, the\n  operation returns true if an odd number of these variables are true. This\n  includes scenarios where one or three variables are true, assuming the total\n  number of variables involved is three.\n\nThis characteristic of XOR can be somewhat complex but is crucial for modeling\nscenarios where the number of true conditions needs to be odd:\n\n```python\nmodel.add_bool_xor(b1, b2)  # Returns true if exactly one of b1 or b2 is true\nmodel.add_bool_xor(\n    b1, b2, b3\n)  # Returns true if an odd number of b1, b2, b3 are true (i.e., one or three)\n```\n\n#### Specifying Unique Conditions\n\nTo enforce that exactly one or at most one of the variables is true, use:\n\n```python\nmodel.add_exactly_one([b1, b2, b3])  # Exactly one of the variables must be true\nmodel.add_at_most_one([b1, b2, b3])  # No more than one of the variables should be true\n```\n\nThese constraints are useful for scenarios where exclusive choices must be\nmodeled.\n\nYou could alternatively also use `add`.\n\n```python\nmodel.add(b1 + b2 + b3 == 1)  # Exactly one of the variables must be true\nmodel.add(b1 + b2 + b3 \u003c= 1)  # No more than one of the variables should be true\n```\n\n#### Modeling Implications\n\nLogical implication, denoted as `-\u003e`, indicates that if the first condition is\ntrue, the second must also be true. This can be modeled as:\n\n```python\nmodel.add_implication(b1, b2)  # If b1 is true, then b2 must also be true\n```\n\nYou could also use `add`.\n\n```python\nmodel.add(b2 \u003e= b1)  # If b1 is true, then b2 must also be true\n```\n\n\u003ca name=\"04-modelling-conditional-constraints\"\u003e\u003c/a\u003e\n\n### Conditional Constraints (Reification)\n\nIn practical applications, scenarios often arise where conditions dictate the\nenforcement of certain constraints. For instance, \"if this condition is true,\nthen a specific constraint should apply,\" or \"if a constraint is violated, a\npenalty variable is set to true, triggering another constraint.\" Additionally,\nreal-world constraints can sometimes be bypassed with financial or other types\nof concessions, such as renting a more expensive truck to exceed a load limit,\nor allowing a worker to take a day off after a double shift.\n\n\u003e In constraint programming, **reification** involves associating a Boolean\n\u003e variable with a constraint to capture its truth value, thereby turning the\n\u003e satisfaction of the constraint into a variable that can be used in further\n\u003e constraints. Full reification links a Boolean variable such that it is `True`\n\u003e if the constraint is satisfied and `False` otherwise, enabling the variable to\n\u003e be directly used in other decisions or constraints. Conversely,\n\u003e half-reification, or implied constraints, involves a one-way linkage where the\n\u003e Boolean variable being `True` implies the constraint must be satisfied, but\n\u003e its being `False` does not necessarily indicate anything about the\n\u003e constraint's satisfaction. This approach is particularly useful for expressing\n\u003e complex conditional logic and for modeling scenarios where only the\n\u003e satisfaction, and not the violation, of a constraint needs to be explicitly\n\u003e handled.\n\nTo effectively manage these conditional scenarios, CP-SAT offers the\n`only_enforce_if`-method for linear and some Boolean constraints, which\nactivates a constraint only if a specified condition is met. This method is not\nonly typically more efficient than traditional methods like the\n[Big-M method](https://en.wikipedia.org/wiki/Big_M_method) but also simplifies\nthe model by eliminating the need to determine an appropriate Big-M value.\n\n```python\n# A value representing the load that needs to be transported\nload_value = model.new_int_var(0, 100, \"load_value\")\n\n# ... some logic to determine the load value ...\n\n# A variable to decide which truck to rent\ntruck_a = model.new_bool_var(\"truck_a\")\ntruck_b = model.new_bool_var(\"truck_b\")\ntruck_c = model.new_bool_var(\"truck_c\")\n\n# Only rent one truck\nmodel.add_at_most_one([truck_a, truck_b, truck_c])\n\n# Depending on which truck is rented, the load value is limited\nmodel.add(load_value \u003c= 50).only_enforce_if(truck_a)\nmodel.add(load_value \u003c= 80).only_enforce_if(truck_b)\nmodel.add(load_value \u003c= 100).only_enforce_if(truck_c)\n\n# Some additional logic\ndriver_has_big_truck_license = model.new_bool_var(\"driver_has_big_truck_license\")\ndriver_has_special_license = model.new_bool_var(\"driver_has_special_license\")\n# Only drivers with a big truck license or a special license can rent truck c\nmodel.add_bool_or(\n    driver_has_big_truck_license, driver_has_special_license\n).only_enforce_if(truck_c)\n\n# Minimize the rent cost\nmodel.minimize(30 * truck_a + 40 * truck_b + 80 * truck_c)\n```\n\nYou can also use negations in the `only_enforce_if` method.\n\n```python\nmodel.add(x + y == 10).only_enforce_if(~b1)\n```\n\nYou can also pass a list of Boolean variables to `only_enforce_if`, in which\ncase the constraint is only enforced if all of the variables in the list are\ntrue.\n\n```python\nmodel.add(x + y == 10).only_enforce_if([b1, ~b2])  # only enforce if b1 AND NOT b2\n```\n\n\u003e [!WARNING]\n\u003e\n\u003e While `only_enforce_if` in CP-SAT is often more efficient than similar\n\u003e concepts in classical MIP-solvers, it can still impact the performance of\n\u003e CP-SAT significantly. Doing some additional reasoning, you can often find a\n\u003e more efficient way to model your problem without having to use\n\u003e `only_enforce_if`. For logical constraints, there are actually\n\u003e straight-forward methods in\n\u003e [propositional calculus](https://en.wikipedia.org/wiki/Propositional_calculus).\n\u003e As `only_enforce_if` is often a more natural way to model your problem, it is\n\u003e still a good idea to use it to get your first prototype running and think\n\u003e about smarter ways later.\n\n\u003ca name=\"04-modelling-absmaxmin\"\u003e\u003c/a\u003e\n\n### Absolute Values and Maximum/Minimum Functions with Integer Variables\n\nWhen working with integer variables in CP-SAT, operations such as computing\nabsolute values, maximum, and minimum values cannot be directly expressed using\nbasic Python operations like `abs`, `max`, or `min`. Instead, these operations\nmust be handled through the use of auxiliary variables and specialized\nconstraints that map these variables to the desired values. The auxiliary\nvariables can then be used in other constraints, representing the desired\nsubexpression.\n\n```python\nmodel = cp_model.CpModel()\nx = model.new_int_var(-100, 100, \"x\")\ny = model.new_int_var(-100, 100, \"y\")\nz = model.new_int_var(-100, 100, \"z\")\n\n# Create an auxiliary variable for the absolute value of x+z\nabs_xz = model.new_int_var(0, 200, \"|x+z|\")\nmodel.add_abs_equality(target=abs_xz, expr=x + z)\n\n# Create variables to capture the maximum and minimum of x, (y-1), and z\nmax_xyz = model.new_int_var(0, 100, \"max(x, y, z-1)\")\nmodel.add_max_equality(target=max_xyz, exprs=[x, y - 1, z])\n\nmin_xyz = model.new_int_var(-100, 100, \"min(x, y, z)\")\nmodel.add_min_equality(target=min_xyz, exprs=[x, y - 1, z])\n```\n\nWhile some practitioners report that these methods are more efficient than those\navailable in classical Mixed Integer Programming solvers, such findings are\npredominantly based on empirical evidence and specific use-case scenarios. It is\nalso worth noting that, surprisingly often, these constraints can be substituted\nwith more efficient linear constraints. Here is an example for achieving maximum\nequality in a more efficient way:\n\n```python\nx = model.new_int_var(0, 100, \"x\")\ny = model.new_int_var(0, 100, \"y\")\nz = model.new_int_var(0, 100, \"z\")\n\n# Ensure that max_xyz is at least the maximum of x, y, and z\nmax_xyz = model.new_int_var(0, 100, \"max_xyz\")\nmodel.add(max_xyz \u003e= x)\nmodel.add(max_xyz \u003e= y)\nmodel.add(max_xyz \u003e= z)\n\n# Minimizing max_xyz to ensure it accurately reflects the maximum value\nmodel.minimize(max_xyz)\n```\n\nThis approach takes advantage of the solver's minimization function to tighten\nthe bound, accurately reflecting the maximum of `x`, `y`, and `z`. By utilizing\nlinear constraints, this method can often achieve faster solving times compared\nto using the `add_max_equality` constraint. Similar techniques also exist for\nmanaging absolute and minimum values, as well as for complex scenarios where\ndirect enforcement of equality through the objective function is not feasible.\n\n\u003ca name=\"04-modelling-multdivmod\"\u003e\u003c/a\u003e\n\n### Multiplication, Division, and Modulo\n\nIn practical problems, you may need to perform more complex arithmetic\noperations than simple additions. Consider the scenario where the rental cost\nfor a set of trucks is calculated as the product of the number of trucks, the\nnumber of days, and the daily rental rate. Here, the first two factors are\nvariables, leading to a quadratic expression. Attempting to multiply two\nvariables directly in CP-SAT will result in an error because the `add` method\nonly accepts linear expressions, which are sums of variables and constants.\nHowever, CP-SAT supports multiplication, division, and modulo operations.\nSimilar to using `abs`, `max`, and `min`, you must create an auxiliary variable\nto represent the result of the operation.\n\n```python\nmodel = cp_model.CpModel()\nx = model.new_int_var(-100, 100, \"x\")\ny = model.new_int_var(-100, 100, \"y\")\nz = model.new_int_var(-100, 100, \"z\")\n\nxyz = model.new_int_var(-(100**3), 100**3, \"x*y*z\")\nmodel.add_multiplication_equality(xyz, [x, y, z])  # xyz = x*y*z\n\nmodel.add_modulo_equality(x, y, 3)  # x = y % 3\nmodel.add_division_equality(x, y, z)  # x = y // z\n```\n\nWhen using these operations, you often transition from linear to non-linear\noptimization, which is generally more challenging to solve. In cases of\ndivision, it is essential to remember that operations are on integers;\ntherefore, `5 // 2` results in `2`, not `2.5`.\n\nMany problems initially involve non-linear expressions that can often be\nreformulated or approximated using linear expressions. This transformation can\nenhance the tractability and speed of solving the problem. Although modeling\nyour problem as closely as possible to the real-world scenario is crucial, it is\nequally important to balance accuracy with tractability. A highly accurate model\nis futile if the solver cannot optimize it efficiently. It might be beneficial\nto employ multiple phases in your optimization process, starting with a simpler,\nless accurate model and gradually refining it.\n\nSome non-linear expressions can still be managed efficiently if they are convex.\nFor instance, second-order cone constraints can be solved in polynomial time\nusing interior point methods. Gurobi, for example, supports these constraints\nnatively. CP-SAT includes an LP-propagator but relies on the Dual Simplex\nalgorithm, which is not suitable for these constraints and must depend on\nsimpler methods. Similarly, most open-source MIP solvers may struggle with these\nconstraints.\n\nIt is challenging to determine if CP-SAT can handle non-linear expressions\nefficiently or which solver would be best suited for your problem. Non-linear\nexpressions are invariably complex, and avoiding them when possible is\nadvisable.\n\nHere is one of my students' favorite examples of a non-linear expression that\ncan be avoided. Once introduced to mathematical notation like\n$\\sum_{e \\in E} cost(e)\\cdot x_e$, if a term depends on the combination of two\nbinary variables, they might initially opt for a quadratic expression such as\n$\\sum_{e,e'\\in E} concost(e, e')\\cdot x_e\\cdot x_{e'}$. However, such cases can\noften be modeled linearly using an auxiliary variable, avoiding the complexities\nof non-linear modeling.\n\n```python\nmodel = cp_model.CpModel()\n\nb1 = model.new_bool_var(\"b1\")\nb2 = model.new_bool_var(\"b2\")\n\nb1b2 = model.new_bool_var(\"b1b2\")\nmodel.add_implication(~b1, ~b1b2)\nmodel.add_implication(~b2, ~b1b2)\nmodel.add_bool_or(~b1, ~b2, b1b2)  # optional, for a penalty term to be minimized.\n```\n\nThere are numerous further instances where non-linear expressions can be\nsimplified by using auxiliary variables or by shifting the non-linear components\ninto constants. However, exploring these techniques is most beneficial when you\nencounter specific challenges related to non-linear expressions in your models.\n\nWe will revisit further discussions on non-linear expressions and their\nconversion to piecewise linear approximations in a subsequent section. This will\nprovide a foundational understanding necessary for addressing more complex\nmodeling scenarios effectively.\n\n\u003ca name=\"04-modelling-alldifferent\"\u003e\u003c/a\u003e\n\n### All Different\n\nIn various assignment and scheduling problems, ensuring that all variables hold\ndistinct values is crucial. For example, in frequency assignment, no two\ntransmitters within the same area should operate on the same frequency, or in\nscheduling, no two tasks should occupy the same time slot. Typically, this\nrequirement could be modeled with a quadratic number of inequality (`!=`)\nconstraints. However, a more elegant solution involves using the\n`add_all_different` constraint, which directly enforces that all variables in a\nlist take unique values. This constraint is particularly useful in solving\npuzzles like Sudoku or the\n[N-queens problem](https://developers.google.com/optimization/cp/queens).\n\n```python\nmodel = cp_model.CpModel()\nx = model.new_int_var(-100, 100, \"x\")\ny = model.new_int_var(-100, 100, \"y\")\nz = model.new_int_var(-100, 100, \"z\")\n\n# Adding an all-different constraint\nmodel.add_all_different([x, y, z])\n\n# Advanced usage with transformations\nvars = [model.new_int_var(0, 10, f\"v_{i}\") for i in range(10)]\nmodel.add_all_different([x + i for i, x in enumerate(vars)])\n```\n\nUsing `add_all_different` not only simplifies the modeling but also utilizes a\ndedicated domain-based propagator in CP-SAT, enhancing efficiency beyond what is\nachievable with multiple `!=` constraints. However, if your model mixes `!=`\nconstraints with `add_all_different`, be cautious, as CP-SAT disables automatic\ninference of `add_all_different` from groups of `!=` constraints, which can lead\nto performance penalties.\n\nFor a practical demonstration, refer to the\n[graph coloring problem example](https://github.com/d-krupke/cpsat-primer/blob/main/examples/add_all_different.ipynb)\nin our repository. Here, using `!=` constraints solved the problem in seconds,\nwhereas `add_all_different` took significantly longer, illustrating the\nimportance of choosing the right method based on the problem scale and\ncomplexity.\n\nAlternatively, modeling with Boolean variables and constraints like\n`add_at_most_one` or pairwise negations (`add_boolean_or(~b1, ~b2)`) can also be\neffective. This approach benefits from CP-SAT's efficient handling of Boolean\nlogic and allows for easy integration of additional constraints or objectives,\nsuch as licensing costs associated with certain frequencies. Although CP-SAT\ndoes something similar internally, it creates these constructs lazily and only\nas needed, whereas explicit modeling in Python may not be as efficient.\n\nThe choice between these methods—or potentially another strategy—depends on\nspecific model requirements and familiarity with CP-SAT's behavior. When in\ndoubt, start with the most intuitive method and refine your approach based on\nperformance observations.\n\n\u003ca name=\"04-modelling-table\"\u003e\u003c/a\u003e\n\n### Domains and Combinations\n\nWhen optimizing scenarios with predefined feasible values or combinations of\nvariables—often outlined in a table—it is advantageous to directly restrict the\ndomain of an expression or set of variables.\n\nConsider an example where you are optimizing a shift schedule for a team of\nemployees, and you have a table of feasible combinations for each shift:\n\n| Employee 1 | Employee 2 | Employee 3 | Employee 4 |\n| ---------- | ---------- | ---------- | ---------- |\n| 1          | 0          | 1          | 0          |\n| 0          | 1          | 1          | 0          |\n| 1          | 0          | 0          | 1          |\n| 0          | 1          | 0          | 1          |\n\nIn CP-SAT, this can be modeled efficiently using the `add_allowed_assignments`\nmethod:\n\n```python\nmodel = cp_model.CpModel()\nx_employee_1 = model.new_bool_var(\"x_employee_1\")\nx_employee_2 = model.new_bool_var(\"x_employee_2\")\nx_employee_3 = model.new_bool_var(\"x_employee_3\")\nx_employee_4 = model.new_bool_var(\"x_employee_4\")\n\n# Define the allowed assignments\nallowed_assignments = [\n    [1, 0, 1, 0],\n    [0, 1, 1, 0],\n    [1, 0, 0, 1],\n    [0, 1, 0, 1],\n]\n\nmodel.add_allowed_assignments(\n    [x_employee_1, x_employee_2, x_employee_3, x_employee_4], allowed_assignments\n)\n```\n\nAlternatively, forbidden combinations can be specified using\n`add_forbidden_assignments`:\n\n```python\nprohibit_assignments = [\n    [1, 0, 1, 0],\n    [0, 1, 1, 0],\n    [1, 0, 0, 1],\n    [0, 1, 0, 1],\n]\nmodel.add_forbidden_assignments(\n    [x_employee_1, x_employee_2, x_employee_3, x_employee_4], prohibit_assignments\n)\n```\n\nThe utility of the `add_allowed_assignments` method becomes more apparent when\nintegrated with other constraints within the model, rather than when it spans\nall variables. If the table covered all variables, one could theoretically\nevaluate each row to identify the best solution without the need for\nsophisticated optimization techniques. However, consider this scenario where\nconstraints are integrated across multiple shifts:\n\n```python\nNUM_SHIFTS = 7\n\nmodel = cp_model.CpModel()\nx_employee_1 = [model.new_bool_var(f\"x_employee_1_{i}\") for i in range(NUM_SHIFTS)]\nx_employee_2 = [model.new_bool_var(f\"x_employee_2_{i}\") for i in range(NUM_SHIFTS)]\nx_employee_3 = [model.new_bool_var(f\"x_employee_3_{i}\") for i in range(NUM_SHIFTS)]\nx_employee_4 = [model.new_bool_var(f\"x_employee_4_{i}\") for i in range(NUM_SHIFTS)]\n\nfor i in range(NUM_SHIFTS):\n    model.add_allowed_assignments(\n        [x_employee_1[i], x_employee_2[i], x_employee_3[i], x_employee_4[i]],\n        allowed_assignments,\n    )\n\n# ... some further constraints and objectives to connect the days ...\n# ... if the days would be independent, you would solve each day separately ...\n```\n\nThe `add_allowed_assignments` method in CP-SAT enables the direct incorporation\nof specific feasible combinations into your optimization model, ensuring that\nonly certain configurations of variables are considered within the solution\nspace. This method effectively \"hard-codes\" these configurations, simplifying\nthe model by predefining which combinations of variables are permissible, much\nlike setting rules for employee shifts or resource allocations.\n\n\u003e [!NOTE]\n\u003e\n\u003e Hardcoding specific combinations in your model is a preliminary step toward\n\u003e advanced decomposition techniques like Dantzig-Wolfe decomposition. In this\n\u003e method, a complex optimization problem is simplified by replacing a group of\n\u003e correlated variables with composite variables. Such a composite variable\n\u003e represents a solution for a subproblem. Optimizing these composite variables\n\u003e in the master problem significantly reduces the model's complexity and\n\u003e improves the efficiency of solving large-scale problems.\n\nA related method for managing linear expressions instead of direct assignments\nis `add_linear_expression_in_domain`. Suppose we know a certain linear\nexpression, \\(10x + 5y\\), must equal 20, 50, or 100:\n\n```python\nmodel = cp_model.CpModel()\nx = model.new_int_var(-100, 100, \"x\")\ny = model.new_int_var(-100, 100, \"y\")\n\ndomain = cp_model.Domain.from_values([20, 50, 100])\nmodel.add_linear_expression_in_domain(10 * x + 5 * y, domain)\n```\n\n\u003e [!WARNING]\n\u003e\n\u003e Ensure calculations are correct, especially when working with integers, to\n\u003e avoid creating an infeasible or overly restrictive model. Consider using an\n\u003e auxiliary variable with a restricted domain and softer constraints (`\u003c=`,\n\u003e `\u003e=`) to achieve a more flexible and forgiving model setup.\n\n\u003ca name=\"04-modelling-element\"\u003e\u003c/a\u003e \u003ca name=\"04-modelling-array\"\u003e\u003c/a\u003e\n\n### Element/Array Constraints\n\nBefore exploring specialized constraints, let us examine the last of the generic\nones. The element constraint facilitates accessing the value of a variable (or\nsince ortools 9.12, a linear expression) within an array using another variable\nas the index. Accessing a variable in an array with a constant index is\nstraightforward; however, integrating a variable index into your model adds\ncomplexity. This constraint can also be used to ensure that a variable matches\nthe value at a specific array position.\n\n```python\nmodel = cp_model.CpModel()\nx = model.new_int_var(-100, 100, \"x\")\ny = model.new_int_var(-100, 100, \"y\")\nz = model.new_int_var(-100, 100, \"z\")\nvar_array = [x, y, z]\n\n# Create a variable for the index and a variable for the value at that index.\nindex_var = model.new_int_var(0, len(var_array) - 1, \"index\")\nvalue_at_index_var = model.new_int_var(-100, 100, \"value_at_index\")\n\n# Apply the element constraint to link the index and value variables.\nmodel.add_element(expressions=var_array, index=index_var, target=value_at_index_var)\n# CAVEAT: Before ortools 9.12, it was `variables=` instead of `expressions=`.\n```\n\nExamples of feasible variable assignments:\n\n| `x` | `y` | `z` | `index_var` | `value_at_index` |\n| --- | --- | --- | ----------- | ---------------- |\n| 3   | 4   | 5   | 0           | 3                |\n| 3   | 4   | 5   | 1           | 4                |\n| 3   | 4   | 5   | 2           | 5                |\n| 7   | 3   | 4   | 0           | 7                |\n\nThe subsequent constraint resembles a stable matching in array form. For two\nequally sized arrays of variables $v$ and $w$, each of size $|v|$, it imposes a\nbijective relationship: $v[i]=j \\Leftrightarrow w[j]=i$ for all\n$i,j \\in 0,\\ldots,|v|-1$. This constraint limits the variables' values to\n$0,\\ldots, |v|-1$.\n\n```python\nmodel = cp_model.CpModel()\nv = [model.new_int_var(0, 5, f\"v_{i}\") for i in range(6)]\nw = [model.new_int_var(0, 5, f\"w_{i}\") for i in range(6)]\n\nmodel.add_inverse(v, w)\n```\n\nExamples of feasible variable assignments:\n\n| array | 0   | 1   | 2   | 3   | 4   | 5   |\n| ----- | --- | --- | --- | --- | --- | --- |\n| v     | 0   | 1   | 2   | 3   | 4   | 5   |\n| w     | 0   | 1   | 2   | 3   | 4   | 5   |\n\n| array | 0   | 1   | 2   | 3   | 4   | 5   |\n| ----- | --- | --- | --- | --- | --- | --- |\n| v     | 1   | 2   | 3   | 4   | 5   | 0   |\n| w     | 5   | 0   | 1   | 2   | 3   | 4   |\n\n| array | 0   | 1   | 2   | 3   | 4   | 5   |\n| ----- | --- | --- | --- | --- | --- | --- |\n| v     | 1   | 0   | 3   | 5   | 2   | 4   |\n| w     | 1   | 0   | 4   | 2   | 5   | 3   |\n\n| ![Example Matching](https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/images/inverse.png) |\n| :--------------------------------------------------------------------------------------------------: |\n|               Visualizing the stable matching induced by the `add_inverse` constraint.               |\n\n\u003e [!WARNING]\n\u003e\n\u003e I generally advise against using the `add_element` and `add_inverse`\n\u003e constraints. While CP-SAT may have effective propagation techniques for them,\n\u003e these constraints can appear unnatural and complex. It's often more\n\u003e straightforward to model stable matching with binary variables $x_{ij}$,\n\u003e indicating whether $v_i$ is matched with $w_j$, and employing an\n\u003e `add_exactly_one` constraint for each vertex to ensure unique matches. If your\n\u003e model needs to capture specific attributes or costs associated with\n\u003e connections, binary variables are necessary. Relying solely on indices would\n\u003e require additional logic for accurate representation. Additionally, use\n\u003e non-binary variables only if the numerical value inherently carries semantic\n\u003e meaning that cannot simply be re-indexed.\n\n\u003c!-- This file was generated by the `build.py` script. Do not edit it manually. --\u003e\n\u003c!-- 04B_advanced_modelling.md --\u003e\n\u003ca name=\"04B-advanced-modelling\"\u003e\u003c/a\u003e\n\n## Advanced Modeling\n\n\nAfter having seen the basic elements of CP-SAT, this chapter will introduce you\nto the more complex constraints. These constraints are already focused on\nspecific problems, such as routing or scheduling, but very generic and powerful\nwithin their domain. However, they also need more explanation on the correct\nusage.\n\n- [Tour Constraints](#04-modelling-circuit): `add_circuit`,\n  `add_multiple_circuit`, `add_reservoir_constraint_with_active`\n- [Intervals](#04-modelling-intervals): `new_interval_var`,\n  `new_interval_var_series`, `new_fixed_size_interval_var`,\n  `new_optional_interval_var`, `new_optional_interval_var_series`,\n  `new_optional_fixed_size_interval_var`,\n  `new_optional_fixed_size_interval_var_series`,\n  `add_no_overlap`,`add_no_overlap_2d`, `add_cumulative`\n- [Automaton Constraints](#04-modelling-automaton): `add_automaton`\n- [Reservoir Constraints](#04-modelling-reservoir): `add_reservoir_constraint`,\n- [Piecewise Linear Constraints](#04-modelling-pwl): Not officially part of\n  CP-SAT, but we provide some free copy\u0026pasted code to do it.\n\n\u003ca name=\"04-modelling-circuit\"\u003e\u003c/a\u003e\n\n### Circuit/Tour-Constraints\n\nRoutes and tours are essential in addressing optimization challenges across\nvarious fields, far beyond traditional routing issues. For example, in DNA\nsequencing, optimizing the sequence in which DNA fragments are assembled is\ncrucial, while in scientific research, methodically ordering the reconfiguration\nof experiments can greatly reduce operational costs and downtime. The\n`add_circuit` and `add_multiple_circuit` constraints in CP-SAT allow you to\neasily model various scenarios. These constraints extend beyond the classical\n[Traveling Salesman Problem (TSP)](https://en.wikipedia.org/wiki/Travelling_salesman_problem),\nallowing for solutions where not every node needs to be visited and\naccommodating scenarios that require multiple disjoint sub-tours. This\nadaptability makes them invaluable for a broad spectrum of practical problems\nwhere the sequence and arrangement of operations critically impact efficiency\nand outcomes.\n\n|                         ![TSP Example](https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/images/optimal_tsp.png)                         |\n| :-------------------------------------------------------------------------------------------------------------------------------------------------: |\n| The Traveling Salesman Problem (TSP) asks for the shortest possible route that visits every vertex exactly once and returns to the starting vertex. |\n\nThe Traveling Salesman Problem is one of the most famous and well-studied\ncombinatorial optimization problems. It is a classic example of a problem that\nis easy to understand, common in practice, but hard to solve. It also has a\nspecial place in the history of optimization, as many techniques that are now\nused generally were first developed for the TSP. If you have not done so yet, I\nrecommend watching\n[this talk by Bill Cook](https://www.youtube.com/watch?v=5VjphFYQKj8), or even\nreading the book\n[In Pursuit of the Traveling Salesman](https://press.princeton.edu/books/paperback/9780691163529/in-pursuit-of-the-traveling-salesman).\n\n\u003e [!TIP]\n\u003e\n\u003e If your problem is specifically the Traveling Salesperson Problem (TSP), you\n\u003e might find the\n\u003e [Concorde solver](https://www.math.uwaterloo.ca/tsp/concorde.html)\n\u003e particularly effective. For problems closely related to the TSP, a Mixed\n\u003e Integer Programming (MIP) solver may be more suitable, as many TSP variants\n\u003e yield strong linear programming relaxations that MIP solvers can efficiently\n\u003e exploit. Additionally, consider\n\u003e [OR-Tools Routing](https://developers.google.com/optimization/routing) if\n\u003e routing constitutes a significant aspect of your problem. However, for\n\u003e scenarios where variants of the TSP are merely a component of a larger\n\u003e problem, utilizing CP-SAT with the `add_circuit` or `add_multiple_circuit`\n\u003e constraints can be very beneficial.\n\n|                                                                                                                                                        ![TSP BnB Example](https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/images/tsp_bnb_improved.png)                                                                                                                                                         |\n| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |\n| This example shows why Mixed Integer Programming solvers are so good in solving the TSP. The linear relaxation (at the top) is already very close to the optimal solution. By branching, i.e., trying 0 and 1, on just two fractional variables, we not only find the optimal solution but can also prove optimality. The example was generated with the [DIY TSP Solver](https://www.math.uwaterloo.ca/tsp/D3/bootQ.html). |\n\n#### `add_circuit`\n\nThe `add_circuit` constraint is utilized to solve circuit problems within\ndirected graphs, even allowing loops. It operates by taking a list of triples\n`(u,v,var)`, where `u` and `v` denote the source and target vertices,\nrespectively, and `var` is a Boolean variable that indicates if an edge is\nincluded in the solution. The constraint ensures that the edges marked as `True`\nform a single circuit visiting each vertex exactly once, aside from vertices\nwith a loop set as `True`. Vertex indices should start at 0 and must not be\nskipped to avoid isolation and infeasibility in the circuit.\n\nHere is an example using the CP-SAT solver to address a directed Traveling\nSalesperson Problem (TSP):\n\n```python\nfrom ortools.sat.python import cp_model\n\n# Directed graph with weighted edges\ndgraph = {(0, 1): 13, (1, 0): 17, ...(2, 3): 27}\n\n# Initialize CP-SAT model\nmodel = cp_model.CpModel()\n\n# Boolean variables for each edge\nedge_vars = {(u, v): model.new_bool_var(f\"e_{u}_{v}\") for (u, v) in dgraph.keys()}\n\n# Circuit constraint for a single tour\nmodel.add_circuit([(u, v, var) for (u, v), var in edge_vars.items()])\n\n# Objective function to minimize total cost\nmodel.minimize(sum(dgraph[(u, v)] * x for (u, v), x in edge_vars.items()))\n\n# Solve model\nsolver = cp_model.CpSolver()\nstatus = solver.solve(model)\nif status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n    tour = [(u, v) for (u, v), x in edge_vars.items() if solver.value(x)]\n    print(\"Tour:\", tour)\n\n# Output: [(0, 1), (2, 0), (3, 2), (1, 3)], i.e., 0 -\u003e 1 -\u003e 3 -\u003e 2 -\u003e 0\n```\n\nThis constraint can be adapted for paths by adding a virtual enforced edge that\ncloses the path into a circuit, such as `(3, 0, 1)` for a path from vertex 0 to\nvertex 3.\n\n#### Creative usage of `add_circuit`\n\nThe `add_circuit` constraint can be creatively adapted to solve various related\nproblems. While there are more efficient algorithms for solving the Shortest\nPath Problem, let us demonstrate how to adapt the `add_circuit` constraint for\neducational purposes.\n\n```python\nfrom ortools.sat.python import cp_model\n\n# Define a weighted, directed graph with edge costs\ndgraph = {(0, 1): 13, (1, 0): 17, ...(2, 3): 27}\n\nsource_vertex = 0\ntarget_vertex = 3\n\n# Add zero-cost loops for vertices not being the source or target\nfor v in [1, 2]:\n    dgraph[(v, v)] = 0\n\n# Initialize CP-SAT model and variables\nmodel = cp_model.CpModel()\nedge_vars = {(u, v): model.new_bool_var(f\"e_{u}_{v}\") for (u, v) in dgraph}\n\n# Define the circuit including a pseudo-edge from target to source\ncircuit = [(u, v, var) for (u, v), var in edge_vars.items()] + [\n    (target_vertex, source_vertex, 1)\n]\nmodel.add_circuit(circuit)\n\n# Minimize total cost\nmodel.minimize(sum(dgraph[(u, v)] * x for (u, v), x in edge_vars.items()))\n\n# Solve and extract the path\nsolver = cp_model.CpSolver()\nstatus = solver.solve(model)\nif status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n    path = [(u, v) for (u, v), x in edge_vars.items() if solver.value(x) and u != v]\n    print(\"Path:\", path)\n\n# Output: [(0, 1), (1, 3)], i.e., 0 -\u003e 1 -\u003e 3\n```\n\nThis approach showcases the flexibility of the `add_circuit` constraint for\nvarious tour and path problems. Explore further examples:\n\n- [Budget constrained tours](https://github.com/d-krupke/cpsat-primer/blob/main/examples/add_circuit_budget.py):\n  Optimize the largest possible tour within a specified budget.\n- [Multiple tours](https://github.com/d-krupke/cpsat-primer/blob/main/examples/add_circuit_multi_tour.py):\n  Solve for $k$ minimal tours covering all vertices.\n\n#### `add_multiple_circuit`\n\nYou can model multiple disjoint tours using several `add_circuit` constraints,\nas demonstrated in\n[this example](https://github.com/d-krupke/cpsat-primer/blob/main/examples/add_circuit_multi_tour.py).\nIf all tours share a common depot (vertex 0), the `add_multiple_circuit`\nconstraint is an alternative. However, this constraint does not allow you to\nspecify the number of tours, nor can it determine to which tour a particular\nedge belongs. Therefore, the `add_circuit` constraint is often a superior\nchoice. Although the arguments for both constraints are identical, vertex 0\nserves a unique role as the depot where all tours commence and conclude.\n\n#### Performance of `add_circuit` for the TSP\n\nThe table below displays the performance of the CP-SAT solver on various\ninstances of the TSPLIB, using the `add_circuit` constraint, under a 90-second\ntime limit. The performance can be considered reasonable, but can be easily\nbeaten by a Mixed Integer Programming solver.\n\n| Instance | # vertices | runtime | lower bound | objective | opt. gap |\n| :------- | ---------: | ------: | ----------: | --------: | -------: |\n| att48    |         48 |    0.47 |       33522 |     33522 |        0 |\n| eil51    |         51 |    0.69 |         426 |       426 |        0 |\n| st70     |         70 |     0.8 |         675 |       675 |        0 |\n| eil76    |         76 |    2.49 |         538 |       538 |        0 |\n| pr76     |         76 |   54.36 |      108159 |    108159 |        0 |\n| kroD100  |        100 |    9.72 |       21294 |     21294 |        0 |\n| kroC100  |        100 |    5.57 |       20749 |     20749 |        0 |\n| kroB100  |        100 |     6.2 |       22141 |     22141 |        0 |\n| kroE100  |        100 |    9.06 |       22049 |     22068 |        0 |\n| kroA100  |        100 |    8.41 |       21282 |     21282 |        0 |\n| eil101   |        101 |    2.24 |         629 |       629 |        0 |\n| lin105   |        105 |    1.37 |       14379 |     14379 |        0 |\n| pr107    |        107 |     1.2 |       44303 |     44303 |        0 |\n| pr124    |        124 |    33.8 |       59009 |     59030 |        0 |\n| pr136    |        136 |   35.98 |       96767 |     96861 |        0 |\n| pr144    |        144 |   21.27 |       58534 |     58571 |        0 |\n| kroB150  |        150 |   58.44 |       26130 |     26130 |        0 |\n| kroA150  |        150 |   90.94 |       26498 |     26977 |       2% |\n| pr152    |        152 |   15.28 |       73682 |     73682 |        0 |\n| kroA200  |        200 |   90.99 |       29209 |     29459 |       1% |\n| kroB200  |        200 |   31.69 |       29437 |     29437 |        0 |\n| pr226    |        226 |   74.61 |       80369 |     80369 |        0 |\n| gil262   |        262 |   91.58 |        2365 |      2416 |       2% |\n| pr264    |        264 |   92.03 |       49121 |     49512 |       1% |\n| pr299    |        299 |   92.18 |       47709 |     49217 |       3% |\n| linhp318 |        318 |   92.45 |       41915 |     52032 |      19% |\n| lin318   |        318 |   92.43 |       41915 |     52025 |      19% |\n| pr439    |        439 |   94.22 |      105610 |    163452 |      35% |\n\nThere are two prominent formulations to model the Traveling Salesman Problem\n(TSP) without an `add_circuit` constraint: the\n[Dantzig-Fulkerson-Johnson (DFJ) formulation](https://en.wikipedia.org/wiki/Travelling_salesman_problem#Dantzig%E2%80%93Fulkerson%E2%80%93Johnson_formulation)\nand the\n[Miller-Tucker-Zemlin (MTZ) formulation](https://en.wikipedia.org/wiki/Travelling_salesman_problem#Miller%E2%80%93Tucker%E2%80%93Zemlin_formulation[21]).\nThe DFJ formulation is generally regarded as more efficient due to its stronger\nlinear relaxation. However, it requires lazy constraints, which are not\nsupported by the CP-SAT solver. When implemented without lazy constraints, the\nperformance of the DFJ formulation is comparable to that of the MTZ formulation\nin CP-SAT. Nevertheless, both formulations perform significantly worse than the\n`add_circuit` constraint. This indicates the superiority of using the\n`add_circuit` constraint for handling tours and paths in such problems. Unlike\nend users, the `add_circuit` constraint can utilize lazy constraints internally,\noffering a substantial advantage in solving the TSP.\n\n\u003ca name=\"04-modelling-intervals\"\u003e\u003c/a\u003e\n\n### Scheduling and Packing with Intervals\n\nA special case of variables are the interval variables, that allow to model\nintervals, i.e., a span of some length with a start and an end. There are fixed\nlength intervals, flexible length intervals, and optional intervals to model\nvarious use cases. These intervals become interesting in combination with the\nno-overlap constraints for 1D and 2D. We can use this for geometric packing\nproblems, scheduling problems, and many other problems, where we have to prevent\noverlaps between intervals. These variables are special because they are\nactually not a variable, but a container that bounds separately defined start,\nlength, and end variables.\n\nThere are four types of interval variables: `new_interval_var`,\n`new_fixed_size_interval_var`, `new_optional_interval_var`, and\n`new_optional_fixed_size_interval_var`. The `new_optional_interval_var` is the\nmost expressive but also the most expensive, while `new_fixed_size_interval_var`\nis the least expressive and the easiest to optimize. All four types take a\n`start=` variable. Intervals with `fixed_size` in their name require a constant\n`size=` argument defining the interval length. Otherwise, the `size=` argument\ncan be a variable in combination with an `end=` variable, which complicates the\nsolution. Intervals with `optional` in their name include an `is_present=`\nargument, a boolean indicating if the interval is present. The no-overlap\nconstraints, discussed later, apply only to intervals that are present, allowing\nfor modeling problems with multiple resources or optional tasks. Instead of a\npure integer variable, all arguments also accept an affine expression, e.g.,\n`start=5*start_var+3`.\n\n```python\nmodel = cp_model.CpModel()\n\nstart_var = model.new_int_var(0, 100, \"start\")\nlength_var = model.new_int_var(10, 20, \"length\")\nend_var = model.new_int_var(0, 100, \"end\")\nis_present_var = model.new_bool_var(\"is_present\")\n\n# creating an interval whose length can be influenced by a variable (more expensive)\nflexible_interval = model.new_interval_var(\n    start=start_var, size=length_var, end=end_var, name=\"flexible_interval\"\n)\n\n# creating an interval of fixed length\nfixed_interval = model.new_fixed_size_interval_var(\n    start=start_var,\n    size=10,  # needs to be a constant\n    name=\"fixed_interval\",\n)\n\n# creating an interval that can be present or not and whose length can be influenced by a variable (most expensive)\noptional_interval = model.new_optional_interval_var(\n    start=start_var,\n    size=length_var,\n    end=end_var,\n    is_present=is_present_var,\n    name=\"optional_interval\",\n)\n\n# creating an interval that can be present or not\noptional_fixed_interval = model.new_optional_fixed_size_interval_var(\n    start=start_var,\n    size=10,  # needs to be a constant\n    is_present=is_present_var,\n    name=\"optional_fixed_interval\",\n)\n```\n\nThese interval variables are not useful on their own, as we could have easily\nachieved the same with a simple linear constraint. However, CP-SAT provides\nspecial constraints for these interval variables, that would actually be much\nharder to model by hand and are also much more efficient.\n\nCP-SAT offers the following three constraints for intervals:\n`add_no_overlap`,`add_no_overlap_2d`, `add_cumulative`. `add_no_overlap` is used\nto prevent overlaps between intervals on a single dimension, e.g., time.\n`add_no_overlap_2d` is used to prevent overlaps between intervals on two\ndimensions, e.g., for packing rectangles. `add_cumulative` is used to model a\nresource constraint, where the sum of the demands of the overlapping intervals\nmust not exceed the capacity of the resource.\n\nThe `add_no_overlap` constraints takes a list of (optional) interval variables\nand ensures that no two present intervals overlap.\n\n```python\nmodel.add_no_overlap(\n    interval_vars=[\n        flexible_interval,\n        fixed_interval,\n        optional_interval,\n        optional_fixed_interval,\n        # ...\n    ]\n)\n```\n\nThe `add_no_overlap_2d` constraints takes two lists of (optional) interval and\nensures that for every `i` and `j` either `x_intervals[i]` and `x_intervals[j]`\nor `y_intervals[i]` and `y_intervals[j]` do not overlap. Thus, both lists must\nhave the same length as `x_intervals[i]` and `y_intervals[i]` are considered\nbelonging together. If either `x_intervals[i]` or `y_intervals[i]` are optional,\nthe whole object is optional.\n\n```python\nmodel.add_no_overlap_2d(\n    x_intervals=[\n        flexible_interval,\n        fixed_interval,\n        optional_interval,\n        optional_fixed_interval,\n        # ...\n    ],\n    y_intervals=[\n        flexible_interval,\n        fixed_interval,\n        optional_interval,\n        optional_fixed_interval,\n        # ...\n    ],\n)\n```\n\nThe `add_cumulative` constraint is used to model a resource constraint, where\nthe sum of the demands of the overlapping intervals must not exceed the capacity\nof the resource. An example could be scheduling the usage of certain energy\nintensive machines, where the sum of the energy demands must not exceed the\ncapacity of the power grid. It takes a list of intervals, a list of demands, and\na capacity variable. The list of demands must have the same length as the list\nof intervals, as the demands of the intervals are matched by index. As capacity\nand demands can be variables (or affine expressions), quite complex resource\nconstraints can be modeled.\n\n```python\ndemand_vars = [model.new_int_var(1, 10, f\"demand_{i}\") for i in range(4)]\ncapacity_var = model.new_int_var(1, 100, \"capacity\")\nmodel.add_cumulative(\n    intervals=[\n        flexible_interval,\n        fixed_interval,\n        optional_interval,\n        optional_fixed_interval,\n    ],\n    demands=demand_vars,\n    capacity=capacity_var,\n)\n```\n\n\u003e [!WARNING]\n\u003e\n\u003e Do not directly jump to intervals when you have a scheduling problem.\n\u003e Intervals are great if you actually have a somewhat continuous time or space\n\u003e that you need to schedule. If you have a more discrete problem, such as a\n\u003e scheduling problem with a fixed number of slots, you can often model this\n\u003e problem much more efficiently using simple Boolean variables and constraints.\n\u003e Especially if you can use domain knowledge to find clusters of meetings that\n\u003e cannot overlap, this can be much more efficient. If the scheduling is\n\u003e dominated by the transitions, your scheduling problem may actually be a\n\u003e routing problems, for which the `add_circuit` constraint is more suitable.\n\nLet us examine a few examples of how to use these constraints effectively.\n\n#### Scheduling for a Conference Room with Intervals\n\nAssume we have a conference room and need to schedule several meetings. Each\nmeeting has a fixed length and a range of possible start times. The time slots\nare in 5-minute intervals starting at 8:00 AM and ending at 6:00 PM. Thus, there\nare $10 \\times 12 = 120$ time slots, and we can use a simple integer variable to\nmodel the start time. With fixed meeting lengths, we can use the\n`new_fixed_size_interval_var` to model the intervals. The `add_no_overlap`\nconstraint ensures no two meetings overlap, and domains for the start time can\nmodel the range of possible start times.\n\nTo handle input data, let us define a `namedtuple` to store the meeting and two\nfunctions to convert between time and index.\n\n```python\n# Convert time to index and back\ndef t_to_idx(hour, minute):\n    return (hour - 8) * 12 + minute // 5\n\n\ndef idx_to_t(time_idx):\n    hour = 8 + time_idx // 12\n    minute = (time_idx % 12) * 5\n    return f\"{hour}:{minute:02d}\"\n\n\n# Define meeting information using namedtuples\nMeetingInfo = namedtuple(\"MeetingInfo\", [\"start_times\", \"duration\"])\n```\n\nThen let us create a few meetings we want to schedule.\n\n```python\n# Meeting definitions\nmeetings = {\n    \"meeting_a\": MeetingInfo(\n        start_times=[\n            [t_to_idx(8, 0), t_to_idx(12, 0)],\n            [t_to_idx(16, 0), t_to_idx(17, 0)],\n        ],\n        duration=120 // 5,  # 2 hours\n    ),\n    \"meeting_b\": MeetingInfo(\n        start_times=[\n            [t_to_idx(10, 0), t_to_idx(12, 0)],\n        ],\n        duration=30 // 5,  # 30 minutes\n    ),\n    \"meeting_c\": MeetingInfo(\n        start_times=[\n            [t_to_idx(16, 0), t_to_idx(17, 0)],\n        ],\n        duration=15 // 5,  # 15 minutes\n    ),\n    \"meeting_d\": MeetingInfo(\n        start_times=[\n            [t_to_idx(8, 0), t_to_idx(10, 0)],\n            [t_to_idx(12, 0), t_to_idx(14, 0)],\n        ],\n        duration=60 // 5,  # 1 hour\n    ),\n}\n```\n\nNow we can create the CP-SAT model and add the intervals and constraints.\n\n```python\n# Create a new CP-SAT model\nmodel = cp_model.CpModel()\n\n# Create start time variables for each meeting\nstart_time_vars = {\n    meeting_name: model.new_int_var_from_domain(\n        cp_model.Domain.from_intervals(meeting_info.start_times),\n        f\"start_{meeting_name}\",\n    )\n    for meeting_name, meeting_info in meetings.items()\n}\n\n# Create interval variables for each meeting\ninterval_vars = {\n    meeting_name: model.new_fixed_size_interval_var(\n        start=start_time_vars[meeting_name],\n        size=meeting_info.duration,\n        name=f\"interval_{meeting_name}\",\n    )\n    for meeting_name, meeting_info in meetings.items()\n}\n\n# Ensure that now two meetings overlap\nmodel.add_no_overlap(list(interval_vars.values()))\n```\n\nAnd finally, we can solve the model and extract the solution.\n\n```python\n# Solve the model\nsolver = cp_model.CpSolver()\nstatus = solver.solve(model)\n\n# Extract and print the solution\nscheduled_times = {}\nif status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n    for meeting_name in meetings:\n        start_time = solver.value(start_time_vars[meeting_name])\n        scheduled_times[meeting_name] = start_time\n        print(f\"{meeting_name} starts at {idx_to_t(start_time)}\")\nelse:\n    print(\"No feasible solution found.\")\n```\n\nDoing some quick magic with matplotlib, we can visualize the schedule.\n\n|                ![Schedule](https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/images/scheduling_example.png)                |\n| :-----------------------------------------------------------------------------------------------------------------------------------: |\n| A possible non-overlapping schedule for the above example. The instance is quite simple, but you could try adding some more meetings. |\n\n#### Scheduling for Multiple Resources with Optional Intervals\n\nNow, imagine we have multiple resources, such as multiple conference rooms, and\nwe need to schedule the meetings such that no two meetings overlap in the same\nroom. This can be modeled with optional intervals, where the intervals exist\nonly if the meeting is scheduled in the room. The `add_no_overlap` constraint\nensures that no two meetings overlap in the same room.\n\nBecause we now have two rooms, we need to create a more challenging instance\nfirst. Otherwise, the solver may not need to use both rooms. We do this by\nsimply adding more and longer meetings.\n\n```python\n# Meeting definitions\nmeetings = {\n    \"meeting_a\": MeetingInfo(\n        start_times=[\n            [t_to_idx(8, 0), t_to_idx(12, 0)],\n            [t_to_idx(16, 0), t_to_idx(16, 0)],\n        ],\n        duration=120 // 5,\n    ),\n    \"meeting_b\": MeetingInfo(\n        start_times=[[t_to_idx(10, 0), t_to_idx(12, 0)]], duration=240 // 5\n    ),\n    \"meeting_c\": MeetingInfo(\n        start_times=[[t_to_idx(16, 0), t_to_idx(17, 0)]], duration=30 // 5\n    ),\n    \"meeting_d\": MeetingInfo(\n        start_times=[\n            [t_to_idx(8, 0), t_to_idx(10, 0)],\n            [t_to_idx(12, 0), t_to_idx(14, 0)],\n        ],\n        duration=60 // 5,\n    ),\n    \"meeting_e\": MeetingInfo(\n        start_times=[[t_to_idx(10, 0), t_to_idx(12, 0)]], duration=120 // 5\n    ),\n    \"meeting_f\": MeetingInfo(\n        start_times=[[t_to_idx(14, 0), t_to_idx(14, 0)]], duration=240 // 5\n    ),\n    \"meeting_g\": MeetingInfo(\n        start_times=[[t_to_idx(14, 0), t_to_idx(16, 0)]], duration=120 // 5\n    ),\n}\n```\n\nThis time, we need to create an interval variable for each room and meeting, as\nwell as a Boolean variable indicating if the meeting is scheduled in the room.\nWe cannot use the same interval variable for multiple rooms, as otherwise the\ninterval would be present in both rooms.\n\n```python\n# Create the model\nmodel = cp_model.CpModel()\n\n# Create start time and room variables\nstart_time_vars = {\n    name: model.new_int_var_from_domain(\n        cp_model.Domain.from_intervals(info.start_times), f\"start_{name}\"\n    )\n    for name, info in meetings.items()\n}\n\nrooms = [\"room_a\", \"room_b\"]\nroom_vars = {\n    name: {room: model.new_bool_var(f\"{name}_in_{room}\") for room in rooms}\n    for name in meetings\n}\n\n# Create interval variables and add no-overlap constraint\ninterval_vars = {\n    name: {\n        # We need a separate interval for each room\n        room: model.new_optional_fixed_size_interval_var(\n            start=start_time_vars[name],\n            size=info.duration,\n            is_present=room_vars[name][room],\n            name=f\"interval_{name}_in_{room}\",\n        )\n        for room in rooms\n    }\n    for name, info in meetings.items()\n}\n```\n\nNow","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-krupke%2Fcpsat-primer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd-krupke%2Fcpsat-primer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-krupke%2Fcpsat-primer/lists"}