[{"data":1,"prerenderedAt":1927},["ShallowReactive",2],{"technical-blog-page-content":3,"technical-blog-post-how-audacia-implements-google-s-or-tools-into-projects":30,"technical-blog-author-how-audacia-implements-google-s-or-tools-into-projects":1915},{"id":4,"title":5,"author":6,"blogTags":6,"body":7,"customExcerpt":6,"date":6,"description":11,"excerpt":6,"extension":14,"h1":15,"image":6,"meta":16,"navigation":17,"path":18,"readingStats":19,"seo":24,"stem":28,"__hash__":29},"technicalBlogPage\u002Ftechnical-blog-page.md","Technical Blog Page",null,{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"md","Technical insights, stories and opinions from our team of consultants, analysts, developers and testers.",{},true,"\u002Ftechnical-blog-page",{"text":20,"minutes":21,"time":22,"words":23},"1 min read",0.29,17400,58,{"title":25,"description":26,"keywords":27},"Technology Insights | Audacia Digital Transformation","Technical insights, stories and opinions from leading software development company Audacia's teams of consultants, analysts, developers and testers.","Digital transformation technology, Technology blog, Technology insights, software development blog, software engineering blog","technical-blog-page","XD1gWfEur9Mu64WptQ5pF-e7Ej60LGiq7sRC58wOAeQ",{"id":31,"title":32,"author":33,"blogTags":34,"body":37,"customExcerpt":1897,"date":1898,"description":1899,"excerpt":1900,"extension":14,"h1":6,"image":1901,"meta":1902,"navigation":17,"path":1903,"readingStats":1904,"seo":1909,"stem":1913,"__hash__":1914},"technicalBlog\u002Ftechnical-blog\u002Fhow-audacia-implements-google-s-or-tools-into-projects.md","How Audacia implements Google OR-Tools into software development projects","Owen Lacey",[35,36],"OR-Tools","NET",{"type":8,"value":38,"toc":1881},[39,48,53,72,76,79,92,95,99,102,105,113,127,142,150,154,157,166,169,172,175,179,182,186,189,192,195,228,235,261,264,269,287,423,431,435,452,477,484,582,606,612,622,644,651,655,658,780,786,797,808,811,814,818,832,846,914,950,953,1099,1122,1127,1134,1138,1141,1147,1150,1171,1175,1181,1233,1247,1251,1262,1358,1366,1376,1550,1554,1557,1622,1626,1629,1685,1689,1692,1696,1699,1860,1863,1866,1870,1877],[40,41,42,43,47],"p",{},"The vast majority of business problems relate to minimising costs. \nWith many possible solutions available, the ‘best’ option could be defined as the one which maximises one of these desired outcomes . How we evaluate these solutions is often based on a preconceived notion about characteristics that make them ‘good’. But what if you don't know how you would have come to that solution, and how do you know which solution is 'best'?\nThe objective of using Google OR-Tools is to find a solution to your problem based on the parameters that ",[44,45,46],"em",{},"you"," define. This can either be to find the single ‘best’ solution or to find a feasible solution that does not violate any of your defined constraints. In this article, we will implement a simple optimisation model using Google OR-Tools by solving a classic Vehicle Routing Problem (VRP).",[49,50,52],"h2",{"id":51},"not-knowing-the-how","Not knowing the 'how'",[40,54,55,56,59,60,63,64,67,68,71],{},"The main challenge in implementing a successful business process model is reducing a business problem into ",[44,57,58],{},"rules"," (or ",[44,61,62],{},"constraints","). If you can represent a problem with a list of criteria that must be met, you are 90% of the way there in terms of being able to solve that problem - the key area of focus can be on the ",[44,65,66],{},"what",", rather than the ",[44,69,70],{},"how",".",[49,73,75],{"id":74},"use-cases","Use cases",[40,77,78],{},"As mentioned earlier, a large proportion of modern business problems can be presented as optimisation problems, such as:",[80,81,82,86,89],"ul",{},[83,84,85],"li",{},"Assigning repair people to customers requiring home visits, ensuring all customers are visited in a specific time period.",[83,87,88],{},"Loading a fleet of vehicles with products minimising the number of vehicles needed.",[83,90,91],{},"Creating a weekly schedule of shifts for a workforce ensuring their minimum weekly hours are met.",[40,93,94],{},"Examples like these are well documented on the Google OR-Tools website. ",[49,96,98],{"id":97},"feasible-vs-optimal","Feasible vs optimal",[40,100,101],{},"Not all problems require a perfect solution. In most cases, merely finding a more efficient solution is enough. A good real-world example of this is scheduling shifts for employees.  ",[40,103,104],{},"Shift scheduling can quite easily turn into an optimisation problem when you start taking into account preferences. For example:",[80,106,107,110],{},[83,108,109],{},"Employee A would prefer not to work on weekends.",[83,111,112],{},"Ideally, at least 2 people would work on a Friday.",[40,114,115,116,119,120,122,123,126],{},"In OR-Tools, words like 'ideally' and 'prefer' translate into what we call a ",[44,117,118],{},"soft constraint",". By ",[44,121,118],{}," we mean that if these criteria were not to be met, it wouldn't be a deal-breaker, especially if it were the only feasible solution. Models which take into account these preferences will ",[44,124,125],{},"score"," a solution based on how many times this criterion was (or was not) met.",[40,128,129,130,133,134,137,138,141],{},"A ",[44,131,132],{},"feasible"," solution is one such that all hard (or ",[44,135,136],{},"mandatory","  ) constraints have been met. This solution becomes ",[44,139,140],{},"optimal"," when the score of a solution is better than all other feasible solutions. In contrast to soft constraints, a hard constraint for this kind of problem could be any of the following:",[80,143,144,147],{},[83,145,146],{},"Employee B cannot work on Tuesday evenings.",[83,148,149],{},"No more than 4 people can work at the same time.",[49,151,153],{"id":152},"about-or-tools","About OR-Tools",[40,155,156],{},"OR-Tools is Google's award-winning suite of tools which allow a developer to solve problems using combinatorial optimisation. One of its primary functions is to provide an easy-to-use model building API as a wrapper around industry-recognised solvers. Rather than siding with a \"one solver fits all\" approach, developers have the option to specify which solver to use (should they have a preference) or to let the software choose the most appropriate.",[40,158,159,160,165],{},"The Google OR-Tools project is open source and Audacia have even helped to contribute to it (",[161,162,164],"a",{"href":163},"https:\u002F\u002Fgithub.com\u002Fgoogle\u002For-tools\u002Fcommit\u002Fbcb3886be084ea90027c4efe37dc46d75dc20ce6","here",").",[40,167,168],{},"The solvers implemented by OR-Tools range from commercial (e.g. IBM's CPLEX solver) to open source (e.g. SCIP). Google has also written its own award-winning CP-SAT solver for solving constraint programming problems.",[40,170,171],{},"The Google OR-Tools website, source code, and forums are littered with examples of real-world problem-solving. As a result, it does not usually take long to find an existing use-case similar to your own.",[40,173,174],{},"OR-Tools can be implemented in projects written in Python, Java, C++, and most importantly for us, .NET. This flexibility allows .NET developers to write models in C# as opposed to similar software packages like OptaPlanner (Java) and GECODE (C++).",[49,176,178],{"id":177},"using-or-tools-in-net","Using OR-Tools in .NET",[40,180,181],{},"Google OR-Tools can be used in a .NET project via a NuGet package. The package automatically adds a DLL reference to your project. Depending on your operating system, you will also need to add a reference to the Windows\u002FOSX\u002FLinux runtime. ",[49,183,185],{"id":184},"worked-example-assigning-repair-people","Worked example - Assigning repair people",[40,187,188],{},"This is a Vehicle Routing Problem (VRP), not dissimilar to a Travelling Salesman Problem (TSP), which you may have come across if you have spent any time with optimisation problems.",[40,190,191],{},"Suppose we are a company that fixes laptops via home visits. Each day, this company has a list of customers to visit and must therefore assign employees (the repair people) to each customer. We would like to minimise the distance travelled by the employees so that they are reducing the amount of fuel to expense and are spending less time on the road as a result. As you can imagine, finding an optimal solution for this problem could save a lot of money on a daily basis.",[40,193,194],{},"Once we have a working example of assigning our repair people, we will introduce time windows to demonstrate how to incorporate real-life constraints into a problem like this.",[40,196,197,198,201,202,205,206,209,210,214,215,221,222,227],{},"First, consider a graph with the customer locations as the ",[44,199,200],{},"nodes"," and the distance between each customer as the ",[44,203,204],{},"edges",". In graph theory, this is referred to as a ",[44,207,208],{},"complete graph",". A complete graph with ",[211,212,213],"code",{},"n"," nodes is referred to as ",[44,216,217,218],{},"K",[219,220,213],"sub",{},". Below is an example of how we can represent a ",[44,223,217,224],{},[219,225,226],{},"4"," graph as a matrix.",[40,229,230],{},[231,232],"img",{"src":233,"alt":234},"\u002Fimg\u002Ftechnical-blog\u002Fmatrix.png#fullwidth","matrix.png",[40,236,237],{},[238,239,240,241,244,245,248,249,252,253,256,257,260],"strong",{},"NB this example is ",[44,242,243],{},"undirected"," i.e ",[211,246,247],{},"i => j == j => i"," for all ",[211,250,251],{},"i",",",[211,254,255],{},"j"," (in other words, the matrix is ",[44,258,259],{},"symmetric","). This may not be the case in real-life scenarios.",[40,262,263],{},"In graph theory, this problem is equivalent to finding the Hamiltonian Path (i.e. a path visiting each node exactly once) of minimum weight.",[265,266,268],"h3",{"id":267},"step-1-distance-matrix","Step 1: Distance matrix",[40,270,271,272,274,275,277,278,281,282,284,285,71],{},"The first step is to create an ",[211,273,213],{}," x ",[211,276,213],{}," matrix where ",[211,279,280],{},"matrix[i,j]"," returns the distance between point ",[211,283,251],{}," and ",[211,286,255],{},[288,289,293],"pre",{"className":290,"code":291,"language":292,"meta":11,"style":11},"language-csharp shiki shiki-themes github-light","var distances = new long[,] {\n    { 0, 2, 8, 10 },\n    { 2, 0, 12, 6 },\n    { 8, 12, 0, 5 }.\n    { 10, 6, 5, 0 }\n};\n","csharp",[211,294,295,321,349,372,395,417],{"__ignoreMap":11},[296,297,300,304,308,311,314,317],"span",{"class":298,"line":299},"line",1,[296,301,303],{"class":302},"sD7c4","var",[296,305,307],{"class":306},"s7eDp"," distances",[296,309,310],{"class":302}," =",[296,312,313],{"class":302}," new",[296,315,316],{"class":302}," long",[296,318,320],{"class":319},"sgsFI","[,] {\n",[296,322,323,326,330,333,336,338,341,343,346],{"class":298,"line":12},[296,324,325],{"class":319},"    { ",[296,327,329],{"class":328},"sYu0t","0",[296,331,332],{"class":319},", ",[296,334,335],{"class":328},"2",[296,337,332],{"class":319},[296,339,340],{"class":328},"8",[296,342,332],{"class":319},[296,344,345],{"class":328},"10",[296,347,348],{"class":319}," },\n",[296,350,352,354,356,358,360,362,365,367,370],{"class":298,"line":351},3,[296,353,325],{"class":319},[296,355,335],{"class":328},[296,357,332],{"class":319},[296,359,329],{"class":328},[296,361,332],{"class":319},[296,363,364],{"class":328},"12",[296,366,332],{"class":319},[296,368,369],{"class":328},"6",[296,371,348],{"class":319},[296,373,375,377,379,381,383,385,387,389,392],{"class":298,"line":374},4,[296,376,325],{"class":319},[296,378,340],{"class":328},[296,380,332],{"class":319},[296,382,364],{"class":328},[296,384,332],{"class":319},[296,386,329],{"class":328},[296,388,332],{"class":319},[296,390,391],{"class":328},"5",[296,393,394],{"class":319}," }.\n",[296,396,398,400,402,404,406,408,410,412,414],{"class":298,"line":397},5,[296,399,325],{"class":319},[296,401,345],{"class":328},[296,403,332],{"class":319},[296,405,369],{"class":328},[296,407,332],{"class":319},[296,409,391],{"class":328},[296,411,332],{"class":319},[296,413,329],{"class":328},[296,415,416],{"class":319}," }\n",[296,418,420],{"class":298,"line":419},6,[296,421,422],{"class":319},"};\n",[40,424,425,426,430],{},"The data is represented in a matrix so that we can quickly determine the pre-calculated distance between two locations. This helps to keep our ",[161,427,429],{"href":428},"#distance-callback","distance callback"," logic clear and concise.\nThe distance between two locations can either be a simple Euclidean distance (i.e. as the crow flies), or something more complicated; perhaps using a routing API like google itself.",[265,432,434],{"id":433},"step-2-create-the-index-manager","Step 2: Create the index manager",[40,436,437,438,441,442,445,446,448,449,451],{},"A lot of variables are added when creating a model and adding constraints. Each location (node) has  both a ",[211,439,440],{},"nodeIndex"," and a ",[211,443,444],{},"variableIndex",". Only the ",[211,447,444],{},"'s are used for solving, whereas the ",[211,450,440],{},"'s are used so that we know which locations have been chosen.",[40,453,454,455,457,458,460,461,463,464,466,467,470,471,473,474,476],{},"Concretely,\n1. We add locations as nodes, creating the ",[211,456,440],{},"'s.\n1. The solver registers each node and creates variables representing them (the ",[211,459,444],{},"'s).\n1. The solver uses the ",[211,462,444],{},"'s to create a solution, returning an ordered list of the ",[211,465,444],{},"'s.\n1. We ask the ",[211,468,469],{},"RoutingIndexManager"," what ",[211,472,440],{},"'s these ",[211,475,444],{},"'s represent. ",[40,478,479,480,483],{},"The ",[211,481,482],{},"RoutingIndexManger"," is set up as below:",[288,485,487],{"className":290,"code":486,"language":292,"meta":11,"style":11},"int numberOfLocations = 4;\nint numberOfVehicles = 1;\nint startingNode = 0;\nvar indexManager = new RoutingIndexManager(\n    numberOfLocations, \u002F\u002F Our location count, including depot (A) \n    numberOfVehicles, \u002F\u002F The number of repair people\n    startingNode \u002F\u002F The index of the depot (0 for A, 1 for B, etc.)\n);\n",[211,488,489,505,519,533,550,559,567,576],{"__ignoreMap":11},[296,490,491,494,497,499,502],{"class":298,"line":299},[296,492,493],{"class":302},"int",[296,495,496],{"class":306}," numberOfLocations",[296,498,310],{"class":302},[296,500,501],{"class":328}," 4",[296,503,504],{"class":319},";\n",[296,506,507,509,512,514,517],{"class":298,"line":12},[296,508,493],{"class":302},[296,510,511],{"class":306}," numberOfVehicles",[296,513,310],{"class":302},[296,515,516],{"class":328}," 1",[296,518,504],{"class":319},[296,520,521,523,526,528,531],{"class":298,"line":351},[296,522,493],{"class":302},[296,524,525],{"class":306}," startingNode",[296,527,310],{"class":302},[296,529,530],{"class":328}," 0",[296,532,504],{"class":319},[296,534,535,537,540,542,544,547],{"class":298,"line":374},[296,536,303],{"class":302},[296,538,539],{"class":306}," indexManager",[296,541,310],{"class":302},[296,543,313],{"class":302},[296,545,546],{"class":306}," RoutingIndexManager",[296,548,549],{"class":319},"(\n",[296,551,552,555],{"class":298,"line":397},[296,553,554],{"class":319},"    numberOfLocations, ",[296,556,558],{"class":557},"sAwPA","\u002F\u002F Our location count, including depot (A) \n",[296,560,561,564],{"class":298,"line":419},[296,562,563],{"class":319},"    numberOfVehicles, ",[296,565,566],{"class":557},"\u002F\u002F The number of repair people\n",[296,568,570,573],{"class":298,"line":569},7,[296,571,572],{"class":319},"    startingNode ",[296,574,575],{"class":557},"\u002F\u002F The index of the depot (0 for A, 1 for B, etc.)\n",[296,577,579],{"class":298,"line":578},8,[296,580,581],{"class":319},");\n",[40,583,584,585,587,588,591,592,594,595,597,598,601,602,594,604,71],{},"The two main uses of the ",[211,586,469],{}," are:\n- ",[211,589,590],{},"NodeToIndex"," - Get the ",[211,593,444],{}," from a given ",[211,596,440],{},".\n- ",[211,599,600],{},"IndexToNode","- Get the ",[211,603,440],{},[211,605,444],{},[40,607,608,609,611],{},"Once solved, we can use the ",[211,610,600],{}," method to infer the selected locations from the solution.",[40,613,614,615,617,618,621],{},"Once we have this ",[211,616,469],{},", we can create our ",[211,619,620],{},"RoutingModel",":",[288,623,625],{"className":290,"code":624,"language":292,"meta":11,"style":11},"var routing = new RoutingModel(manager);\n",[211,626,627],{"__ignoreMap":11},[296,628,629,631,634,636,638,641],{"class":298,"line":299},[296,630,303],{"class":302},[296,632,633],{"class":306}," routing",[296,635,310],{"class":302},[296,637,313],{"class":302},[296,639,640],{"class":306}," RoutingModel",[296,642,643],{"class":319},"(manager);\n",[40,645,646,647,650],{},"An instance of this class contains all information required to find a solution. Later on, we'll call ",[211,648,649],{},"routing.SolveWithParameters(...)"," to obtain a  solution.",[265,652,654],{"id":653},"step-3-distance-callback","Step 3: Distance callback",[40,656,657],{},"We can now make use of our distance matrix. For the model to find a solution to the problem, it will need to continually refer back to something to tell it the distance between two nodes. This takes two steps:",[288,659,661],{"className":290,"code":660,"language":292,"meta":11,"style":11},"  Func\u003Clong, long, long> distanceFunc = (fromVariableIndex, toVariableIndex) = {\n     \u002F\u002F Get the node indexes from the variable indexes;\n    long fromNodeIndex = manager.IndexToNode(fromVariableIndex);\n    long toNodeIndex = manager.IndexToNode(toVariableIndex);\n    return matrix[fromNodeIndex, toNodeIndex];\n\n  int distanceCallbackIndex = routing.RegisterTransitCallback(\n      distanceFunc \u002F\u002F A func to evaluate the weight between two nodes\n  );\n",[211,662,663,699,704,722,738,743,748,766,774],{"__ignoreMap":11},[296,664,665,668,671,674,676,678,680,682,685,688,690,693,696],{"class":298,"line":299},[296,666,667],{"class":306},"  Func",[296,669,670],{"class":319},"\u003C",[296,672,673],{"class":302},"long",[296,675,332],{"class":319},[296,677,673],{"class":302},[296,679,332],{"class":319},[296,681,673],{"class":302},[296,683,684],{"class":319},"> ",[296,686,687],{"class":306},"distanceFunc",[296,689,310],{"class":302},[296,691,692],{"class":319}," (fromVariableIndex, toVariableIndex) ",[296,694,695],{"class":302},"=",[296,697,698],{"class":319}," {\n",[296,700,701],{"class":298,"line":12},[296,702,703],{"class":557},"     \u002F\u002F Get the node indexes from the variable indexes;\n",[296,705,706,709,712,714,717,719],{"class":298,"line":351},[296,707,708],{"class":302},"    long",[296,710,711],{"class":319}," fromNodeIndex ",[296,713,695],{"class":302},[296,715,716],{"class":319}," manager.",[296,718,600],{"class":306},[296,720,721],{"class":319},"(fromVariableIndex);\n",[296,723,724,726,729,731,733,735],{"class":298,"line":374},[296,725,708],{"class":302},[296,727,728],{"class":319}," toNodeIndex ",[296,730,695],{"class":302},[296,732,716],{"class":319},[296,734,600],{"class":306},[296,736,737],{"class":319},"(toVariableIndex);\n",[296,739,740],{"class":298,"line":397},[296,741,742],{"class":319},"    return matrix[fromNodeIndex, toNodeIndex];\n",[296,744,745],{"class":298,"line":419},[296,746,747],{"emptyLinePlaceholder":17},"\n",[296,749,750,753,756,758,761,764],{"class":298,"line":569},[296,751,752],{"class":302},"  int",[296,754,755],{"class":319}," distanceCallbackIndex ",[296,757,695],{"class":302},[296,759,760],{"class":319}," routing.",[296,762,763],{"class":306},"RegisterTransitCallback",[296,765,549],{"class":319},[296,767,768,771],{"class":298,"line":578},[296,769,770],{"class":319},"      distanceFunc ",[296,772,773],{"class":557},"\u002F\u002F A func to evaluate the weight between two nodes\n",[296,775,777],{"class":298,"line":776},9,[296,778,779],{"class":319},"  );\n",[781,782,783],"ol",{},[83,784,785],{},"Register a callback for the model to look up when checking two nodes:",[40,787,788,789,792,793,796],{},"The solver registers this transit callback, and returns an index (",[211,790,791],{},"distanceCallbackIndex",") which serves as this callback's unique identifier. Registering this callback tells the model to run this ",[211,794,795],{},"Func"," each time it is deciding which node to select next. If more callbacks are registered (e.g. if we were to keep track of travel time also), we would call this method again, returning a different index.",[781,798,799],{"start":12},[83,800,801,802,805],{},"Tell the model that this index should be used as the weight of the path (or arc) between two nodes. ",[803,804],"br",{},[211,806,807],{},"routing.SetArcCostEvaluatorOfAllVehicles(distanceCallbackIndex);",[40,809,810],{},"You may be wondering why the model does not automatically use the index created in step one as the distance. You can specify many indexes for the model to keep tabs on as it is calculating. This enables you to keep running totals of whatever you like (e.g. the distance a specific vehicle has travelled or travel time for all vehicles). ",[40,812,813],{},"The matrix was set up to make this distance callback as simple as possible. You have the power to make the 'distance' between two nodes anything you like, but be aware that performance can suffer drastically should you introduce anything particularly computation-heavy within a callback.",[265,815,817],{"id":816},"step-4-travel-time-callback","Step 4: Travel time callback",[40,819,820,821,824,825,827,828,831],{},"Similarly to step 3, we can create a ",[211,822,823],{},"timeFunc",". This behaves just like ",[211,826,687],{},", but returns the ",[44,829,830],{},"time"," it takes to travel between two locations.",[40,833,834,835,837,838,841,842,845],{},"Once we've registered this ",[211,836,823],{}," as a transit callback (and returned a ",[211,839,840],{},"timeCallbackIndex","), we create a ",[44,843,844],{},"dimension"," to keep track of a running time total:",[288,847,849],{"className":290,"code":848,"language":292,"meta":11,"style":11},"routing.AddDimension(\n    timeCallbackIndex, \u002F\u002F transit callback\n    20,                \u002F\u002F slack\n    10000,             \u002F\u002F max capacity\n    false,             \u002F\u002F start cumul to zero\n    \"Time\"             \u002F\u002F dimension name\n); \n",[211,850,851,861,869,880,891,901,910],{"__ignoreMap":11},[296,852,853,856,859],{"class":298,"line":299},[296,854,855],{"class":319},"routing.",[296,857,858],{"class":306},"AddDimension",[296,860,549],{"class":319},[296,862,863,866],{"class":298,"line":12},[296,864,865],{"class":319},"    timeCallbackIndex, ",[296,867,868],{"class":557},"\u002F\u002F transit callback\n",[296,870,871,874,877],{"class":298,"line":351},[296,872,873],{"class":328},"    20",[296,875,876],{"class":319},",                ",[296,878,879],{"class":557},"\u002F\u002F slack\n",[296,881,882,885,888],{"class":298,"line":374},[296,883,884],{"class":328},"    10000",[296,886,887],{"class":319},",             ",[296,889,890],{"class":557},"\u002F\u002F max capacity\n",[296,892,893,896,898],{"class":298,"line":397},[296,894,895],{"class":328},"    false",[296,897,887],{"class":319},[296,899,900],{"class":557},"\u002F\u002F start cumul to zero\n",[296,902,903,907],{"class":298,"line":419},[296,904,906],{"class":905},"sYBdl","    \"Time\"",[296,908,909],{"class":557},"             \u002F\u002F dimension name\n",[296,911,912],{"class":298,"line":569},[296,913,581],{"class":319},[80,915,916,926,932,938,944],{},[83,917,918,920,921,923,924,71],{},[211,919,840],{},": this code was omitted for brevity, but was created in the same way as the ",[211,922,791],{},", using the ",[211,925,823],{},[83,927,928,931],{},[211,929,930],{},"slack",": This slack variable gives the model some wiggle-room, and allows for a maximum of 20 minutes delay per location. For example, the repair man doing work before\u002Fafter the visit, the customer not answering the door initially etc. This is a great example of Google OR-Tools’ flexibility when it comes to solving real-life problems.",[83,933,934,937],{},[211,935,936],{},"max capacity",": This represents the maximum time taken. In some cases, you can calculate exactly what this maximum should be. However, in most cases, it is easier to set it to an arbitrarily large number, provided you are sure the time can never exceed this value. The max time taken is not particularly relevant in this scenario but remains very important for keeping track of vehicle capacities (i.e. you are performing collections and a vehicle can only fit so much inside).",[83,939,940,943],{},[211,941,942],{},"start cumul to zero",": This represents whether time should be relative to each vehicle's starting time or not. This is not relevant in our current scenario as both drivers will be leaving immediately and won't be returning to site until all tasks are done. This can be set to true, but it would be advised that you limit the distance a vehicle travels to a single outing, rather than as a whole.",[83,945,946,949],{},[211,947,948],{},"dimension name",": An identifier for this dimension, so we can get it back from the routing model.",[40,951,952],{},"We can now access the dimension, and the cumulative totals it is keeping track of.",[288,954,956],{"className":290,"code":955,"language":292,"meta":11,"style":11},"RoutingDimension timeDimension = routing.GetMutableDimension(\"Time\");\n\n\u002F\u002F for each visit, we can get the variables representing snapshot information at that visit\nfor (var nodeIndex = 0; nodeIndex \u003C numberOfLocations; nodeIndex++) \n{\n    long variableIndex = manager.NodeToIndex(nodeIndex);\n    IntVar slackAtLocation = timeDimension.SlackVar(variableIndex);\n    IntVar travelTimeToLocation = timeDimension.TransitVar(variableIndex);\n    IntVar totalTimeTaken = timeDimension.CumulVar(variableIndex);\n}\n",[211,957,958,981,985,990,1021,1026,1042,1061,1077,1093],{"__ignoreMap":11},[296,959,960,963,966,968,970,973,976,979],{"class":298,"line":299},[296,961,962],{"class":306},"RoutingDimension",[296,964,965],{"class":306}," timeDimension",[296,967,310],{"class":302},[296,969,760],{"class":319},[296,971,972],{"class":306},"GetMutableDimension",[296,974,975],{"class":319},"(",[296,977,978],{"class":905},"\"Time\"",[296,980,581],{"class":319},[296,982,983],{"class":298,"line":12},[296,984,747],{"emptyLinePlaceholder":17},[296,986,987],{"class":298,"line":351},[296,988,989],{"class":557},"\u002F\u002F for each visit, we can get the variables representing snapshot information at that visit\n",[296,991,992,995,998,1000,1003,1005,1007,1010,1012,1015,1018],{"class":298,"line":374},[296,993,994],{"class":302},"for",[296,996,997],{"class":319}," (",[296,999,303],{"class":302},[296,1001,1002],{"class":306}," nodeIndex",[296,1004,310],{"class":302},[296,1006,530],{"class":328},[296,1008,1009],{"class":319},"; nodeIndex ",[296,1011,670],{"class":302},[296,1013,1014],{"class":319}," numberOfLocations; nodeIndex",[296,1016,1017],{"class":302},"++",[296,1019,1020],{"class":319},") \n",[296,1022,1023],{"class":298,"line":397},[296,1024,1025],{"class":319},"{\n",[296,1027,1028,1030,1033,1035,1037,1039],{"class":298,"line":419},[296,1029,708],{"class":302},[296,1031,1032],{"class":306}," variableIndex",[296,1034,310],{"class":302},[296,1036,716],{"class":319},[296,1038,590],{"class":306},[296,1040,1041],{"class":319},"(nodeIndex);\n",[296,1043,1044,1047,1050,1052,1055,1058],{"class":298,"line":569},[296,1045,1046],{"class":306},"    IntVar",[296,1048,1049],{"class":306}," slackAtLocation",[296,1051,310],{"class":302},[296,1053,1054],{"class":319}," timeDimension.",[296,1056,1057],{"class":306},"SlackVar",[296,1059,1060],{"class":319},"(variableIndex);\n",[296,1062,1063,1065,1068,1070,1072,1075],{"class":298,"line":578},[296,1064,1046],{"class":306},[296,1066,1067],{"class":306}," travelTimeToLocation",[296,1069,310],{"class":302},[296,1071,1054],{"class":319},[296,1073,1074],{"class":306},"TransitVar",[296,1076,1060],{"class":319},[296,1078,1079,1081,1084,1086,1088,1091],{"class":298,"line":776},[296,1080,1046],{"class":306},[296,1082,1083],{"class":306}," totalTimeTaken",[296,1085,310],{"class":302},[296,1087,1054],{"class":319},[296,1089,1090],{"class":306},"CumulVar",[296,1092,1060],{"class":319},[296,1094,1096],{"class":298,"line":1095},10,[296,1097,1098],{"class":319},"}\n",[80,1100,1101,1107,1116],{},[83,1102,1103,1106],{},[211,1104,1105],{},"slackAtLocation",": how much time is spent waiting at this visit (as above, this is a number between 0 and 20).",[83,1108,1109,1112,1113,1115],{},[211,1110,1111],{},"travelTimeToLocation",": how long it took the driver to get to this location (i.e the value returned by the ",[211,1114,823],{}," for this and the previous node).",[83,1117,1118,1121],{},[211,1119,1120],{},"totalTimeTaken",": how much time has elapsed since the driver left the depot. This is key for creating constraints.",[1123,1124,1126],"h4",{"id":1125},"intvar","IntVar",[40,1128,1129,1130,1133],{},"This is a Google OR-Tools object , wrapping an integer value,   which represents the solution provided to us by the model. Once we have a solution, you can use ",[211,1131,1132],{},"solution.Value(intVar)"," to get the value of that specific variable, if necessary.",[265,1135,1137],{"id":1136},"step-5-add-deadline-constraints","Step 5: Add deadline constraints",[40,1139,1140],{},"As posed by this problem, we have a time window in which to complete each visit.   What this means in the code is that each location has a min & max time it needs to be visited by.",[40,1142,1143,1144,71],{},"Slack is very useful here as it allows the driver to wait at a location before setting off to the next location. You can think of slack as 'padding' to help meet things like time window constraints. The slack at a location is included in its cumulative time; some pseudo-code: ",[211,1145,1146],{},"CumulVar(i) = CumulVar(i-1) + Slack(i) + Transit(i-1, i)",[40,1148,1149],{},"For time window constraints, the upper and lower bounds are added as follows:",[288,1151,1153],{"className":290,"code":1152,"language":292,"meta":11,"style":11},"timeDimension.CumulVar(variableIndex).SetRange(minTime, maxTime);\n",[211,1154,1155],{"__ignoreMap":11},[296,1156,1157,1160,1162,1165,1168],{"class":298,"line":299},[296,1158,1159],{"class":319},"timeDimension.",[296,1161,1090],{"class":306},[296,1163,1164],{"class":319},"(variableIndex).",[296,1166,1167],{"class":306},"SetRange",[296,1169,1170],{"class":319},"(minTime, maxTime);\n",[1123,1172,1174],{"id":1173},"penalties","Penalties",[40,1176,1177,1178,1180],{},"Rather than enforcing hard constraints, you have the option to set a penalty for lateness. Implementing penalties correctly is a good way to control the behaviour of your model. \nWe can do this by replacing the ",[211,1179,1167],{}," code in step 5 with the below:",[288,1182,1184],{"className":290,"code":1183,"language":292,"meta":11,"style":11},"\u002F\u002F with this\nconst int penalty = 1000; \u002F\u002F The higher this number, the worse it is in a minimization problem\ntimeDimension.SetCumulVarLowerBound(variableIndex, startWindow, penalty);\ntimeDimension.SetCumulVarUpperBound(variableIndex, endWindow, penalty);\n",[211,1185,1186,1191,1213,1223],{"__ignoreMap":11},[296,1187,1188],{"class":298,"line":299},[296,1189,1190],{"class":557},"\u002F\u002F with this\n",[296,1192,1193,1196,1199,1202,1204,1207,1210],{"class":298,"line":12},[296,1194,1195],{"class":302},"const",[296,1197,1198],{"class":302}," int",[296,1200,1201],{"class":306}," penalty",[296,1203,310],{"class":302},[296,1205,1206],{"class":328}," 1000",[296,1208,1209],{"class":319},"; ",[296,1211,1212],{"class":557},"\u002F\u002F The higher this number, the worse it is in a minimization problem\n",[296,1214,1215,1217,1220],{"class":298,"line":351},[296,1216,1159],{"class":319},[296,1218,1219],{"class":306},"SetCumulVarLowerBound",[296,1221,1222],{"class":319},"(variableIndex, startWindow, penalty);\n",[296,1224,1225,1227,1230],{"class":298,"line":374},[296,1226,1159],{"class":319},[296,1228,1229],{"class":306},"SetCumulVarUpperBound",[296,1231,1232],{"class":319},"(variableIndex, endWindow, penalty);\n",[40,1234,1235,1236,1239,1240,1242,1243,1246],{},"The effect this has is that, rather than this not being a feasible solution, this solution is outputted with the ",[211,1237,1238],{},"penalty"," included in the ",[44,1241,125],{}," of the ",[211,1244,1245],{},"objectiveValue",".  ",[265,1248,1250],{"id":1249},"step-6-solve","Step 6: Solve",[40,1252,1253,1254,1257,1258,1261],{},"At this stage we have a 'complete' model, we can solve. To do this, the routing model requires search parameters with the ",[211,1255,1256],{},"PathCheapestArc"," strategy ensuring it minimizes the distance travelled by all vehicles. The Google OR-Tools class ",[211,1259,1260],{},"operations_research_constraint_solver"," exposes default search parameters for us to use.",[288,1263,1265],{"className":290,"code":1264,"language":292,"meta":11,"style":11},"\u002F\u002F Use Google&apos;s default search parameters.\nSearchParameters searchParameters = operations_research_constraint_solver.DefaultRoutingSearchParameters();\n\n\u002F\u002F Minimize the total distance the vehicle travels to visit all customers.\nsearchParameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.PathCheapestArc;\n\nAssignment solution = routing.SolveWithParameters(searchParameters);\n\n\u002F\u002F This is the length of the shortest path\u002Farc, plus any penalties incurred.\nlong objectiveValue = solution.ObjectiveValue();\n",[211,1266,1267,1272,1291,1295,1300,1310,1314,1332,1336,1341],{"__ignoreMap":11},[296,1268,1269],{"class":298,"line":299},[296,1270,1271],{"class":557},"\u002F\u002F Use Google&apos;s default search parameters.\n",[296,1273,1274,1277,1280,1282,1285,1288],{"class":298,"line":12},[296,1275,1276],{"class":306},"SearchParameters",[296,1278,1279],{"class":306}," searchParameters",[296,1281,310],{"class":302},[296,1283,1284],{"class":319}," operations_research_constraint_solver.",[296,1286,1287],{"class":306},"DefaultRoutingSearchParameters",[296,1289,1290],{"class":319},"();\n",[296,1292,1293],{"class":298,"line":351},[296,1294,747],{"emptyLinePlaceholder":17},[296,1296,1297],{"class":298,"line":374},[296,1298,1299],{"class":557},"\u002F\u002F Minimize the total distance the vehicle travels to visit all customers.\n",[296,1301,1302,1305,1307],{"class":298,"line":397},[296,1303,1304],{"class":319},"searchParameters.FirstSolutionStrategy ",[296,1306,695],{"class":302},[296,1308,1309],{"class":319}," FirstSolutionStrategy.Types.Value.PathCheapestArc;\n",[296,1311,1312],{"class":298,"line":419},[296,1313,747],{"emptyLinePlaceholder":17},[296,1315,1316,1319,1322,1324,1326,1329],{"class":298,"line":569},[296,1317,1318],{"class":306},"Assignment",[296,1320,1321],{"class":306}," solution",[296,1323,310],{"class":302},[296,1325,760],{"class":319},[296,1327,1328],{"class":306},"SolveWithParameters",[296,1330,1331],{"class":319},"(searchParameters);\n",[296,1333,1334],{"class":298,"line":578},[296,1335,747],{"emptyLinePlaceholder":17},[296,1337,1338],{"class":298,"line":776},[296,1339,1340],{"class":557},"\u002F\u002F This is the length of the shortest path\u002Farc, plus any penalties incurred.\n",[296,1342,1343,1345,1348,1350,1353,1356],{"class":298,"line":1095},[296,1344,673],{"class":302},[296,1346,1347],{"class":306}," objectiveValue",[296,1349,310],{"class":302},[296,1351,1352],{"class":319}," solution.",[296,1354,1355],{"class":306},"ObjectiveValue",[296,1357,1290],{"class":319},[40,1359,1360,1361,1365],{},"These search parameters can become a lot more sophisticated. Based on your requirements, you can for example:\n- Set a solution limit, to exit when a certain number of feasible solutions have been found.\n- Set a time limit, to exit out after a certain period of time.\nSee the ",[161,1362,1364],{"href":1363},"https:\u002F\u002Fdevelopers.google.com\u002Foptimization\u002Frouting\u002Frouting_options","routing options"," for the full list of parameters.",[40,1367,1368,1369,1372,1373,1375],{},"Using the  ",[211,1370,1371],{},"solution"," we created, and the original ",[211,1374,620],{}," , we can obtain the selected locations as follows:",[288,1377,1379],{"className":290,"code":1378,"language":292,"meta":11,"style":11},"long currentVariableIndex = routing.Start(0);\nvar locationNumber = 0;\nConsole.WriteLine($&quot;Objective value: {solution.ObjectiveValue()}&quot;);\n\u002F\u002F Now we&apos;ve solved, the routing model knows when we&apos;ve ended or not\nwhile (!routing.IsEnd(index))\n{\n    \u002F\u002F Get the next selected location\n    currentVariableIndex = solution.Value(routing.NextVar(currentVariableIndex));\n    var nodeIndex = routingIndexManager.IndexToNode((int)index);\n    Console.WriteLine($&quot;Location number {locationNumber}: {nodeIndex}&quot;);\n}\n",[211,1380,1381,1401,1414,1447,1452,1470,1474,1479,1500,1522,1545],{"__ignoreMap":11},[296,1382,1383,1385,1388,1390,1392,1395,1397,1399],{"class":298,"line":299},[296,1384,673],{"class":302},[296,1386,1387],{"class":306}," currentVariableIndex",[296,1389,310],{"class":302},[296,1391,760],{"class":319},[296,1393,1394],{"class":306},"Start",[296,1396,975],{"class":319},[296,1398,329],{"class":328},[296,1400,581],{"class":319},[296,1402,1403,1405,1408,1410,1412],{"class":298,"line":12},[296,1404,303],{"class":302},[296,1406,1407],{"class":306}," locationNumber",[296,1409,310],{"class":302},[296,1411,530],{"class":328},[296,1413,504],{"class":319},[296,1415,1416,1419,1422,1425,1428,1431,1434,1437,1439,1442,1444],{"class":298,"line":351},[296,1417,1418],{"class":319},"Console.",[296,1420,1421],{"class":306},"WriteLine",[296,1423,1424],{"class":319},"($",[296,1426,1427],{"class":302},"&",[296,1429,1430],{"class":319},"quot;Objective ",[296,1432,1433],{"class":306},"value",[296,1435,1436],{"class":319},": {solution.",[296,1438,1355],{"class":306},[296,1440,1441],{"class":319},"()}",[296,1443,1427],{"class":302},[296,1445,1446],{"class":319},"quot;);\n",[296,1448,1449],{"class":298,"line":374},[296,1450,1451],{"class":557},"\u002F\u002F Now we&apos;ve solved, the routing model knows when we&apos;ve ended or not\n",[296,1453,1454,1457,1459,1462,1464,1467],{"class":298,"line":397},[296,1455,1456],{"class":302},"while",[296,1458,997],{"class":319},[296,1460,1461],{"class":302},"!",[296,1463,855],{"class":319},[296,1465,1466],{"class":306},"IsEnd",[296,1468,1469],{"class":319},"(index))\n",[296,1471,1472],{"class":298,"line":419},[296,1473,1025],{"class":319},[296,1475,1476],{"class":298,"line":569},[296,1477,1478],{"class":557},"    \u002F\u002F Get the next selected location\n",[296,1480,1481,1484,1486,1488,1491,1494,1497],{"class":298,"line":578},[296,1482,1483],{"class":319},"    currentVariableIndex ",[296,1485,695],{"class":302},[296,1487,1352],{"class":319},[296,1489,1490],{"class":306},"Value",[296,1492,1493],{"class":319},"(routing.",[296,1495,1496],{"class":306},"NextVar",[296,1498,1499],{"class":319},"(currentVariableIndex));\n",[296,1501,1502,1505,1507,1509,1512,1514,1517,1519],{"class":298,"line":776},[296,1503,1504],{"class":302},"    var",[296,1506,1002],{"class":306},[296,1508,310],{"class":302},[296,1510,1511],{"class":319}," routingIndexManager.",[296,1513,600],{"class":306},[296,1515,1516],{"class":319},"((",[296,1518,493],{"class":302},[296,1520,1521],{"class":319},")index);\n",[296,1523,1524,1527,1529,1531,1533,1536,1538,1541,1543],{"class":298,"line":1095},[296,1525,1526],{"class":319},"    Console.",[296,1528,1421],{"class":306},[296,1530,1424],{"class":319},[296,1532,1427],{"class":302},[296,1534,1535],{"class":319},"quot;Location number {locationNumber}",[296,1537,621],{"class":302},[296,1539,1540],{"class":319}," {nodeIndex}",[296,1542,1427],{"class":302},[296,1544,1446],{"class":319},[296,1546,1548],{"class":298,"line":1547},11,[296,1549,1098],{"class":319},[1123,1551,1553],{"id":1552},"case-1-without-time-windows","Case 1: without time windows:",[40,1555,1556],{},"When no time windows are applied, we will obtain a solution that gives us the shortest path. This path is A -> B -> D -> C:",[288,1558,1560],{"className":290,"code":1559,"language":292,"meta":11,"style":11},"Objective value: 13\nLocation number 1: 0\nLocation number 2: 1\nLocation number 3: 3\nLocation number 4: 2\n",[211,1561,1562,1575,1588,1599,1611],{"__ignoreMap":11},[296,1563,1564,1567,1569,1572],{"class":298,"line":299},[296,1565,1566],{"class":319},"Objective ",[296,1568,1433],{"class":306},[296,1570,1571],{"class":319},": ",[296,1573,1574],{"class":328},"13\n",[296,1576,1577,1580,1583,1585],{"class":298,"line":12},[296,1578,1579],{"class":319},"Location number ",[296,1581,1582],{"class":328},"1",[296,1584,621],{"class":302},[296,1586,1587],{"class":328}," 0\n",[296,1589,1590,1592,1594,1596],{"class":298,"line":351},[296,1591,1579],{"class":319},[296,1593,335],{"class":328},[296,1595,621],{"class":302},[296,1597,1598],{"class":328}," 1\n",[296,1600,1601,1603,1606,1608],{"class":298,"line":374},[296,1602,1579],{"class":319},[296,1604,1605],{"class":328},"3",[296,1607,621],{"class":302},[296,1609,1610],{"class":328}," 3\n",[296,1612,1613,1615,1617,1619],{"class":298,"line":397},[296,1614,1579],{"class":319},[296,1616,226],{"class":328},[296,1618,621],{"class":302},[296,1620,1621],{"class":328}," 2\n",[1123,1623,1625],{"id":1624},"case-2-with-time-windows","Case 2: with time windows:",[40,1627,1628],{},"If we were to add a time window, such that location C must be visited between 0 and 10 (I have deliberately left out the units here but you can think of this as minutes or hours if that helps)our previous example would no longer work, as location C is visited at 13. Using the code in step 5, we are provided with the path A -> C -> D -> B. While this has a lower objective value, it visits C first and therefore the constraint is met.",[288,1630,1632],{"className":290,"code":1631,"language":292,"meta":11,"style":11},"Objective value: 19\nLocation number 1: 0\nLocation number 2: 2\nLocation number 3: 3\nLocation number 4: 1\n",[211,1633,1634,1645,1655,1665,1675],{"__ignoreMap":11},[296,1635,1636,1638,1640,1642],{"class":298,"line":299},[296,1637,1566],{"class":319},[296,1639,1433],{"class":306},[296,1641,1571],{"class":319},[296,1643,1644],{"class":328},"19\n",[296,1646,1647,1649,1651,1653],{"class":298,"line":12},[296,1648,1579],{"class":319},[296,1650,1582],{"class":328},[296,1652,621],{"class":302},[296,1654,1587],{"class":328},[296,1656,1657,1659,1661,1663],{"class":298,"line":351},[296,1658,1579],{"class":319},[296,1660,335],{"class":328},[296,1662,621],{"class":302},[296,1664,1621],{"class":328},[296,1666,1667,1669,1671,1673],{"class":298,"line":374},[296,1668,1579],{"class":319},[296,1670,1605],{"class":328},[296,1672,621],{"class":302},[296,1674,1610],{"class":328},[296,1676,1677,1679,1681,1683],{"class":298,"line":397},[296,1678,1579],{"class":319},[296,1680,226],{"class":328},[296,1682,621],{"class":302},[296,1684,1598],{"class":328},[1123,1686,1688],{"id":1687},"potential-enhancements","Potential enhancements",[40,1690,1691],{},"It is possible to make this model more sophisticated by introducing new rules such as:\n- Customer A must be visited between 11:00-12:00.\n- Vehicle B must travel no more than 100 miles.\n- Vehicle C can visit no more than 5 customers.",[1123,1693,1695],{"id":1694},"unit-testing","Unit testing",[40,1697,1698],{},"Due to not having control over how a solution is found, the need for ensuring your model is well tested is vital. As mentioned above, the ability to break a problem down into a list of constraints can be used as inspiration for some test cases. Let’s take this time window as an example for a test case:",[288,1700,1702],{"className":290,"code":1701,"language":292,"meta":11,"style":11},"[Fact]\npublic void Visit_is_not_scheduled_before_the_start_of_a_customers_time_window()\n{\n    \u002F\u002F For brevity, these parameters have everything the model needs to \n    \u002F\u002F solve i.e distance matrix, time windows, number of vehicles, etc.\n    var parameters = GetParameters();\n    \u002F\u002F Set the time window for customer A in the format [min,max]\n    parameters.TimeWindows[0] = [100, 200];\n\n    var result = _repairManScheduler.Execute(parameters);\n\n    var visitedCustomer = GetVisitForCustomerAtIndex(result, 0);\n    visitedCustomer.StartTime.Should().BeGreaterThanOrEqualTo(100);\n}\n",[211,1703,1704,1715,1729,1733,1738,1743,1757,1762,1788,1792,1810,1814,1834,1855],{"__ignoreMap":11},[296,1705,1706,1709,1712],{"class":298,"line":299},[296,1707,1708],{"class":319},"[",[296,1710,1711],{"class":306},"Fact",[296,1713,1714],{"class":319},"]\n",[296,1716,1717,1720,1723,1726],{"class":298,"line":12},[296,1718,1719],{"class":302},"public",[296,1721,1722],{"class":302}," void",[296,1724,1725],{"class":306}," Visit_is_not_scheduled_before_the_start_of_a_customers_time_window",[296,1727,1728],{"class":319},"()\n",[296,1730,1731],{"class":298,"line":351},[296,1732,1025],{"class":319},[296,1734,1735],{"class":298,"line":374},[296,1736,1737],{"class":557},"    \u002F\u002F For brevity, these parameters have everything the model needs to \n",[296,1739,1740],{"class":298,"line":397},[296,1741,1742],{"class":557},"    \u002F\u002F solve i.e distance matrix, time windows, number of vehicles, etc.\n",[296,1744,1745,1747,1750,1752,1755],{"class":298,"line":419},[296,1746,1504],{"class":302},[296,1748,1749],{"class":306}," parameters",[296,1751,310],{"class":302},[296,1753,1754],{"class":306}," GetParameters",[296,1756,1290],{"class":319},[296,1758,1759],{"class":298,"line":569},[296,1760,1761],{"class":557},"    \u002F\u002F Set the time window for customer A in the format [min,max]\n",[296,1763,1764,1767,1769,1772,1774,1777,1780,1782,1785],{"class":298,"line":578},[296,1765,1766],{"class":319},"    parameters.TimeWindows[",[296,1768,329],{"class":328},[296,1770,1771],{"class":319},"] ",[296,1773,695],{"class":302},[296,1775,1776],{"class":319}," [",[296,1778,1779],{"class":328},"100",[296,1781,332],{"class":319},[296,1783,1784],{"class":328},"200",[296,1786,1787],{"class":319},"];\n",[296,1789,1790],{"class":298,"line":776},[296,1791,747],{"emptyLinePlaceholder":17},[296,1793,1794,1796,1799,1801,1804,1807],{"class":298,"line":1095},[296,1795,1504],{"class":302},[296,1797,1798],{"class":306}," result",[296,1800,310],{"class":302},[296,1802,1803],{"class":319}," _repairManScheduler.",[296,1805,1806],{"class":306},"Execute",[296,1808,1809],{"class":319},"(parameters);\n",[296,1811,1812],{"class":298,"line":1547},[296,1813,747],{"emptyLinePlaceholder":17},[296,1815,1817,1819,1822,1824,1827,1830,1832],{"class":298,"line":1816},12,[296,1818,1504],{"class":302},[296,1820,1821],{"class":306}," visitedCustomer",[296,1823,310],{"class":302},[296,1825,1826],{"class":306}," GetVisitForCustomerAtIndex",[296,1828,1829],{"class":319},"(result, ",[296,1831,329],{"class":328},[296,1833,581],{"class":319},[296,1835,1837,1840,1843,1846,1849,1851,1853],{"class":298,"line":1836},13,[296,1838,1839],{"class":319},"    visitedCustomer.StartTime.",[296,1841,1842],{"class":306},"Should",[296,1844,1845],{"class":319},"().",[296,1847,1848],{"class":306},"BeGreaterThanOrEqualTo",[296,1850,975],{"class":319},[296,1852,1779],{"class":328},[296,1854,581],{"class":319},[296,1856,1858],{"class":298,"line":1857},14,[296,1859,1098],{"class":319},[40,1861,1862],{},"Here, we have done the following:\n- Defined a constraint via the method name.\n- Set up the test data (in this case by creating a visit and specifying a time window).\n- Invoked the solver, passing in our test data.\n- Ensured the result has scheduled the visit to take place after the customer’s minimum time.",[40,1864,1865],{},"These problems are usually hard to test in terms of the data setup required and the time taken to inspect results. However, having unit tests to cover the fundamental constraints of your model is a great foundation to minimize regression issues further down the line, especially if the model is becoming more complex as time goes on.",[49,1867,1869],{"id":1868},"conclusion","Conclusion",[40,1871,1872,1873,1876],{},"We have shown that in a short amount of time we can implement a solution to the ",[44,1874,1875],{},"Vehicle Routing Problem with Time Windows"," (VRPTW) problem using Google OR-Tools. In real life uses, these models are generally more complex and built completely bespoke to a client's requirements. Audacia have now successfully implemented Google OR-Tools on a number of projects with models ranging from task scheduling to capacity planning. ",[1878,1879,1880],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}",{"title":11,"searchDepth":12,"depth":12,"links":1882},[1883,1884,1885,1886,1887,1888,1896],{"id":51,"depth":12,"text":52},{"id":74,"depth":12,"text":75},{"id":97,"depth":12,"text":98},{"id":152,"depth":12,"text":153},{"id":177,"depth":12,"text":178},{"id":184,"depth":12,"text":185,"children":1889},[1890,1891,1892,1893,1894,1895],{"id":267,"depth":351,"text":268},{"id":433,"depth":351,"text":434},{"id":653,"depth":351,"text":654},{"id":816,"depth":351,"text":817},{"id":1136,"depth":351,"text":1137},{"id":1249,"depth":351,"text":1250},{"id":1868,"depth":12,"text":1869},"In this article, we will implement a simple optimisation model using Google OR-Tools by solving a classic Vehicle Routing Problem (VRP).","2021-07-20T23:00:00.000Z","The vast majority of business problems relate to minimising costs. \nWith many possible solutions available, the ‘best’ option could be defined as the one which maximises one of these desired outcomes . How we evaluate these solutions is often based on a preconceived notion about characteristics that make them ‘good’. But what if you don't know how you would have come to that solution, and how do you know which solution is 'best'?\nThe objective of using Google OR-Tools is to find a solution to your problem based on the parameters that you define. This can either be to find the single ‘best’ solution or to find a feasible solution that does not violate any of your defined constraints. In this article, we will implement a simple optimisation model using Google OR-Tools by solving a classic Vehicle Routing Problem (VRP).","[object Object]","\u002Fimg\u002Ftechnical-blog\u002Fgoogle-or-tools.jpg",{},"\u002Ftechnical-blog\u002Fhow-audacia-implements-google-s-or-tools-into-projects",{"text":1905,"minutes":1906,"time":1907,"words":1908},"17 min read",16.07,964200,3214,{"title":1910,"description":1911,"keywords":1912},"Implementing Google OR-Tools into software development projects | Audacia","Owen Lacey, Principal Consultant at software development company, Audacia, explains how to implement a simple optimisation model using Google OR-Tools by solving a classic Vehicle Routing Problem (VRP).\n",".NET, Software development tools, Google, OR-Tools, software development company","technical-blog\u002Fhow-audacia-implements-google-s-or-tools-into-projects","oaoRaNANI88G29qKL2hoso3_VM1AAgnbEqOMyZbvBmU",{"id":1916,"title":33,"bio":1917,"extension":14,"image":1918,"meta":1919,"stem":1925,"__hash__":1926},"author\u002Fauthors\u002Fowen-lacey.md","Owen Lacey was a Principal Software Consultant at Audacia for over 8 years. He worked across a number of industries as a developer, including manufacturing, automotive repairs and no-code development. As well as development, he oversaw the delivery of a number of projects, and was involved in consultancy for more advanced features such as machine learning and Google OR Tools.","\u002Fimg\u002Fauthors\u002Fowenheadshot.jpg",{"path":1920,"description":11,"body":1921},"\u002Fauthors\u002Fowen-lacey",{"type":8,"value":1922,"toc":1923},[],{"title":11,"searchDepth":12,"depth":12,"links":1924},[],"authors\u002Fowen-lacey","p9bCaZuwDvQLbcJQDSmEWAYqzx-Ycg3DaJgRALNfKLo",1778145951389]