[{"data":1,"prerenderedAt":1768},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fvehicle-routing-java":3},{"id":4,"title":5,"body":6,"description":1759,"extension":1760,"faq":1761,"meta":1762,"navigation":344,"noindex":1763,"path":1764,"seo":1765,"stem":1766,"__hash__":1767},"content\u002Fdeveloper\u002Fvehicle-routing-java.md","Vehicle routing in Java",{"type":7,"value":8,"toc":1749},"minimark",[9,13,25,30,33,68,71,74,78,81,176,179,305,308,312,318,537,547,550,710,714,721,903,906,1182,1186,1189,1219,1226,1348,1351,1355,1358,1452,1455,1507,1510,1515,1518,1522,1533,1592,1595,1666,1678,1727,1733,1737,1740,1745],[10,11,5],"h1",{"id":12},"vehicle-routing-in-java",[14,15,16,17,24],"p",{},"This page plans a real week of deliveries with the Java client\n",[18,19,23],"a",{"href":20,"rel":21},"https:\u002F\u002Fgithub.com\u002FQuicopt\u002Fquicopt-java",[22],"nofollow","quicopt-java",". The rules are constraint streams over a\nplanning list variable, and the search runs on the Quicopt planner service. The data are real and\nanonymised.",[26,27,29],"h2",{"id":28},"the-delivery-week","The delivery week",[14,31,32],{},"Every weekday, 13 or 14 trucks leave one depot and serve 88 to 105 customers. A plan says which\ntruck serves which customers, and in which order. It has to keep five rules:",[34,35,36,44,50,56,62],"ul",{},[37,38,39,43],"li",{},[40,41,42],"strong",{},"Capacity."," No trip carries more than its truck holds.",[37,45,46,49],{},[40,47,48],{},"Time windows."," Some customers have a latest start of service.",[37,51,52,55],{},[40,53,54],{},"Duty day."," A driver's day, from leaving the depot to returning, lasts at most 8 h 45: 9 h 30\nof working time less a 45-minute break, deducted every day. Waiting for a window counts.",[37,57,58,61],{},[40,59,60],{},"Trailer access."," Some trucks pull a trailer today. They carry about twice as much, but most\ncustomers' yards are too small for them.",[37,63,64,67],{},[40,65,66],{},"Reloads."," A truck may return to the depot up to four times to load again. Each return starts\na new trip with an empty truck.",[14,69,70],{},"Of the plans that keep all five rules, the best one drives the fewest hours in total.",[14,72,73],{},"Each day is a JSON file with the customers, the trucks and two tables of drive times. A truck\nwith a trailer takes other roads than a truck alone, so each kind has its own table. Names, places\nand weekdays are gone, and times and demands are perturbed.",[26,75,77],{"id":76},"the-model","The model",[14,79,80],{},"A truck's route is a list of stops, and the solver decides the list:",[82,83,88],"pre",{"className":84,"code":85,"language":86,"meta":87,"style":87},"language-java shiki shiki-themes github-dark","@PlanningEntity\npublic class Vehicle {\n    \u002F\u002F …\n    @PlanningListVariable(allowsUnassignedValues = true)\n    private List\u003CStop> stops = new ArrayList\u003C>();\n","java","",[89,90,91,104,120,127,152],"code",{"__ignoreMap":87},[92,93,96,100],"span",{"class":94,"line":95},"line",1,[92,97,99],{"class":98},"s95oV","@",[92,101,103],{"class":102},"snl16","PlanningEntity\n",[92,105,107,110,113,117],{"class":94,"line":106},2,[92,108,109],{"class":102},"public",[92,111,112],{"class":102}," class",[92,114,116],{"class":115},"svObZ"," Vehicle",[92,118,119],{"class":98}," {\n",[92,121,123],{"class":94,"line":122},3,[92,124,126],{"class":125},"sAwPA","    \u002F\u002F …\n",[92,128,130,133,136,139,143,146,149],{"class":94,"line":129},4,[92,131,132],{"class":98},"    @",[92,134,135],{"class":102},"PlanningListVariable",[92,137,138],{"class":98},"(",[92,140,142],{"class":141},"sDLfK","allowsUnassignedValues",[92,144,145],{"class":102}," =",[92,147,148],{"class":141}," true",[92,150,151],{"class":98},")\n",[92,153,155,158,161,164,167,170,173],{"class":94,"line":154},5,[92,156,157],{"class":102},"    private",[92,159,160],{"class":98}," List\u003C",[92,162,163],{"class":102},"Stop",[92,165,166],{"class":98},"> stops ",[92,168,169],{"class":102},"=",[92,171,172],{"class":102}," new",[92,174,175],{"class":98}," ArrayList\u003C>();\n",[14,177,178],{},"A stop is a customer or a reload. A reload sits at the depot and splits the route into trips. The\nnumber of reloads a truck needs is part of the answer, so the plan holds a pool of them, as many as\nall trucks together may use. The solver puts some into routes and leaves the rest out, which is why\nthe list variable allows unassigned values:",[82,180,182],{"className":84,"code":181,"language":86,"meta":87,"style":87},"int reloads = day.vehicles().stream().mapToInt(Day.Truck::maxReloads).sum();\nfor (int r = 0; r \u003C reloads; r++) {   \u002F\u002F loop over the reload pool\n    stops.add(Stop.reload(\"reload \" + (r + 1), depot));\n}   \u002F\u002F end loop over the reload pool\n",[89,183,184,226,262,297],{"__ignoreMap":87},[92,185,186,189,192,194,197,200,203,206,208,211,214,217,220,223],{"class":94,"line":95},[92,187,188],{"class":102},"int",[92,190,191],{"class":98}," reloads ",[92,193,169],{"class":102},[92,195,196],{"class":98}," day.",[92,198,199],{"class":115},"vehicles",[92,201,202],{"class":98},"().",[92,204,205],{"class":115},"stream",[92,207,202],{"class":98},[92,209,210],{"class":115},"mapToInt",[92,212,213],{"class":98},"(Day.Truck",[92,215,216],{"class":102},"::",[92,218,219],{"class":98},"maxReloads).",[92,221,222],{"class":115},"sum",[92,224,225],{"class":98},"();\n",[92,227,228,231,234,236,239,241,244,247,250,253,256,259],{"class":94,"line":106},[92,229,230],{"class":102},"for",[92,232,233],{"class":98}," (",[92,235,188],{"class":102},[92,237,238],{"class":98}," r ",[92,240,169],{"class":102},[92,242,243],{"class":141}," 0",[92,245,246],{"class":98},"; r ",[92,248,249],{"class":102},"\u003C",[92,251,252],{"class":98}," reloads; r",[92,254,255],{"class":102},"++",[92,257,258],{"class":98},") {   ",[92,260,261],{"class":125},"\u002F\u002F loop over the reload pool\n",[92,263,264,267,270,273,276,278,282,285,288,291,294],{"class":94,"line":122},[92,265,266],{"class":98},"    stops.",[92,268,269],{"class":115},"add",[92,271,272],{"class":98},"(Stop.",[92,274,275],{"class":115},"reload",[92,277,138],{"class":98},[92,279,281],{"class":280},"sU2Wk","\"reload \"",[92,283,284],{"class":102}," +",[92,286,287],{"class":98}," (r ",[92,289,290],{"class":102},"+",[92,292,293],{"class":141}," 1",[92,295,296],{"class":98},"), depot));\n",[92,298,299,302],{"class":94,"line":129},[92,300,301],{"class":98},"}   ",[92,303,304],{"class":125},"\u002F\u002F end loop over the reload pool\n",[14,306,307],{},"A reload left out costs nothing. A customer left out breaks a rule of its own, on the score's\nmedium level, so the solver first serves everyone and then shortens the drive.",[26,309,311],{"id":310},"the-rules","The rules",[14,313,314,317],{},[89,315,316],{},"RoutingConstraints"," lists the seven rules. Five are hard and have the same shape: for each truck,\npenalize by how much its route breaks the rule.",[82,319,321],{"className":84,"code":320,"language":86,"meta":87,"style":87},"public class RoutingConstraints implements ConstraintProvider {\n\n    @Override\n    public Constraint[] defineConstraints(ConstraintFactory factory) {\n        return new Constraint[] {\n                capacity(factory),\n                timeWindow(factory),\n                dutyDay(factory),\n                trailerAccess(factory),\n                reloadLimit(factory),\n                everyCustomerServed(factory),\n                drive(factory)\n        };\n    }\n\n    \u002F** No trip carries more than the truck holds. *\u002F\n    Constraint capacity(ConstraintFactory factory) {\n        return factory.forEach(Vehicle.class)\n                .penalize(HardMediumSoftScore.ONE_HARD, Vehicle::excessLoad)\n                .asConstraint(\"capacity\");\n    }\n",[89,322,323,340,346,353,377,389,398,406,414,422,430,438,447,453,459,464,470,485,499,516,532],{"__ignoreMap":87},[92,324,325,327,329,332,335,338],{"class":94,"line":95},[92,326,109],{"class":102},[92,328,112],{"class":102},[92,330,331],{"class":115}," RoutingConstraints",[92,333,334],{"class":102}," implements",[92,336,337],{"class":115}," ConstraintProvider",[92,339,119],{"class":98},[92,341,342],{"class":94,"line":106},[92,343,345],{"emptyLinePlaceholder":344},true,"\n",[92,347,348,350],{"class":94,"line":122},[92,349,132],{"class":98},[92,351,352],{"class":102},"Override\n",[92,354,355,358,361,364,367,370,374],{"class":94,"line":129},[92,356,357],{"class":102},"    public",[92,359,360],{"class":102}," Constraint",[92,362,363],{"class":98},"[] ",[92,365,366],{"class":115},"defineConstraints",[92,368,369],{"class":98},"(ConstraintFactory ",[92,371,373],{"class":372},"s9osk","factory",[92,375,376],{"class":98},") {\n",[92,378,379,382,384,386],{"class":94,"line":154},[92,380,381],{"class":102},"        return",[92,383,172],{"class":102},[92,385,360],{"class":102},[92,387,388],{"class":98},"[] {\n",[92,390,392,395],{"class":94,"line":391},6,[92,393,394],{"class":115},"                capacity",[92,396,397],{"class":98},"(factory),\n",[92,399,401,404],{"class":94,"line":400},7,[92,402,403],{"class":115},"                timeWindow",[92,405,397],{"class":98},[92,407,409,412],{"class":94,"line":408},8,[92,410,411],{"class":115},"                dutyDay",[92,413,397],{"class":98},[92,415,417,420],{"class":94,"line":416},9,[92,418,419],{"class":115},"                trailerAccess",[92,421,397],{"class":98},[92,423,425,428],{"class":94,"line":424},10,[92,426,427],{"class":115},"                reloadLimit",[92,429,397],{"class":98},[92,431,433,436],{"class":94,"line":432},11,[92,434,435],{"class":115},"                everyCustomerServed",[92,437,397],{"class":98},[92,439,441,444],{"class":94,"line":440},12,[92,442,443],{"class":115},"                drive",[92,445,446],{"class":98},"(factory)\n",[92,448,450],{"class":94,"line":449},13,[92,451,452],{"class":98},"        };\n",[92,454,456],{"class":94,"line":455},14,[92,457,458],{"class":98},"    }\n",[92,460,462],{"class":94,"line":461},15,[92,463,345],{"emptyLinePlaceholder":344},[92,465,467],{"class":94,"line":466},16,[92,468,469],{"class":125},"    \u002F** No trip carries more than the truck holds. *\u002F\n",[92,471,473,476,479,481,483],{"class":94,"line":472},17,[92,474,475],{"class":98},"    Constraint ",[92,477,478],{"class":115},"capacity",[92,480,369],{"class":98},[92,482,373],{"class":372},[92,484,376],{"class":98},[92,486,488,490,493,496],{"class":94,"line":487},18,[92,489,381],{"class":102},[92,491,492],{"class":98}," factory.",[92,494,495],{"class":115},"forEach",[92,497,498],{"class":98},"(Vehicle.class)\n",[92,500,502,505,508,511,513],{"class":94,"line":501},19,[92,503,504],{"class":98},"                .",[92,506,507],{"class":115},"penalize",[92,509,510],{"class":98},"(HardMediumSoftScore.ONE_HARD, Vehicle",[92,512,216],{"class":102},[92,514,515],{"class":98},"excessLoad)\n",[92,517,519,521,524,526,529],{"class":94,"line":518},20,[92,520,504],{"class":98},[92,522,523],{"class":115},"asConstraint",[92,525,138],{"class":98},[92,527,528],{"class":280},"\"capacity\"",[92,530,531],{"class":98},");\n",[92,533,535],{"class":94,"line":534},21,[92,536,458],{"class":98},[14,538,539,540,543,544,546],{},"A route that keeps the rule penalizes 0, so no ",[89,541,542],{},"filter"," stands in front of ",[89,545,507],{},". A filter\nwould call the same measure again and walk the route a second time.",[14,548,549],{},"The other two rules are the medium level and the objective:",[82,551,553],{"className":84,"code":552,"language":86,"meta":87,"style":87},"    \u002F** Every customer is on some route. Reloads may stay unused. *\u002F\n    Constraint everyCustomerServed(ConstraintFactory factory) {\n        return factory.forEachIncludingUnassigned(Stop.class)\n                .filter(stop -> !stop.isReload() && stop.getVehicle() == null)\n                .penalize(HardMediumSoftScore.ONE_MEDIUM)\n                .asConstraint(\"customer served\");\n    }\n\n    \u002F** The objective: as little driving as possible. *\u002F\n    Constraint drive(ConstraintFactory factory) {\n        return factory.forEach(Vehicle.class)\n                .penalize(HardMediumSoftScore.ONE_SOFT, Vehicle::driveSeconds)\n                .asConstraint(\"drive\");\n    }\n",[89,554,555,560,570,582,625,634,647,651,655,660,669,679,693,706],{"__ignoreMap":87},[92,556,557],{"class":94,"line":95},[92,558,559],{"class":125},"    \u002F** Every customer is on some route. Reloads may stay unused. *\u002F\n",[92,561,562,564,567],{"class":94,"line":106},[92,563,475],{"class":98},[92,565,566],{"class":115},"everyCustomerServed",[92,568,569],{"class":98},"(ConstraintFactory factory) {\n",[92,571,572,574,576,579],{"class":94,"line":122},[92,573,381],{"class":102},[92,575,492],{"class":98},[92,577,578],{"class":115},"forEachIncludingUnassigned",[92,580,581],{"class":98},"(Stop.class)\n",[92,583,584,586,588,591,594,597,600,603,606,609,612,615,617,620,623],{"class":94,"line":129},[92,585,504],{"class":98},[92,587,542],{"class":115},[92,589,590],{"class":98},"(stop ",[92,592,593],{"class":102},"->",[92,595,596],{"class":102}," !",[92,598,599],{"class":98},"stop.",[92,601,602],{"class":115},"isReload",[92,604,605],{"class":98},"() ",[92,607,608],{"class":102},"&&",[92,610,611],{"class":98}," stop.",[92,613,614],{"class":115},"getVehicle",[92,616,605],{"class":98},[92,618,619],{"class":102},"==",[92,621,622],{"class":141}," null",[92,624,151],{"class":98},[92,626,627,629,631],{"class":94,"line":154},[92,628,504],{"class":98},[92,630,507],{"class":115},[92,632,633],{"class":98},"(HardMediumSoftScore.ONE_MEDIUM)\n",[92,635,636,638,640,642,645],{"class":94,"line":391},[92,637,504],{"class":98},[92,639,523],{"class":115},[92,641,138],{"class":98},[92,643,644],{"class":280},"\"customer served\"",[92,646,531],{"class":98},[92,648,649],{"class":94,"line":400},[92,650,458],{"class":98},[92,652,653],{"class":94,"line":408},[92,654,345],{"emptyLinePlaceholder":344},[92,656,657],{"class":94,"line":416},[92,658,659],{"class":125},"    \u002F** The objective: as little driving as possible. *\u002F\n",[92,661,662,664,667],{"class":94,"line":424},[92,663,475],{"class":98},[92,665,666],{"class":115},"drive",[92,668,569],{"class":98},[92,670,671,673,675,677],{"class":94,"line":432},[92,672,381],{"class":102},[92,674,492],{"class":98},[92,676,495],{"class":115},[92,678,498],{"class":98},[92,680,681,683,685,688,690],{"class":94,"line":440},[92,682,504],{"class":98},[92,684,507],{"class":115},[92,686,687],{"class":98},"(HardMediumSoftScore.ONE_SOFT, Vehicle",[92,689,216],{"class":102},[92,691,692],{"class":98},"driveSeconds)\n",[92,694,695,697,699,701,704],{"class":94,"line":449},[92,696,504],{"class":98},[92,698,523],{"class":115},[92,700,138],{"class":98},[92,702,703],{"class":280},"\"drive\"",[92,705,531],{"class":98},[92,707,708],{"class":94,"line":455},[92,709,458],{"class":98},[26,711,713],{"id":712},"route-measures","Route measures",[14,715,716,717,720],{},"Each measure is a method on ",[89,718,719],{},"Vehicle"," that walks the route. Capacity holds per trip, so the load\nstarts again at every reload:",[82,722,724],{"className":84,"code":723,"language":86,"meta":87,"style":87},"    \u002F** @return load beyond the truck's capacity, summed over its trips *\u002F\n    public long excessLoad() {\n        long load = 0;\n        long excess = 0;\n        for (Stop stop : stops) {\n            if (stop.isReload()) {\n                excess += Math.max(0, load - capacity);\n                load = 0;\n            } else {\n                load += stop.demand();\n            }\n        }\n        return excess + Math.max(0, load - capacity);\n    }\n",[89,725,726,737,750,765,778,792,805,833,844,854,867,872,877,899],{"__ignoreMap":87},[92,727,728,731,734],{"class":94,"line":95},[92,729,730],{"class":125},"    \u002F** ",[92,732,733],{"class":102},"@return",[92,735,736],{"class":125}," load beyond the truck's capacity, summed over its trips *\u002F\n",[92,738,739,741,744,747],{"class":94,"line":106},[92,740,357],{"class":102},[92,742,743],{"class":102}," long",[92,745,746],{"class":115}," excessLoad",[92,748,749],{"class":98},"() {\n",[92,751,752,755,758,760,762],{"class":94,"line":122},[92,753,754],{"class":102},"        long",[92,756,757],{"class":98}," load ",[92,759,169],{"class":102},[92,761,243],{"class":141},[92,763,764],{"class":98},";\n",[92,766,767,769,772,774,776],{"class":94,"line":129},[92,768,754],{"class":102},[92,770,771],{"class":98}," excess ",[92,773,169],{"class":102},[92,775,243],{"class":141},[92,777,764],{"class":98},[92,779,780,783,786,789],{"class":94,"line":154},[92,781,782],{"class":102},"        for",[92,784,785],{"class":98}," (Stop stop ",[92,787,788],{"class":102},":",[92,790,791],{"class":98}," stops) {\n",[92,793,794,797,800,802],{"class":94,"line":391},[92,795,796],{"class":102},"            if",[92,798,799],{"class":98}," (stop.",[92,801,602],{"class":115},[92,803,804],{"class":98},"()) {\n",[92,806,807,810,813,816,819,821,824,827,830],{"class":94,"line":400},[92,808,809],{"class":98},"                excess ",[92,811,812],{"class":102},"+=",[92,814,815],{"class":98}," Math.",[92,817,818],{"class":115},"max",[92,820,138],{"class":98},[92,822,823],{"class":141},"0",[92,825,826],{"class":98},", load ",[92,828,829],{"class":102},"-",[92,831,832],{"class":98}," capacity);\n",[92,834,835,838,840,842],{"class":94,"line":408},[92,836,837],{"class":98},"                load ",[92,839,169],{"class":102},[92,841,243],{"class":141},[92,843,764],{"class":98},[92,845,846,849,852],{"class":94,"line":416},[92,847,848],{"class":98},"            } ",[92,850,851],{"class":102},"else",[92,853,119],{"class":98},[92,855,856,858,860,862,865],{"class":94,"line":424},[92,857,837],{"class":98},[92,859,812],{"class":102},[92,861,611],{"class":98},[92,863,864],{"class":115},"demand",[92,866,225],{"class":98},[92,868,869],{"class":94,"line":432},[92,870,871],{"class":98},"            }\n",[92,873,874],{"class":94,"line":440},[92,875,876],{"class":98},"        }\n",[92,878,879,881,883,885,887,889,891,893,895,897],{"class":94,"line":449},[92,880,381],{"class":102},[92,882,771],{"class":98},[92,884,290],{"class":102},[92,886,815],{"class":98},[92,888,818],{"class":115},[92,890,138],{"class":98},[92,892,823],{"class":141},[92,894,826],{"class":98},[92,896,829],{"class":102},[92,898,832],{"class":98},[92,900,901],{"class":94,"line":455},[92,902,458],{"class":98},[14,904,905],{},"The duty day takes more care. A truck that arrives before a window opens waits, and the waiting\ncounts as working time. It could also leave the depot later and wait less, as long as every stop\nstill starts in time. The walk starts the truck at 0 and keeps two sums: the waiting so far, and\nhow much later the truck could have left and still made every latest start. The day is shorter by\nthe smaller of the two:",[82,907,909],{"className":84,"code":908,"language":86,"meta":87,"style":87},"    public long durationSeconds() {\n        long clock = 0;\n        long waitSoFar = 0;                     \u002F\u002F waiting before the current stop, all stops so far\n        long canLeaveLater = Long.MAX_VALUE;    \u002F\u002F how much later the truck could leave and be on time\n        Location at = depot;\n        for (Stop stop : stops) {\n            long arrival = clock + travel.drive(at, stop.location());\n            long serviceStart = Math.max(arrival, stop.earliestStart());\n            waitSoFar += serviceStart - arrival;\n            if (stop.latestStart() != Stop.OPEN) {\n                canLeaveLater = Math.min(canLeaveLater, waitSoFar + stop.latestStart() - serviceStart);\n            }\n            clock = serviceStart + stop.serviceSeconds();\n            at = stop.location();\n        }\n        long end = clock + travel.drive(at, depot);\n        return end - Math.max(0, Math.min(waitSoFar, canLeaveLater));\n    }\n",[89,910,911,922,935,952,967,977,987,1015,1036,1050,1067,1095,1099,1117,1130,1134,1154,1178],{"__ignoreMap":87},[92,912,913,915,917,920],{"class":94,"line":95},[92,914,357],{"class":102},[92,916,743],{"class":102},[92,918,919],{"class":115}," durationSeconds",[92,921,749],{"class":98},[92,923,924,926,929,931,933],{"class":94,"line":106},[92,925,754],{"class":102},[92,927,928],{"class":98}," clock ",[92,930,169],{"class":102},[92,932,243],{"class":141},[92,934,764],{"class":98},[92,936,937,939,942,944,946,949],{"class":94,"line":122},[92,938,754],{"class":102},[92,940,941],{"class":98}," waitSoFar ",[92,943,169],{"class":102},[92,945,243],{"class":141},[92,947,948],{"class":98},";                     ",[92,950,951],{"class":125},"\u002F\u002F waiting before the current stop, all stops so far\n",[92,953,954,956,959,961,964],{"class":94,"line":129},[92,955,754],{"class":102},[92,957,958],{"class":98}," canLeaveLater ",[92,960,169],{"class":102},[92,962,963],{"class":98}," Long.MAX_VALUE;    ",[92,965,966],{"class":125},"\u002F\u002F how much later the truck could leave and be on time\n",[92,968,969,972,974],{"class":94,"line":154},[92,970,971],{"class":98},"        Location at ",[92,973,169],{"class":102},[92,975,976],{"class":98}," depot;\n",[92,978,979,981,983,985],{"class":94,"line":391},[92,980,782],{"class":102},[92,982,785],{"class":98},[92,984,788],{"class":102},[92,986,791],{"class":98},[92,988,989,992,995,997,999,1001,1004,1006,1009,1012],{"class":94,"line":400},[92,990,991],{"class":102},"            long",[92,993,994],{"class":98}," arrival ",[92,996,169],{"class":102},[92,998,928],{"class":98},[92,1000,290],{"class":102},[92,1002,1003],{"class":98}," travel.",[92,1005,666],{"class":115},[92,1007,1008],{"class":98},"(at, stop.",[92,1010,1011],{"class":115},"location",[92,1013,1014],{"class":98},"());\n",[92,1016,1017,1019,1022,1024,1026,1028,1031,1034],{"class":94,"line":408},[92,1018,991],{"class":102},[92,1020,1021],{"class":98}," serviceStart ",[92,1023,169],{"class":102},[92,1025,815],{"class":98},[92,1027,818],{"class":115},[92,1029,1030],{"class":98},"(arrival, stop.",[92,1032,1033],{"class":115},"earliestStart",[92,1035,1014],{"class":98},[92,1037,1038,1041,1043,1045,1047],{"class":94,"line":416},[92,1039,1040],{"class":98},"            waitSoFar ",[92,1042,812],{"class":102},[92,1044,1021],{"class":98},[92,1046,829],{"class":102},[92,1048,1049],{"class":98}," arrival;\n",[92,1051,1052,1054,1056,1059,1061,1064],{"class":94,"line":424},[92,1053,796],{"class":102},[92,1055,799],{"class":98},[92,1057,1058],{"class":115},"latestStart",[92,1060,605],{"class":98},[92,1062,1063],{"class":102},"!=",[92,1065,1066],{"class":98}," Stop.OPEN) {\n",[92,1068,1069,1072,1074,1076,1079,1082,1084,1086,1088,1090,1092],{"class":94,"line":432},[92,1070,1071],{"class":98},"                canLeaveLater ",[92,1073,169],{"class":102},[92,1075,815],{"class":98},[92,1077,1078],{"class":115},"min",[92,1080,1081],{"class":98},"(canLeaveLater, waitSoFar ",[92,1083,290],{"class":102},[92,1085,611],{"class":98},[92,1087,1058],{"class":115},[92,1089,605],{"class":98},[92,1091,829],{"class":102},[92,1093,1094],{"class":98}," serviceStart);\n",[92,1096,1097],{"class":94,"line":440},[92,1098,871],{"class":98},[92,1100,1101,1104,1106,1108,1110,1112,1115],{"class":94,"line":449},[92,1102,1103],{"class":98},"            clock ",[92,1105,169],{"class":102},[92,1107,1021],{"class":98},[92,1109,290],{"class":102},[92,1111,611],{"class":98},[92,1113,1114],{"class":115},"serviceSeconds",[92,1116,225],{"class":98},[92,1118,1119,1122,1124,1126,1128],{"class":94,"line":455},[92,1120,1121],{"class":98},"            at ",[92,1123,169],{"class":102},[92,1125,611],{"class":98},[92,1127,1011],{"class":115},[92,1129,225],{"class":98},[92,1131,1132],{"class":94,"line":461},[92,1133,876],{"class":98},[92,1135,1136,1138,1141,1143,1145,1147,1149,1151],{"class":94,"line":466},[92,1137,754],{"class":102},[92,1139,1140],{"class":98}," end ",[92,1142,169],{"class":102},[92,1144,928],{"class":98},[92,1146,290],{"class":102},[92,1148,1003],{"class":98},[92,1150,666],{"class":115},[92,1152,1153],{"class":98},"(at, depot);\n",[92,1155,1156,1158,1160,1162,1164,1166,1168,1170,1173,1175],{"class":94,"line":472},[92,1157,381],{"class":102},[92,1159,1140],{"class":98},[92,1161,829],{"class":102},[92,1163,815],{"class":98},[92,1165,818],{"class":115},[92,1167,138],{"class":98},[92,1169,823],{"class":141},[92,1171,1172],{"class":98},", Math.",[92,1174,1078],{"class":115},[92,1176,1177],{"class":98},"(waitSoFar, canLeaveLater));\n",[92,1179,1180],{"class":94,"line":487},[92,1181,458],{"class":98},[26,1183,1185],{"id":1184},"code-the-service-can-run","Code the service can run",[14,1187,1188],{},"The search runs on the service, not in your JVM. The client reads the rule methods, and every\nmethod they call, and sends them with the plan. Version 0.1.0-beta.1 can't send three things, and\nthe model is written around them:",[34,1190,1191,1201,1207],{},[37,1192,1193,1196,1197,1200],{},[40,1194,1195],{},"Array reads."," A rule reads a drive time through a method, never from ",[89,1198,1199],{},"long[][]"," directly.\nThe client calls the method for every pair of locations before the solve and sends the results\nas a table.",[37,1202,1203,1206],{},[40,1204,1205],{},"New objects."," A record holding drive, duration and load from one walk would be the natural\ndesign. Instead, each measure is a method of its own and walks the route itself.",[37,1208,1209,1215,1216,1214],{},[40,1210,1211,1214],{},[89,1212,1213],{},"switch","."," A rule that depends on a kind of truck or day uses ",[89,1217,1218],{},"if",[14,1220,1221,1222,1225],{},"The drive times show the first point. ",[89,1223,1224],{},"TravelTimes"," holds one table, and every vehicle of one kind\nshares one object:",[82,1227,1229],{"className":84,"code":1228,"language":86,"meta":87,"style":87},"public record TravelTimes(long[][] seconds) {\n\n    \u002F**\n     * @param from where the drive starts\n     * @param to where the drive ends\n     * @return the drive in seconds\n     *\u002F\n    public long drive(Location from, Location to) {\n        return seconds[from.index()][to.index()];\n    }\n}\n",[89,1230,1231,1249,1253,1258,1272,1284,1293,1298,1321,1339,1343],{"__ignoreMap":87},[92,1232,1233,1235,1238,1241,1243,1246],{"class":94,"line":95},[92,1234,109],{"class":102},[92,1236,1237],{"class":102}," record",[92,1239,1240],{"class":115}," TravelTimes",[92,1242,138],{"class":98},[92,1244,1245],{"class":102},"long",[92,1247,1248],{"class":98},"[][] seconds) {\n",[92,1250,1251],{"class":94,"line":106},[92,1252,345],{"emptyLinePlaceholder":344},[92,1254,1255],{"class":94,"line":122},[92,1256,1257],{"class":125},"    \u002F**\n",[92,1259,1260,1263,1266,1269],{"class":94,"line":129},[92,1261,1262],{"class":125},"     * ",[92,1264,1265],{"class":102},"@param",[92,1267,1268],{"class":372}," from",[92,1270,1271],{"class":125}," where the drive starts\n",[92,1273,1274,1276,1278,1281],{"class":94,"line":154},[92,1275,1262],{"class":125},[92,1277,1265],{"class":102},[92,1279,1280],{"class":372}," to",[92,1282,1283],{"class":125}," where the drive ends\n",[92,1285,1286,1288,1290],{"class":94,"line":391},[92,1287,1262],{"class":125},[92,1289,733],{"class":102},[92,1291,1292],{"class":125}," the drive in seconds\n",[92,1294,1295],{"class":94,"line":400},[92,1296,1297],{"class":125},"     *\u002F\n",[92,1299,1300,1302,1304,1307,1310,1313,1316,1319],{"class":94,"line":408},[92,1301,357],{"class":102},[92,1303,743],{"class":102},[92,1305,1306],{"class":115}," drive",[92,1308,1309],{"class":98},"(Location ",[92,1311,1312],{"class":372},"from",[92,1314,1315],{"class":98},", Location ",[92,1317,1318],{"class":372},"to",[92,1320,376],{"class":98},[92,1322,1323,1325,1328,1331,1334,1336],{"class":94,"line":416},[92,1324,381],{"class":102},[92,1326,1327],{"class":98}," seconds[from.",[92,1329,1330],{"class":115},"index",[92,1332,1333],{"class":98},"()][to.",[92,1335,1330],{"class":115},[92,1337,1338],{"class":98},"()];\n",[92,1340,1341],{"class":94,"line":424},[92,1342,458],{"class":98},[92,1344,1345],{"class":94,"line":432},[92,1346,1347],{"class":98},"}\n",[14,1349,1350],{},"A model that breaks one of these is refused before the solve starts. The message names the method\nand the line.",[26,1352,1354],{"id":1353},"the-solve","The solve",[14,1356,1357],{},"The configuration names the classes and a time limit:",[82,1359,1361],{"className":84,"code":1360,"language":86,"meta":87,"style":87},"        SolverConfig config = new SolverConfig()\n                .withSolutionClass(RoutePlan.class)\n                .withEntityClasses(Vehicle.class, Stop.class)\n                .withConstraintProviderClass(RoutingConstraints.class)\n                .withTerminationSpentLimit(perDay);\n        Solver\u003CRoutePlan> solver = SolverFactory.\u003CRoutePlan> create(config).buildSolver();\n",[89,1362,1363,1378,1388,1398,1408,1418],{"__ignoreMap":87},[92,1364,1365,1368,1370,1372,1375],{"class":94,"line":95},[92,1366,1367],{"class":98},"        SolverConfig config ",[92,1369,169],{"class":102},[92,1371,172],{"class":102},[92,1373,1374],{"class":115}," SolverConfig",[92,1376,1377],{"class":98},"()\n",[92,1379,1380,1382,1385],{"class":94,"line":106},[92,1381,504],{"class":98},[92,1383,1384],{"class":115},"withSolutionClass",[92,1386,1387],{"class":98},"(RoutePlan.class)\n",[92,1389,1390,1392,1395],{"class":94,"line":122},[92,1391,504],{"class":98},[92,1393,1394],{"class":115},"withEntityClasses",[92,1396,1397],{"class":98},"(Vehicle.class, Stop.class)\n",[92,1399,1400,1402,1405],{"class":94,"line":129},[92,1401,504],{"class":98},[92,1403,1404],{"class":115},"withConstraintProviderClass",[92,1406,1407],{"class":98},"(RoutingConstraints.class)\n",[92,1409,1410,1412,1415],{"class":94,"line":154},[92,1411,504],{"class":98},[92,1413,1414],{"class":115},"withTerminationSpentLimit",[92,1416,1417],{"class":98},"(perDay);\n",[92,1419,1420,1423,1426,1429,1431,1434,1436,1438,1441,1444,1447,1450],{"class":94,"line":391},[92,1421,1422],{"class":98},"        Solver\u003C",[92,1424,1425],{"class":102},"RoutePlan",[92,1427,1428],{"class":98},"> solver ",[92,1430,169],{"class":102},[92,1432,1433],{"class":98}," SolverFactory.",[92,1435,249],{"class":102},[92,1437,1425],{"class":98},[92,1439,1440],{"class":102},">",[92,1442,1443],{"class":115}," create",[92,1445,1446],{"class":98},"(config).",[92,1448,1449],{"class":115},"buildSolver",[92,1451,225],{"class":98},[14,1453,1454],{},"One solver plans the days one after another:",[82,1456,1458],{"className":84,"code":1457,"language":86,"meta":87,"style":87},"            Day day = JSON.readValue(new File(args[a]), Day.class);\n            RoutePlan plan = solver.solve(new RoutePlan(day));\n",[89,1459,1460,1484],{"__ignoreMap":87},[92,1461,1462,1465,1467,1470,1473,1475,1478,1481],{"class":94,"line":95},[92,1463,1464],{"class":98},"            Day day ",[92,1466,169],{"class":102},[92,1468,1469],{"class":98}," JSON.",[92,1471,1472],{"class":115},"readValue",[92,1474,138],{"class":98},[92,1476,1477],{"class":102},"new",[92,1479,1480],{"class":115}," File",[92,1482,1483],{"class":98},"(args[a]), Day.class);\n",[92,1485,1486,1489,1491,1494,1497,1499,1501,1504],{"class":94,"line":106},[92,1487,1488],{"class":98},"            RoutePlan plan ",[92,1490,169],{"class":102},[92,1492,1493],{"class":98}," solver.",[92,1495,1496],{"class":115},"solve",[92,1498,138],{"class":98},[92,1500,1477],{"class":102},[92,1502,1503],{"class":115}," RoutePlan",[92,1505,1506],{"class":98},"(day));\n",[14,1508,1509],{},"With 20 seconds per day, the run printed:",[1511,1512],"cell-output",{":lines":1513,"label":1514},"[\"Day 1  0hard\u002F0medium\u002F-117365soft  drive 32.60 h  12 trucks, 13 trips\",\"Day 2  0hard\u002F0medium\u002F-127969soft  drive 35.55 h  11 trucks, 14 trips\",\"Day 3  0hard\u002F0medium\u002F-99320soft   drive 27.59 h  10 trucks, 12 trips\",\"Day 4  0hard\u002F0medium\u002F-121173soft  drive 33.66 h  11 trucks, 13 trips\",\"Day 5  0hard\u002F0medium\u002F-126215soft  drive 35.06 h  12 trucks, 14 trips\",\"Week drive 164.46 h\"]","Output",[14,1516,1517],{},"Every day scores 0 hard and 0 medium: the plan breaks no rule and serves every customer. The soft\nscore is minus the drive in seconds. Each trip beyond the number of trucks is a reload: on day 2,\ntrucks come back to load again three times.",[26,1519,1521],{"id":1520},"running-it","Running it",[14,1523,1524,1525,1532],{},"The whole project, data included, is the example\n",[18,1526,1529],{"href":1527,"rel":1528},"https:\u002F\u002Fgithub.com\u002FQuicopt\u002Fquicopt-java\u002Ftree\u002Fmain\u002Fexamples\u002Fvehicle-routing",[22],[89,1530,1531],{},"examples\u002Fvehicle-routing","\nin the client's repository. The client isn't on Maven Central yet, so install the version the\nexample uses first:",[82,1534,1538],{"className":1535,"code":1536,"language":1537,"meta":87,"style":87},"language-bash shiki shiki-themes github-dark","git clone https:\u002F\u002Fgithub.com\u002FQuicopt\u002Fquicopt-java.git\ncd quicopt-java\ngit checkout v0.1.0-beta.1\nmvn -q install -DskipTests\ngit checkout main\n","bash",[89,1539,1540,1551,1559,1569,1583],{"__ignoreMap":87},[92,1541,1542,1545,1548],{"class":94,"line":95},[92,1543,1544],{"class":115},"git",[92,1546,1547],{"class":280}," clone",[92,1549,1550],{"class":280}," https:\u002F\u002Fgithub.com\u002FQuicopt\u002Fquicopt-java.git\n",[92,1552,1553,1556],{"class":94,"line":106},[92,1554,1555],{"class":141},"cd",[92,1557,1558],{"class":280}," quicopt-java\n",[92,1560,1561,1563,1566],{"class":94,"line":122},[92,1562,1544],{"class":115},[92,1564,1565],{"class":280}," checkout",[92,1567,1568],{"class":280}," v0.1.0-beta.1\n",[92,1570,1571,1574,1577,1580],{"class":94,"line":129},[92,1572,1573],{"class":115},"mvn",[92,1575,1576],{"class":141}," -q",[92,1578,1579],{"class":280}," install",[92,1581,1582],{"class":141}," -DskipTests\n",[92,1584,1585,1587,1589],{"class":94,"line":154},[92,1586,1544],{"class":115},[92,1588,1565],{"class":280},[92,1590,1591],{"class":280}," main\n",[14,1593,1594],{},"The example depends on it:",[82,1596,1600],{"className":1597,"code":1598,"language":1599,"meta":87,"style":87},"language-xml shiki shiki-themes github-dark","    \u003Cdependency>\n      \u003CgroupId>com.quicopt\u003C\u002FgroupId>\n      \u003CartifactId>quicopt-planner\u003C\u002FartifactId>\n      \u003Cversion>0.1.0-beta.1\u003C\u002Fversion>\n    \u003C\u002Fdependency>\n","xml",[89,1601,1602,1614,1629,1643,1657],{"__ignoreMap":87},[92,1603,1604,1607,1611],{"class":94,"line":95},[92,1605,1606],{"class":98},"    \u003C",[92,1608,1610],{"class":1609},"s4JwU","dependency",[92,1612,1613],{"class":98},">\n",[92,1615,1616,1619,1622,1625,1627],{"class":94,"line":106},[92,1617,1618],{"class":98},"      \u003C",[92,1620,1621],{"class":1609},"groupId",[92,1623,1624],{"class":98},">com.quicopt\u003C\u002F",[92,1626,1621],{"class":1609},[92,1628,1613],{"class":98},[92,1630,1631,1633,1636,1639,1641],{"class":94,"line":122},[92,1632,1618],{"class":98},[92,1634,1635],{"class":1609},"artifactId",[92,1637,1638],{"class":98},">quicopt-planner\u003C\u002F",[92,1640,1635],{"class":1609},[92,1642,1613],{"class":98},[92,1644,1645,1647,1650,1653,1655],{"class":94,"line":129},[92,1646,1618],{"class":98},[92,1648,1649],{"class":1609},"version",[92,1651,1652],{"class":98},">0.1.0-beta.1\u003C\u002F",[92,1654,1649],{"class":1609},[92,1656,1613],{"class":98},[92,1658,1659,1662,1664],{"class":94,"line":154},[92,1660,1661],{"class":98},"    \u003C\u002F",[92,1663,1610],{"class":1609},[92,1665,1613],{"class":98},[14,1667,1668,1669,1673,1674,1677],{},"The service needs an API key. ",[18,1670,1672],{"href":1671},"\u002Fsignup","Register"," to receive one by email, and put it into the\nenvironment variable ",[89,1675,1676],{},"QUICOPT_API_KEY",". Then build with JDK 21 or later and run:",[82,1679,1681],{"className":1535,"code":1680,"language":1537,"meta":87,"style":87},"cd examples\u002Fvehicle-routing\nmvn -q package\njava -jar target\u002Frouting.jar 20 data\u002Fday-1.json data\u002Fday-2.json data\u002Fday-3.json data\u002Fday-4.json data\u002Fday-5.json\n",[89,1682,1683,1690,1699],{"__ignoreMap":87},[92,1684,1685,1687],{"class":94,"line":95},[92,1686,1555],{"class":141},[92,1688,1689],{"class":280}," examples\u002Fvehicle-routing\n",[92,1691,1692,1694,1696],{"class":94,"line":106},[92,1693,1573],{"class":115},[92,1695,1576],{"class":141},[92,1697,1698],{"class":280}," package\n",[92,1700,1701,1703,1706,1709,1712,1715,1718,1721,1724],{"class":94,"line":122},[92,1702,86],{"class":115},[92,1704,1705],{"class":141}," -jar",[92,1707,1708],{"class":280}," target\u002Frouting.jar",[92,1710,1711],{"class":141}," 20",[92,1713,1714],{"class":280}," data\u002Fday-1.json",[92,1716,1717],{"class":280}," data\u002Fday-2.json",[92,1719,1720],{"class":280}," data\u002Fday-3.json",[92,1722,1723],{"class":280}," data\u002Fday-4.json",[92,1725,1726],{"class":280}," data\u002Fday-5.json\n",[14,1728,1729,1730,1214],{},"Each day's routes are written to ",[89,1731,1732],{},"plans\u002Fday-\u003Cn>.json",[26,1734,1736],{"id":1735},"your-own-model","Your own model",[14,1738,1739],{},"The Java client is in beta. Questions about it, or a planning model you want to run on Quicopt?\nJust ask:",[1741,1742],"contact-cta",{"sub":1743,"title":1744},"Whether something's unclear or you want to run your own planning model on Quicopt — tell us what you're planning.","Questions? Talk to us.",[1746,1747,1748],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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 .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":87,"searchDepth":106,"depth":106,"links":1750},[1751,1752,1753,1754,1755,1756,1757,1758],{"id":28,"depth":106,"text":29},{"id":76,"depth":106,"text":77},{"id":310,"depth":106,"text":311},{"id":712,"depth":106,"text":713},{"id":1184,"depth":106,"text":1185},{"id":1353,"depth":106,"text":1354},{"id":1520,"depth":106,"text":1521},{"id":1735,"depth":106,"text":1736},"A real week of deliveries with trailers, reloads, time windows and a duty day, planned with the Java client quicopt-java on the Quicopt planner service.","md",null,{},false,"\u002Fdeveloper\u002Fvehicle-routing-java",{"title":5,"description":1759},"developer\u002Fvehicle-routing-java","QfUZYZV51LHKY2PrYDFSTfRrNb2vT8PMogzPF2tvjcs",1793877539754]