Repository: PyVRP/PyVRP
Pull request #425 (2024-01-23 16:26:55)

Add support for reading MDVRPTW instances
This PR:

- [x] Is part of #305, #252.
- [x] Adds and passes relevant tests.
- [x] Has well-formatted code and documentation.

See https://github.com/PyVRP/Instances/pull/6 for the instances this is meant to read.

### Benchmark on MDVRPTW

A bit of a try-out (345da5ad5f524ac3b246018a316c7c27881350c6; 10x seeds; 1h; job ID 6963529):
```
              MEAN      MIN      MAX  COV
    inst
obj PR11A   6670.5   6659.1   6678.7  0.1
    PR11B   4816.6   4814.6   4825.4  0.1
    PR12A   8183.7   8159.9   8205.2  0.2
    PR12B   6030.0   6016.4   6041.8  0.2
    PR13A   9568.1   9545.6   9616.2  0.2
    PR13B   7229.8   7216.5   7259.7  0.2
    PR14A  11023.9  10981.6  11067.7  0.2
    PR14B   8723.8   8690.6   8742.9  0.2
    PR15A  12862.2  12795.5  12949.7  0.3
    PR15B  10326.8  10291.2  10364.1  0.3
    PR16A  14199.2  14091.5  14302.3  0.5
    PR16B  11380.2  11333.2  11427.8  0.3
    PR17A   6295.7   6292.6   6302.3  0.1
    PR17B   4773.9   4771.2   4782.4  0.1
    PR18A   8232.7   8211.7   8253.1  0.2
    PR18B   6470.3   6459.5   6487.9  0.2
    PR19A  10624.8  10580.7  10672.2  0.2
    PR19B   8142.3   8119.4   8171.0  0.3
    PR20A  11808.5  11753.0  11871.6  0.3
    PR20B  10140.4  10102.5  10177.9  0.3
    PR21A   6238.3   6234.4   6254.7  0.1
    PR21B   4830.3   4822.4   4840.5  0.1
    PR22A   7942.4   7910.7   7981.6  0.3
    PR22B   6448.3   6422.8   6479.8  0.3
    PR23A   9853.1   9771.4   9933.5  0.4
    PR23B   8402.0   8362.9   8430.6  0.3
    PR24A  11832.8  11747.0  11953.4  0.5
    PR24B  10612.2  10570.0  10642.6  0.2
```

Relevant comparison (Table 8 of [Vidal et al. (2013)](https://doi.org/10.1016/j.cor.2012.07.018)):
![image](https://github.com/PyVRP/PyVRP/assets/16272507/ad83dee7-d205-4935-b5a1-de9d6b32d13a)

<details>

**Notes**:

- It is essential that you add a test when making code changes.
  This keeps the code coverage level up, and helps ensure the changes work as intended.
- When fixing a bug, you must add a test that would produce the bug in the master branch, and then show that it is fixed with the new code. 
- New code additions must be well formatted. Changes should pass the pre-commit workflow, which you can set up locally using [pre-commit](https://pre-commit.com/#intro). 
- Docstring additions must render correctly, including escapes and LaTeX.

</details>


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PyVRP/PyVRP/pull/425?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PyVRP) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`1c439c3`)](https://app.codecov.io/gh/PyVRP/PyVRP/commit/1c439c39419c205e76d5c3b45c8ddc06c3e64c21?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PyVRP) 94.55% compared to head [(`0088726`)](https://app.codecov.io/gh/PyVRP/PyVRP/pull/425?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PyVRP) 94.57%.
> Report is 1 commits behind head on main.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #425      +/-   ##
==========================================
+ Coverage   94.55%   94.57%   +0.01%     
==========================================
  Files          72       72              
  Lines        2683     2691       +8     
  Branches      246      246              
==========================================
+ Hits         2537     2545       +8     
  Misses         32       32              
  Partials      114      114              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PyVRP/PyVRP/pull/425?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PyVRP).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PyVRP).

N-Wouda: We'll need to think of a way to write solutions for these instances next, but I think that can be done outside of this PR (instead, in e.g. #252).

N-Wouda: @leonlan can you review this in tandem with https://github.com/PyVRP/Instances/pull/6?
N-Wouda: Benchmark's running! 🤞 
N-Wouda: Didn't quite work. When running locally I could not really figure this one out, but it could be due to the size of some of the data values (which might at some point exceed what `int` can handle). I've updated the data to return to just floats, and am now running a benchmark in double precision.

N-Wouda: This worked, and is substantially better than the baseline results. I don't know if they're reliable (this being our first time running them), but I cannot think of any reason why they would not be.

All in all I'm pretty happy with this. Let's merge this in, and then think about how we ought to do our benchmarking for MDVRPTW.


Patch:
diff --git a/docs/source/dev/supported_vrplib_fields.rst b/docs/source/dev/supported_vrplib_fields.rst
index 5f4f382..fdf6b3c 100644
--- a/docs/source/dev/supported_vrplib_fields.rst
+++ b/docs/source/dev/supported_vrplib_fields.rst
@@ -41,6 +41,10 @@ PyVRP supports the following specifications:
       Number of vehicles in the instance.
       The number of vehicles defaults to the number of clients in the instance when this specification is not provided: PyVRP assumes an unlimited fleet in this case.
 
+   ``VEHICLES_MAX_DURATION``
+      Maximum route duration for each vehicle.
+      Route durations are assumed to be unconstrained if this value is not specified.
+
 
 Data sections
 -------------
@@ -81,3 +85,7 @@ PyVRP supports the following data sections:
 
    ``TIME_WINDOW_SECTION``
       Array of :math:`[e, l]` time window data, for each location.
+
+   ``VEHICLES_DEPOT_SECTION``
+      Depot assignments for each vehicle, typically used in multi-depot instances.
+      Vehicles are assigned to the first depot if this section is not provided.
diff --git a/instances b/instances
index 74369a9..24d8161 160000
--- a/instances
+++ b/instances
@@ -1,1 +1,1 @@
-Subproject commit 74369a9b22f1290a418f05a27b8e9e87396633d3
+Subproject commit 24d8161434e063326df6f22c0455f66c3f220768
diff --git a/pyvrp/read.py b/pyvrp/read.py
index 9ac1be4..912d106 100644
--- a/pyvrp/read.py
+++ b/pyvrp/read.py
@@ -1,7 +1,8 @@
 import functools
 import pathlib
+from collections import Counter
 from numbers import Number
-from typing import Callable, Union
+from typing import Callable, Optional, Union
 from warnings import warn
 
 import numpy as np
@@ -112,6 +113,13 @@ def read(
     num_vehicles: int = instance.get("vehicles", dimension - 1)
     capacity: int = instance.get("capacity", _INT_MAX)
 
+    # If this value is supplied, we should pass it through the round func and
+    # then unwrap the result. If it's not given, the default value is None,
+    # which PyVRP understands.
+    max_duration: Optional[int] = instance.get("vehicles_max_duration")
+    if max_duration is not None:
+        max_duration = round_func(np.array([max_duration])).item()
+
     distances: np.ndarray = round_func(instance["edge_weight"])
 
     if "demand" in instance:
@@ -148,6 +156,15 @@ def read(
         service_times = np.zeros(dimension, dtype=int)
         time_windows = np.zeros((dimension, 2), dtype=int)
 
+    if "vehicles_depot" in instance:
+        counts = Counter(instance["vehicles_depot"]).items()
+        depot_vehicle_pairs = [  # pairs of (depot idx, # vehicles at depot)
+            (depot_idx - 1, num_available)
+            for depot_idx, num_available in counts
+        ]
+    else:
+        depot_vehicle_pairs = [(0, num_vehicles)]
+
     if "release_time" in instance:
         release_times: np.ndarray = round_func(instance["release_time"])
     else:
@@ -184,7 +201,16 @@ def read(
         )
         for idx in range(dimension)
     ]
-    vehicle_types = [VehicleType(num_vehicles, capacity=capacity)]
+
+    vehicle_types = [
+        VehicleType(
+            num_available,
+            capacity,
+            depot_idx,
+            max_duration=max_duration,
+        )
+        for depot_idx, num_available in depot_vehicle_pairs
+    ]
 
     return ProblemData(
         clients[len(depots) :],


Test patch:
diff --git a/pyvrp/tests/data/PR11A.vrp b/pyvrp/tests/data/PR11A.vrp
new file mode 100644
index 0000000..f88675b
--- /dev/null
+++ b/pyvrp/tests/data/PR11A.vrp
@@ -0,0 +1,1515 @@
+NAME: PR11A
+COMMENT: After Vidal et al. (2013) and adapted to PyVRP's VRPLIB dialect.
+TYPE: MDVRPTW
+EDGE_WEIGHT_TYPE: EUC_2D
+DIMENSION: 364
+VEHICLES: 40
+CAPACITY: 200
+VEHICLES_MAX_DURATION: 450
+NODE_COORD_SECTION
+1	2.958	4.357
+2	21.494	-8.63
+3	-43.073	-18.094
+4	-10.406	-37.888
+5	70.769	-29.196
+6	-79.231	74.084
+7	97.576	-85.391
+8	68.832	-19.019
+9	-87.907	91.417
+10	-27.185	1.319
+11	49.321	40.594
+12	93.058	62.799
+13	66.827	-43.508
+14	77.556	98.223
+15	-60.744	86.188
+16	79.995	-50.762
+17	64.497	14.175
+18	94.934	54.93
+19	-52.087	66.721
+20	15.07	8.961
+21	35.846	-64.678
+22	23.023	83.251
+23	-11.115	10.705
+24	64.93	39.527
+25	-70.629	-90.927
+26	-87.41	68.209
+27	27.174	-34.118
+28	-53.887	46.247
+29	-95.191	-1.038
+30	19.02	74.803
+31	-26.046	-12.517
+32	19.23	74.597
+33	-95.339	60.508
+34	48.564	-11.497
+35	42.316	-4.864
+36	43.546	61.968
+37	29.527	-98.943
+38	42.071	-69.016
+39	-45.613	29.637
+40	-9.572	-86.566
+41	3.917	33.51
+42	85.217	-79.948
+43	57.628	80.163
+44	96.538	57.972
+45	91.649	-41.534
+46	9.78	93.652
+47	63.531	94.46
+48	77.053	47.884
+49	-36.597	-47.695
+50	-72.402	23.878
+51	-78.868	-50.176
+52	4.082	-89.608
+53	0.059	92.423
+54	-76.866	47.64
+55	83.604	-95.448
+56	93.73	52.683
+57	59.516	22.106
+58	-77.177	-94.324
+59	34.638	-85.192
+60	-55.442	-62.205
+61	-98.137	-73.146
+62	-73.753	-18.809
+63	-13.928	37.488
+64	82.692	-16.542
+65	19.734	73.01
+66	-97.99	57.599
+67	57.439	-82.957
+68	-82.281	47.029
+69	44.269	-40.427
+70	95.576	-2.344
+71	-8.428	-84.828
+72	66.208	-86.82
+73	66.656	-78.973
+74	40.64	90.624
+75	3.163	-41.893
+76	62.926	93.578
+77	-88.784	48.968
+78	-35.348	69.916
+79	48.137	21.153
+80	-87.774	53.083
+81	-33.654	28.555
+82	-81.397	-82.066
+83	92.41	-10.076
+84	70.726	-85.392
+85	35.108	40.007
+86	-54.311	90.973
+87	35.154	33.779
+88	-33.562	-83.864
+89	42.256	-55.045
+90	-94.215	-36.933
+91	89.73	42.601
+92	-57.644	65.021
+93	-78.914	80.272
+94	23.701	24.168
+95	79.622	96.988
+96	-63.145	-5.725
+97	93.216	8.942
+98	-13.405	93.229
+99	-50.427	-72.999
+100	-84.72	-67.492
+101	44.419	41.31
+102	-62.745	65.696
+103	56.967	40.619
+104	60.497	-71.289
+105	59.729	-28.772
+106	27.977	10.761
+107	-11.22	55.675
+108	15.038	-71.58
+109	2.362	-68.305
+110	58.311	48.743
+111	-68.317	85.241
+112	61.408	-70.084
+113	26.996	69.617
+114	62.101	-59.575
+115	65.2	-79.032
+116	28.54	-57.815
+117	-70.423	-80.983
+118	88.97	-62.216
+119	90.236	65.352
+120	50.477	43.724
+121	63.462	15.785
+122	-96.279	-76.955
+123	-6.412	-8.384
+124	-88.11	36.757
+125	57.635	74.254
+126	75.649	-22.328
+127	22.128	-58.142
+128	-80.607	-68.704
+129	77.577	55.188
+130	44.177	29.352
+131	31.48	34.232
+132	-53.086	30.88
+133	-29.757	-67.398
+134	55.592	-74.02
+135	-25.893	35.631
+136	20.784	20.082
+137	46.345	-91.145
+138	-66.123	-46.35
+139	75.201	-35.894
+140	36.561	60.148
+141	61.374	97.776
+142	81.275	81.851
+143	-49.189	-60.161
+144	44.034	-53.23
+145	37.828	-68.314
+146	-53.671	-23.869
+147	-57.209	44.528
+148	39.521	-95.385
+149	-91.131	-65.33
+150	8.347	65.426
+151	75.979	89.084
+152	99.637	-49.794
+153	-23.496	95.279
+154	-79.277	-22.748
+155	15.242	95.652
+156	-84.997	-0.574
+157	91.039	69.953
+158	-60.235	81.351
+159	17.006	56.009
+160	-66.73	-41.659
+161	-4.533	9.104
+162	-6.237	85.991
+163	-75.497	97.859
+164	7.882	40.441
+165	-52.731	99.84
+166	89.001	21.294
+167	-29.736	-36.741
+168	19.658	-61.825
+169	83.76	-44.755
+170	-81.878	-88.726
+171	15.145	38.068
+172	71.735	46.04
+173	58.221	92.206
+174	-34.867	-90.024
+175	4.769	90.88
+176	-79.62	43.504
+177	79.308	-61.354
+178	-97.764	4.529
+179	-71.019	18.95
+180	-63.107	50.759
+181	70.727	97.116
+182	98.13	-21.122
+183	90.441	89.427
+184	-89.295	-86.07
+185	54.095	31.333
+186	-47.018	-99.842
+187	77.426	-32.749
+188	-50.837	44.492
+189	-24.373	-41.795
+190	-79.746	52.476
+191	21.31	-51.234
+192	57.616	68.896
+193	-34.608	-70.334
+194	-42.363	92.175
+195	43.436	-18.255
+196	-68.816	28.156
+197	79.252	99.155
+198	73.23	2.06
+199	-8.354	41.97
+200	56.291	-85.561
+201	65.575	15.525
+202	89.525	0.676
+203	-86.819	-15.437
+204	-75.8	30.985
+205	28.025	64.54
+206	-88.73	20.473
+207	68.548	47.348
+208	-67.247	-58.783
+209	84.605	-23.11
+210	-14.231	82.473
+211	-40.406	-74.69
+212	-24.282	78.877
+213	-28.25	-84.13
+214	64.565	-26.288
+215	85.108	-52.882
+216	-79.472	45.79
+217	-5.336	88.046
+218	-78.764	49.502
+219	-63.111	73.002
+220	-3.891	-99.948
+221	7.549	-5.308
+222	19.123	-27.018
+223	44.737	-16.687
+224	72.585	-74.851
+225	-16.839	37.726
+226	94.261	92.015
+227	56.192	-84.283
+228	68.635	26.727
+229	-99.17	28.876
+230	-45.074	50.54
+231	3.857	92.612
+232	20.222	-69.211
+233	-40.476	-77.39
+234	-57.198	-72.777
+235	56.312	65.402
+236	69.7	80.937
+237	90.906	-9.33
+238	-97.436	65.063
+239	42.06	31.785
+240	-73.499	21.246
+241	-85.046	-27.866
+242	49.337	-89.743
+243	35.597	-19.54
+244	-61.688	37.016
+245	42.689	-45.019
+246	92.79	-94.538
+247	24.999	-60.03
+248	-59.224	-77.116
+249	90.548	30.961
+250	-12.275	88.35
+251	55.234	16.229
+252	27.81	98.151
+253	-3.149	-65.864
+254	98.907	-38.92
+255	32.179	79.962
+256	28.28	68.653
+257	18.019	-32.892
+258	-6.911	-77.002
+259	-11.851	64.795
+260	51.136	-85.981
+261	36.686	-22.851
+262	-67.853	-98.845
+263	-80.102	-3.15
+264	87.073	-2.082
+265	-57.074	2.351
+266	75.097	74.816
+267	73.501	3.689
+268	68.862	74.515
+269	-27.734	-38.551
+270	81.097	89.536
+271	78.878	-45.516
+272	23.462	-92.327
+273	-83.392	16.664
+274	-67.957	94.658
+275	7.112	-49.165
+276	-83.452	73.871
+277	94.96	-64.55
+278	84.39	28.653
+279	71.381	-95.378
+280	-32.943	-64.505
+281	-42.012	37.956
+282	-27.482	-72.12
+283	13.155	-61.826
+284	88.124	54.19
+285	-68.968	-71.299
+286	-97.041	9.426
+287	-81.893	-61.942
+288	86.794	-37.83
+289	18.363	29.664
+290	-50.519	95.311
+291	51.922	13.562
+292	-63.163	-54.967
+293	-97.65	23.888
+294	-6.737	28.808
+295	10.109	-92.166
+296	-45.46	-3.36
+297	81.001	47.966
+298	-44.665	-58.114
+299	55.571	-22.96
+300	84.259	-75.528
+301	84.793	68.606
+302	34.711	-40.102
+303	-89.04	-7.186
+304	-61.039	40.829
+305	-24.26	-11.067
+306	64.501	-53.568
+307	-0.296	83.299
+308	-57.614	-52.736
+309	49.224	34.873
+310	-9.184	-93.817
+311	32.108	-19.172
+312	-88.309	-56.421
+313	-80.604	-91.908
+314	95.905	66.735
+315	80.853	70.21
+316	-15.086	86.436
+317	-36.493	58.852
+318	42.46	-68.931
+319	-50.904	87.312
+320	-71.612	-23.398
+321	92.321	35.984
+322	48.304	35.815
+323	11.621	-71.534
+324	-14.256	64.879
+325	-23.533	40.119
+326	-0.337	53.996
+327	-53.791	73.958
+328	-36.616	-99.178
+329	33.47	-93.486
+330	-98.427	-35.969
+331	94.118	-46.456
+332	15.39	26.725
+333	12.05	27.397
+334	-77.921	-31.492
+335	62.305	72.457
+336	87.468	8.826
+337	-94.983	70.145
+338	43.498	41.279
+339	-70.285	31.852
+340	80.375	27.776
+341	8.985	93.313
+342	92.54	-52.894
+343	72.002	16.913
+344	97.377	-23.339
+345	36.717	47.516
+346	7.149	-71.451
+347	-43.206	42.939
+348	-83.605	90.892
+349	1.67	94.475
+350	40.715	-1.898
+351	-18.009	96.118
+352	68.544	32.884
+353	65.797	95.628
+354	22.312	73.149
+355	52.49	17.437
+356	73.874	77.598
+357	-92.253	-90.888
+358	41.127	-58.161
+359	96.775	-23.832
+360	-74.067	-94.372
+361	-0.832	-87.738
+362	38.495	1.565
+363	61.817	-2.644
+364	-73.967	-4.3
+DEMAND_SECTION
+1	0
+2	0
+3	0
+4	0
+5	25
+6	13
+7	19
+8	14
+9	16
+10	14
+11	4
+12	14
+13	11
+14	20
+15	20
+16	14
+17	14
+18	23
+19	23
+20	23
+21	7
+22	12
+23	23
+24	23
+25	12
+26	3
+27	20
+28	9
+29	3
+30	13
+31	24
+32	9
+33	13
+34	23
+35	17
+36	24
+37	7
+38	14
+39	20
+40	23
+41	5
+42	18
+43	5
+44	18
+45	20
+46	19
+47	2
+48	17
+49	9
+50	1
+51	18
+52	1
+53	12
+54	20
+55	2
+56	5
+57	16
+58	8
+59	21
+60	25
+61	7
+62	1
+63	21
+64	5
+65	16
+66	13
+67	1
+68	5
+69	8
+70	15
+71	5
+72	2
+73	22
+74	21
+75	6
+76	23
+77	20
+78	22
+79	6
+80	10
+81	15
+82	2
+83	16
+84	24
+85	18
+86	19
+87	17
+88	6
+89	1
+90	20
+91	20
+92	24
+93	17
+94	13
+95	14
+96	10
+97	14
+98	16
+99	24
+100	25
+101	22
+102	19
+103	7
+104	16
+105	4
+106	23
+107	20
+108	3
+109	14
+110	19
+111	15
+112	7
+113	21
+114	16
+115	20
+116	18
+117	23
+118	16
+119	12
+120	19
+121	22
+122	8
+123	12
+124	11
+125	21
+126	20
+127	3
+128	5
+129	4
+130	11
+131	12
+132	7
+133	9
+134	16
+135	17
+136	16
+137	13
+138	23
+139	3
+140	7
+141	7
+142	19
+143	4
+144	25
+145	25
+146	19
+147	17
+148	15
+149	25
+150	13
+151	21
+152	12
+153	14
+154	3
+155	8
+156	6
+157	7
+158	18
+159	9
+160	21
+161	3
+162	21
+163	21
+164	8
+165	22
+166	16
+167	8
+168	20
+169	24
+170	3
+171	2
+172	15
+173	22
+174	6
+175	22
+176	4
+177	10
+178	16
+179	5
+180	10
+181	19
+182	16
+183	2
+184	19
+185	21
+186	23
+187	20
+188	11
+189	22
+190	2
+191	20
+192	2
+193	16
+194	2
+195	12
+196	22
+197	10
+198	16
+199	7
+200	4
+201	20
+202	6
+203	18
+204	23
+205	22
+206	9
+207	25
+208	3
+209	6
+210	20
+211	16
+212	10
+213	9
+214	25
+215	4
+216	19
+217	10
+218	4
+219	20
+220	2
+221	1
+222	15
+223	9
+224	20
+225	8
+226	15
+227	3
+228	17
+229	25
+230	16
+231	20
+232	20
+233	21
+234	23
+235	19
+236	5
+237	16
+238	18
+239	21
+240	11
+241	8
+242	12
+243	17
+244	2
+245	5
+246	8
+247	14
+248	23
+249	2
+250	6
+251	24
+252	16
+253	14
+254	24
+255	19
+256	12
+257	5
+258	5
+259	10
+260	1
+261	20
+262	2
+263	13
+264	3
+265	19
+266	14
+267	12
+268	12
+269	7
+270	9
+271	16
+272	13
+273	10
+274	11
+275	18
+276	18
+277	4
+278	5
+279	16
+280	18
+281	21
+282	8
+283	13
+284	19
+285	22
+286	2
+287	5
+288	5
+289	18
+290	8
+291	24
+292	9
+293	1
+294	16
+295	7
+296	14
+297	2
+298	15
+299	11
+300	6
+301	3
+302	22
+303	13
+304	19
+305	3
+306	10
+307	10
+308	11
+309	13
+310	11
+311	23
+312	9
+313	16
+314	16
+315	18
+316	18
+317	16
+318	10
+319	2
+320	20
+321	16
+322	11
+323	12
+324	23
+325	20
+326	24
+327	6
+328	9
+329	17
+330	17
+331	24
+332	17
+333	9
+334	7
+335	10
+336	10
+337	5
+338	21
+339	21
+340	23
+341	3
+342	12
+343	20
+344	15
+345	4
+346	12
+347	5
+348	10
+349	19
+350	12
+351	16
+352	7
+353	7
+354	20
+355	6
+356	8
+357	6
+358	9
+359	1
+360	1
+361	8
+362	15
+363	22
+364	18
+SERVICE_TIME_SECTION
+1	0
+2	0
+3	0
+4	0
+5	20
+6	15
+7	10
+8	5
+9	12
+10	11
+11	16
+12	21
+13	23
+14	14
+15	20
+16	25
+17	8
+18	7
+19	11
+20	12
+21	3
+22	5
+23	12
+24	4
+25	11
+26	10
+27	9
+28	12
+29	17
+30	7
+31	1
+32	2
+33	24
+34	2
+35	24
+36	11
+37	22
+38	7
+39	13
+40	3
+41	20
+42	20
+43	19
+44	21
+45	23
+46	15
+47	21
+48	12
+49	5
+50	24
+51	17
+52	23
+53	17
+54	16
+55	9
+56	5
+57	5
+58	18
+59	10
+60	18
+61	19
+62	18
+63	22
+64	10
+65	1
+66	7
+67	14
+68	12
+69	2
+70	13
+71	23
+72	11
+73	9
+74	17
+75	22
+76	25
+77	11
+78	13
+79	11
+80	13
+81	2
+82	2
+83	7
+84	3
+85	19
+86	10
+87	24
+88	23
+89	16
+90	2
+91	13
+92	7
+93	10
+94	20
+95	25
+96	4
+97	17
+98	8
+99	3
+100	11
+101	11
+102	5
+103	5
+104	18
+105	9
+106	9
+107	3
+108	22
+109	2
+110	3
+111	12
+112	23
+113	17
+114	14
+115	11
+116	24
+117	1
+118	24
+119	19
+120	5
+121	21
+122	3
+123	4
+124	13
+125	7
+126	21
+127	4
+128	25
+129	23
+130	17
+131	22
+132	2
+133	22
+134	18
+135	13
+136	1
+137	15
+138	20
+139	10
+140	5
+141	22
+142	25
+143	7
+144	10
+145	16
+146	10
+147	18
+148	7
+149	22
+150	3
+151	14
+152	9
+153	25
+154	22
+155	22
+156	3
+157	21
+158	5
+159	21
+160	24
+161	25
+162	4
+163	7
+164	2
+165	17
+166	23
+167	22
+168	7
+169	1
+170	16
+171	1
+172	24
+173	14
+174	8
+175	24
+176	19
+177	8
+178	4
+179	17
+180	4
+181	8
+182	10
+183	2
+184	17
+185	9
+186	16
+187	12
+188	19
+189	12
+190	16
+191	2
+192	14
+193	13
+194	17
+195	17
+196	2
+197	6
+198	12
+199	23
+200	2
+201	16
+202	22
+203	7
+204	15
+205	23
+206	15
+207	22
+208	1
+209	1
+210	8
+211	25
+212	22
+213	20
+214	16
+215	6
+216	19
+217	21
+218	2
+219	19
+220	11
+221	14
+222	21
+223	5
+224	9
+225	17
+226	12
+227	18
+228	21
+229	3
+230	14
+231	22
+232	15
+233	14
+234	11
+235	20
+236	16
+237	5
+238	4
+239	20
+240	21
+241	15
+242	11
+243	20
+244	10
+245	14
+246	23
+247	4
+248	20
+249	25
+250	8
+251	7
+252	4
+253	1
+254	23
+255	20
+256	22
+257	6
+258	24
+259	16
+260	11
+261	1
+262	23
+263	2
+264	16
+265	16
+266	2
+267	20
+268	7
+269	17
+270	4
+271	23
+272	12
+273	18
+274	2
+275	7
+276	14
+277	24
+278	25
+279	3
+280	7
+281	3
+282	4
+283	12
+284	22
+285	1
+286	19
+287	16
+288	15
+289	9
+290	23
+291	25
+292	19
+293	15
+294	13
+295	10
+296	20
+297	18
+298	4
+299	22
+300	3
+301	10
+302	25
+303	16
+304	13
+305	11
+306	15
+307	8
+308	16
+309	23
+310	24
+311	1
+312	10
+313	1
+314	13
+315	11
+316	14
+317	6
+318	25
+319	12
+320	21
+321	24
+322	4
+323	23
+324	16
+325	13
+326	15
+327	17
+328	22
+329	15
+330	6
+331	8
+332	7
+333	23
+334	16
+335	24
+336	9
+337	17
+338	20
+339	10
+340	23
+341	6
+342	8
+343	13
+344	4
+345	19
+346	15
+347	19
+348	15
+349	13
+350	1
+351	3
+352	7
+353	18
+354	22
+355	6
+356	22
+357	5
+358	15
+359	20
+360	7
+361	16
+362	2
+363	11
+364	17
+TIME_WINDOW_SECTION
+1	0	1000
+2	0	1000
+3	0	1000
+4	0	1000
+5	146	281
+6	84	204
+7	333	446
+8	445	609
+9	449	618
+10	231	339
+11	109	276
+12	147	275
+13	254	371
+14	111	237
+15	116	254
+16	147	315
+17	91	263
+18	67	192
+19	268	375
+20	93	230
+21	316	422
+22	74	213
+23	217	394
+24	433	595
+25	113	260
+26	145	240
+27	437	594
+28	413	554
+29	90	248
+30	402	494
+31	100	257
+32	437	553
+33	72	247
+34	292	471
+35	462	633
+36	297	425
+37	179	323
+38	330	458
+39	161	293
+40	432	579
+41	422	555
+42	259	364
+43	258	388
+44	356	535
+45	214	316
+46	158	256
+47	228	405
+48	359	514
+49	117	248
+50	153	286
+51	350	470
+52	136	287
+53	410	584
+54	155	259
+55	141	287
+56	239	411
+57	183	321
+58	474	620
+59	472	575
+60	143	248
+61	141	318
+62	339	483
+63	80	189
+64	418	538
+65	172	313
+66	427	601
+67	276	456
+68	78	215
+69	230	391
+70	411	524
+71	137	254
+72	99	278
+73	387	513
+74	284	396
+75	255	379
+76	168	290
+77	470	605
+78	369	545
+79	314	470
+80	212	383
+81	447	585
+82	149	294
+83	273	435
+84	78	222
+85	233	358
+86	312	438
+87	443	556
+88	426	525
+89	194	368
+90	240	383
+91	99	264
+92	141	318
+93	261	407
+94	87	262
+95	146	323
+96	458	616
+97	269	377
+98	119	245
+99	403	500
+100	167	297
+101	147	291
+102	262	364
+103	449	616
+104	364	526
+105	202	316
+106	94	258
+107	176	301
+108	446	554
+109	422	591
+110	397	563
+111	260	435
+112	121	259
+113	156	312
+114	435	576
+115	449	544
+116	252	417
+117	317	448
+118	235	393
+119	323	500
+120	95	222
+121	84	207
+122	174	343
+123	288	419
+124	249	391
+125	337	479
+126	134	249
+127	283	407
+128	126	221
+129	474	586
+130	376	504
+131	467	624
+132	171	285
+133	200	326
+134	189	332
+135	349	446
+136	407	533
+137	462	592
+138	305	445
+139	275	399
+140	340	462
+141	99	193
+142	452	614
+143	316	408
+144	166	331
+145	315	481
+146	408	521
+147	304	435
+148	387	521
+149	416	564
+150	297	449
+151	296	404
+152	349	476
+153	373	514
+154	401	525
+155	404	501
+156	348	482
+157	105	212
+158	191	350
+159	282	376
+160	257	368
+161	263	431
+162	427	538
+163	464	574
+164	280	436
+165	345	517
+166	311	421
+167	315	489
+168	256	400
+169	397	498
+170	84	180
+171	162	280
+172	74	183
+173	313	404
+174	78	172
+175	304	451
+176	467	578
+177	428	574
+178	246	368
+179	450	573
+180	448	549
+181	82	253
+182	267	416
+183	246	404
+184	160	256
+185	361	535
+186	173	287
+187	347	448
+188	94	260
+189	388	530
+190	387	555
+191	74	236
+192	426	602
+193	197	354
+194	262	407
+195	248	375
+196	351	450
+197	468	563
+198	334	480
+199	211	350
+200	306	454
+201	255	403
+202	456	610
+203	466	623
+204	294	415
+205	249	354
+206	271	390
+207	205	299
+208	230	366
+209	159	283
+210	107	268
+211	447	600
+212	265	408
+213	199	294
+214	292	447
+215	304	424
+216	236	382
+217	212	365
+218	273	407
+219	264	357
+220	373	509
+221	309	425
+222	241	412
+223	122	280
+224	194	346
+225	235	364
+226	361	505
+227	372	488
+228	378	526
+229	289	468
+230	377	544
+231	62	156
+232	347	453
+233	120	252
+234	220	338
+235	213	379
+236	389	493
+237	169	289
+238	128	304
+239	292	458
+240	225	334
+241	455	624
+242	78	217
+243	133	226
+244	66	157
+245	210	335
+246	293	453
+247	329	420
+248	219	338
+249	168	322
+250	357	524
+251	328	436
+252	91	267
+253	73	202
+254	366	512
+255	473	589
+256	189	301
+257	270	413
+258	333	503
+259	72	221
+260	199	372
+261	397	519
+262	189	283
+263	92	239
+264	470	579
+265	131	237
+266	164	262
+267	194	350
+268	284	389
+269	172	278
+270	104	281
+271	215	389
+272	331	501
+273	312	472
+274	265	371
+275	140	257
+276	209	378
+277	83	229
+278	109	271
+279	81	261
+280	282	431
+281	111	268
+282	195	344
+283	255	410
+284	368	464
+285	118	208
+286	335	431
+287	212	308
+288	282	446
+289	244	356
+290	105	212
+291	101	238
+292	259	383
+293	85	216
+294	245	398
+295	385	532
+296	229	379
+297	194	322
+298	290	445
+299	128	299
+300	66	162
+301	183	286
+302	220	373
+303	367	544
+304	439	557
+305	229	327
+306	339	479
+307	239	331
+308	192	290
+309	181	284
+310	348	487
+311	302	419
+312	161	282
+313	389	492
+314	466	563
+315	207	299
+316	332	469
+317	379	511
+318	194	310
+319	86	257
+320	295	408
+321	384	556
+322	293	454
+323	381	542
+324	237	370
+325	428	564
+326	331	480
+327	183	347
+328	445	549
+329	112	282
+330	426	577
+331	479	656
+332	443	573
+333	309	419
+334	403	553
+335	407	525
+336	365	484
+337	382	547
+338	223	319
+339	296	398
+340	397	569
+341	356	447
+342	275	437
+343	422	557
+344	268	367
+345	330	508
+346	153	321
+347	143	281
+348	265	403
+349	257	362
+350	68	243
+351	261	353
+352	244	374
+353	300	472
+354	94	245
+355	233	391
+356	400	518
+357	261	400
+358	348	509
+359	291	430
+360	146	294
+361	85	223
+362	251	359
+363	175	336
+364	191	367
+VEHICLES_DEPOT_SECTION
+1	1
+2	1
+3	1
+4	1
+5	1
+6	1
+7	1
+8	1
+9	1
+10	1
+11	2
+12	2
+13	2
+14	2
+15	2
+16	2
+17	2
+18	2
+19	2
+20	2
+21	3
+22	3
+23	3
+24	3
+25	3
+26	3
+27	3
+28	3
+29	3
+30	3
+31	4
+32	4
+33	4
+34	4
+35	4
+36	4
+37	4
+38	4
+39	4
+40	4
+DEPOT_SECTION
+1
+2
+3
+4
+EOF
diff --git a/pyvrp/tests/test_read.py b/pyvrp/tests/test_read.py
index b10bdd1..e85c27b 100644
--- a/pyvrp/tests/test_read.py
+++ b/pyvrp/tests/test_read.py
@@ -1,6 +1,7 @@
 from math import sqrt
 
 from numpy.testing import (
+    assert_,
     assert_allclose,
     assert_equal,
     assert_raises,
@@ -295,3 +296,35 @@ def test_multiple_depots():
     assert_allclose(depot2.y, 1_297)
     assert_allclose(depot2.tw_early, 5_000)
     assert_allclose(depot2.tw_late, 20_000)
+
+
+def test_mdvrptw_instance():
+    """
+    Tests that reading an MDVRPTW instance happens correctly, particularly the
+    maximum route duration and multiple depot aspects.
+    """
+    data = read("data/PR11A.vrp", round_func="trunc")
+
+    assert_equal(data.num_locations, 364)
+    assert_equal(data.num_depots, 4)
+    assert_equal(data.num_clients, 360)
+
+    assert_equal(data.num_vehicles, 40)
+    assert_equal(data.num_vehicle_types, 4)  # one vehicle type per depot
+
+    for idx in range(data.num_vehicle_types):
+        vehicle_type = data.vehicle_type(idx)
+
+        # There should be ten vehicles for each depot, with the following
+        # capacities and maximum route durations.
+        assert_equal(vehicle_type.num_available, 10)
+        assert_equal(vehicle_type.depot, idx)
+        assert_allclose(vehicle_type.capacity, 200)
+        assert_allclose(vehicle_type.max_duration, 450)
+
+    # We haven't seen many instances with negative coordinates, but this
+    # MDVRPTW instance has those. That should be allowed.
+    assert_(any(depot.x < 0) for depot in data.depots())
+    assert_(any(depot.y < 0) for depot in data.depots())
+    assert_(any(client.x < 0) for client in data.clients())
+    assert_(any(client.y < 0) for client in data.clients())