nextroute 1.11.1.dev0__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nextroute might be problematic. Click here for more details.

@@ -0,0 +1,140 @@
1
+ # © 2019-present nextmv.io inc
2
+
3
+ """
4
+ Defines the output class.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from nextroute.base_model import BaseModel
11
+ from nextroute.check.schema import Output as CheckOutput
12
+ from nextroute.schema.location import Location
13
+ from nextroute.schema.statistics import Statistics
14
+
15
+
16
+ class Version(BaseModel):
17
+ """A version used for solving."""
18
+
19
+ sdk: str
20
+ """Nextmv SDK."""
21
+
22
+
23
+ class StopOutput(BaseModel):
24
+ """Basic structure for the output of a stop."""
25
+
26
+ id: str
27
+ """ID of the stop."""
28
+ location: Location
29
+ """Location of the stop."""
30
+
31
+ custom_data: Optional[Any] = None
32
+ """Custom data of the stop."""
33
+
34
+
35
+ class PlannedStopOutput(BaseModel):
36
+ """Output of a stop planned in the solution."""
37
+
38
+ stop: StopOutput
39
+ """Basic information on the stop."""
40
+
41
+ arrival_time: Optional[datetime] = None
42
+ """Actual arrival time at this stop."""
43
+ cumulative_travel_distance: Optional[float] = None
44
+ """Cumulative distance to travel from the first stop to this one, in meters."""
45
+ cumulative_travel_duration: Optional[float] = None
46
+ """Cumulative duration to travel from the first stop to this one, in seconds."""
47
+ custom_data: Optional[Any] = None
48
+ """Custom data of the stop."""
49
+ duration: Optional[float] = None
50
+ """Duration of the service at the stop, in seconds."""
51
+ early_arrival_duration: Optional[float] = None
52
+ """Duration of early arrival at the stop, in seconds."""
53
+ end_time: Optional[datetime] = None
54
+ """End time of the service at the stop."""
55
+ late_arrival_duration: Optional[float] = None
56
+ """Duration of late arrival at the stop, in seconds."""
57
+ mix_items: Optional[Any] = None
58
+ """Mix items at the stop."""
59
+ start_time: Optional[datetime] = None
60
+ """Start time of the service at the stop."""
61
+ target_arrival_time: Optional[datetime] = None
62
+ """Target arrival time at this stop."""
63
+ travel_distance: Optional[float] = None
64
+ """Distance to travel from the previous stop to this one, in meters."""
65
+ travel_duration: Optional[float] = None
66
+ """Duration to travel from the previous stop to this one, in seconds."""
67
+ waiting_duration: Optional[float] = None
68
+ """Waiting duratino at the stop, in seconds."""
69
+
70
+
71
+ class VehicleOutput(BaseModel):
72
+ """Output of a vehicle in the solution."""
73
+
74
+ id: str
75
+ """ID of the vehicle."""
76
+
77
+ alternate_stops: Optional[List[str]] = None
78
+ """List of alternate stops that were planned on the vehicle."""
79
+ custom_data: Optional[Any] = None
80
+ """Custom data of the vehicle."""
81
+ route: Optional[List[PlannedStopOutput]] = None
82
+ """Route of the vehicle, which is a list of stops that were planned on
83
+ it."""
84
+ route_duration: Optional[float] = None
85
+ """Total duration of the vehicle's route, in seconds."""
86
+ route_stops_duration: Optional[float] = None
87
+ """Total duration of the stops of the vehicle, in seconds."""
88
+ route_travel_distance: Optional[float] = None
89
+ """Total travel distance of the vehicle, in meters."""
90
+ route_travel_duration: Optional[float] = None
91
+ """Total travel duration of the vehicle, in seconds."""
92
+ route_waiting_duration: Optional[float] = None
93
+ """Total waiting duration of the vehicle, in seconds."""
94
+
95
+
96
+ class ObjectiveOutput(BaseModel):
97
+ """Information of the objective (value function)."""
98
+
99
+ name: str
100
+ """Name of the objective."""
101
+
102
+ base: Optional[float] = None
103
+ """Base of the objective."""
104
+ custom_data: Optional[Any] = None
105
+ """Custom data of the objective."""
106
+ factor: Optional[float] = None
107
+ """Factor of the objective."""
108
+ objectives: Optional[List[Dict[str, Any]]] = None
109
+ """List of objectives. Each list is actually of the same class
110
+ `ObjectiveOutput`, but we avoid a recursive definition here."""
111
+ value: Optional[float] = None
112
+ """Value of the objective, which is equivalent to `self.base *
113
+ self.factor`."""
114
+
115
+
116
+ class Solution(BaseModel):
117
+ """Solution to a Vehicle Routing Problem (VRP)."""
118
+
119
+ unplanned: Optional[List[StopOutput]] = None
120
+ """List of stops that were not planned in the solution."""
121
+ vehicles: Optional[List[VehicleOutput]] = None
122
+ """List of vehicles in the solution."""
123
+ objective: Optional[ObjectiveOutput] = None
124
+ """Information of the objective (value function)."""
125
+ check: Optional[CheckOutput] = None
126
+ """Check of the solution, if enabled."""
127
+
128
+
129
+ class Output(BaseModel):
130
+ """Output schema for Nextroute."""
131
+
132
+ options: Dict[str, Any]
133
+ """Options used to obtain this output."""
134
+ version: Version
135
+ """Versions used for the solution."""
136
+
137
+ solutions: Optional[List[Solution]] = None
138
+ """Solutions to the problem."""
139
+ statistics: Optional[Statistics] = None
140
+ """Statistics of the solution."""
@@ -0,0 +1,149 @@
1
+ # © 2019-present nextmv.io inc
2
+
3
+ """
4
+ Schema for statistics.
5
+ """
6
+
7
+ from typing import Any, Dict, List, Optional, Union
8
+
9
+ from pydantic import Field
10
+
11
+ from nextroute.base_model import BaseModel
12
+
13
+
14
+ class RunStatistics(BaseModel):
15
+ """
16
+ Statistics about a general run.
17
+
18
+ Parameters
19
+ ----------
20
+ duration : float, optional
21
+ Duration of the run in seconds.
22
+ iterations : int, optional
23
+ Number of iterations.
24
+ custom : Union[Any, Dict[str, Any]], optional
25
+ Custom statistics created by the user. Can normally expect a `Dict[str,
26
+ Any]`.
27
+ """
28
+
29
+ duration: Optional[float] = None
30
+ """Duration of the run in seconds."""
31
+ iterations: Optional[int] = None
32
+ """Number of iterations."""
33
+ custom: Optional[
34
+ Union[
35
+ Any,
36
+ Dict[str, Any],
37
+ ]
38
+ ] = None
39
+ """Custom statistics created by the user. Can normally expect a `Dict[str,
40
+ Any]`."""
41
+
42
+
43
+ class ResultStatistics(BaseModel):
44
+ """
45
+ Statistics about a specific result.
46
+
47
+ Parameters
48
+ ----------
49
+ duration : float, optional
50
+ Duration of the run in seconds.
51
+ value : float, optional
52
+ Value of the result.
53
+ custom : Union[Any, Dict[str, Any]], optional
54
+ Custom statistics created by the user. Can normally expect a `Dict[str,
55
+ Any]`.
56
+ """
57
+
58
+ duration: Optional[float] = None
59
+ """Duration of the run in seconds."""
60
+ value: Optional[float] = None
61
+ """Value of the result."""
62
+ custom: Optional[
63
+ Union[
64
+ Any,
65
+ Dict[str, Any],
66
+ ]
67
+ ] = None
68
+ """Custom statistics created by the user. Can normally expect a `Dict[str,
69
+ Any]`."""
70
+
71
+
72
+ class DataPoint(BaseModel):
73
+ """
74
+ A data point.
75
+
76
+ Parameters
77
+ ----------
78
+ x : float
79
+ X coordinate of the data point.
80
+ y : float
81
+ Y coordinate of the data point.
82
+ """
83
+
84
+ x: float
85
+ """X coordinate of the data point."""
86
+ y: float
87
+ """Y coordinate of the data point."""
88
+
89
+
90
+ class Series(BaseModel):
91
+ """
92
+ A series of data points.
93
+
94
+ Parameters
95
+ ----------
96
+ name : str, optional
97
+ Name of the series.
98
+ data_points : List[DataPoint], optional
99
+ Data of the series.
100
+ """
101
+
102
+ name: Optional[str] = None
103
+ """Name of the series."""
104
+ data_points: Optional[List[DataPoint]] = None
105
+ """Data of the series."""
106
+
107
+
108
+ class SeriesData(BaseModel):
109
+ """
110
+ Data of a series.
111
+
112
+ Parameters
113
+ ----------
114
+ value : Series, optional
115
+ A series for the value of the solution.
116
+ custom : List[Series], optional
117
+ A list of series for custom statistics.
118
+ """
119
+
120
+ value: Optional[Series] = None
121
+ """A series for the value of the solution."""
122
+ custom: Optional[List[Series]] = None
123
+ """A list of series for custom statistics."""
124
+
125
+
126
+ class Statistics(BaseModel):
127
+ """
128
+ Statistics of a solution.
129
+
130
+ Parameters
131
+ ----------
132
+ run : RunStatistics, optional
133
+ Statistics about the run.
134
+ result : ResultStatistics, optional
135
+ Statistics about the last result.
136
+ series_data : SeriesData, optional
137
+ Series data about some metric.
138
+ statistics_schema : str, optional
139
+ Schema (version). This class only supports `v1`.
140
+ """
141
+
142
+ run: Optional[RunStatistics] = None
143
+ """Statistics about the run."""
144
+ result: Optional[ResultStatistics] = None
145
+ """Statistics about the last result."""
146
+ series_data: Optional[SeriesData] = None
147
+ """Data of the series."""
148
+ statistics_schema: Optional[str] = Field(alias="schema", default="v1")
149
+ """Schema (version). This class only supports `v1`."""
@@ -0,0 +1,65 @@
1
+ # © 2019-present nextmv.io inc
2
+
3
+ """
4
+ Defines the stop class.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import Any, List, Optional
9
+
10
+ from nextroute.base_model import BaseModel
11
+ from nextroute.schema.location import Location
12
+
13
+
14
+ class StopDefaults(BaseModel):
15
+ """Default values for a stop."""
16
+
17
+ compatibility_attributes: Optional[List[str]] = None
18
+ """Attributes that the stop is compatible with."""
19
+ duration: Optional[int] = None
20
+ """Duration of the stop in seconds."""
21
+ early_arrival_time_penalty: Optional[float] = None
22
+ """Penalty per second for arriving at the stop before the target arrival time."""
23
+ late_arrival_time_penalty: Optional[float] = None
24
+ """Penalty per second for arriving at the stop after the target arrival time."""
25
+ max_wait: Optional[int] = None
26
+ """Maximum waiting duration in seconds at the stop."""
27
+ quantity: Optional[Any] = None
28
+ """Quantity of the stop."""
29
+ start_time_window: Optional[Any] = None
30
+ """Time window in which the stop can start service."""
31
+ target_arrival_time: Optional[datetime] = None
32
+ """Target arrival time at the stop."""
33
+ unplanned_penalty: Optional[int] = None
34
+ """Penalty for not planning a stop."""
35
+
36
+
37
+ class Stop(StopDefaults):
38
+ """Stop is a location that must be visited by a vehicle in a Vehicle
39
+ Routing Problem (VRP.)"""
40
+
41
+ id: str
42
+ """Unique identifier for the stop."""
43
+ location: Location
44
+ """Location of the stop."""
45
+
46
+ custom_data: Optional[Any] = None
47
+ """Arbitrary data associated with the stop."""
48
+ mixing_items: Optional[Any] = None
49
+ """Defines the items that are inserted or removed from the vehicle when visiting the stop."""
50
+ precedes: Optional[Any] = None
51
+ """Stops that must be visited after this one on the same route."""
52
+ succeeds: Optional[Any] = None
53
+ """Stops that must be visited before this one on the same route."""
54
+
55
+
56
+ class AlternateStop(StopDefaults):
57
+ """An alternate stop can be serviced instead of another stop."""
58
+
59
+ id: str
60
+ """Unique identifier for the stop."""
61
+ location: Location
62
+ """Location of the stop."""
63
+
64
+ custom_data: Optional[Any] = None
65
+ """Arbitrary data associated with the stop."""
@@ -0,0 +1,72 @@
1
+ # © 2019-present nextmv.io inc
2
+
3
+ """
4
+ Defines the vehicle class.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import Any, List, Optional
9
+
10
+ from nextroute.base_model import BaseModel
11
+ from nextroute.schema.location import Location
12
+
13
+
14
+ class InitialStop(BaseModel):
15
+ """Represents a stop that is already planned on a vehicle."""
16
+
17
+ id: str
18
+ """Unique identifier of the stop."""
19
+
20
+ fixed: Optional[bool] = None
21
+ """Whether the stop is fixed on the route."""
22
+
23
+
24
+ class VehicleDefaults(BaseModel):
25
+ """Default values for vehicles."""
26
+
27
+ activation_penalty: Optional[int] = None
28
+ """Penalty of using the vehicle."""
29
+ alternate_stops: Optional[List[str]] = None
30
+ """A set of alternate stops for which only one should be serviced."""
31
+ capacity: Optional[Any] = None
32
+ """Capacity of the vehicle."""
33
+ compatibility_attributes: Optional[List[str]] = None
34
+ """Attributes that the vehicle is compatible with."""
35
+ end_location: Optional[Location] = None
36
+ """Location where the vehicle ends."""
37
+ end_time: Optional[datetime] = None
38
+ """Latest time at which the vehicle ends its route."""
39
+ max_distance: Optional[int] = None
40
+ """Maximum distance in meters that the vehicle can travel."""
41
+ max_duration: Optional[int] = None
42
+ """Maximum duration in seconds that the vehicle can travel."""
43
+ max_stops: Optional[int] = None
44
+ """Maximum number of stops that the vehicle can visit."""
45
+ max_wait: Optional[int] = None
46
+ """Maximum aggregated waiting time that the vehicle can wait across route stops."""
47
+ min_stops: Optional[int] = None
48
+ """Minimum stops that a vehicle should visit."""
49
+ min_stops_penalty: Optional[float] = None
50
+ """Penalty for not visiting the minimum number of stops."""
51
+ speed: Optional[float] = None
52
+ """Speed of the vehicle in meters per second."""
53
+ start_level: Optional[Any] = None
54
+ """Initial level of the vehicle."""
55
+ start_location: Optional[Location] = None
56
+ """Location where the vehicle starts."""
57
+ start_time: Optional[datetime] = None
58
+ """Time when the vehicle starts its route."""
59
+
60
+
61
+ class Vehicle(VehicleDefaults):
62
+ """A vehicle services stops in a Vehicle Routing Problem (VRP)."""
63
+
64
+ id: str
65
+ """Unique identifier of the vehicle."""
66
+
67
+ custom_data: Optional[Any] = None
68
+ """Arbitrary custom data."""
69
+ initial_stops: Optional[List[InitialStop]] = None
70
+ """Initial stops planned on the vehicle."""
71
+ stop_duration_multiplier: Optional[float] = None
72
+ """Multiplier for the duration of stops."""
nextroute/solve.py ADDED
@@ -0,0 +1,145 @@
1
+ # © 2019-present nextmv.io inc
2
+
3
+ """
4
+ Methods for solving a Vehicle Routing Problem with Nextroute.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ import platform
10
+ import subprocess
11
+ from typing import Any, Dict, Union
12
+
13
+ from nextroute.options import Options
14
+ from nextroute.schema.input import Input
15
+ from nextroute.schema.output import Output
16
+
17
+ SUPPORTED_OS = ["linux", "windows", "darwin"]
18
+ """The operating systems supported by the Nextroute engine."""
19
+ SUPPORTED_ARCHITECTURES = ["amd64", "x86_64", "arm64", "aarch64"]
20
+ """The architectures supported by the Nextroute engine."""
21
+
22
+
23
+ def solve(
24
+ input: Union[Input, Dict[str, Any]],
25
+ options: Union[Options, Dict[str, Any]],
26
+ ) -> Output:
27
+ """
28
+ Solve a Vehicle Routing Problem (VRP) using the Nextroute engine. The input
29
+ and options are passed to the engine, and the output is returned. The input
30
+ and options can be provided as dictionaries or as objects, although the
31
+ recommended way is to use the classes, as they provide validation.
32
+
33
+ Examples
34
+ --------
35
+
36
+ * Using default options to load an input from a file.
37
+ ```python
38
+ import json
39
+
40
+ import nextroute
41
+
42
+ with open("input.json") as f:
43
+ data = json.load(f)
44
+
45
+ input = nextroute.schema.Input.from_dict(data)
46
+ options = nextroute.Options()
47
+ output = nextroute.solve(input, options)
48
+ print(output)
49
+ ```
50
+
51
+ * Using custom options to load an input from a file.
52
+ ```python
53
+ import json
54
+
55
+ import nextroute
56
+
57
+ with open("input.json") as f:
58
+ data = json.load(f)
59
+
60
+ input = nextroute.schema.Input.from_dict(data)
61
+ options = nextroute.Options(
62
+ solve=nextroute.ParallelSolveOptions(duration=2),
63
+ )
64
+ output = nextroute.solve(input, options)
65
+ print(output)
66
+ ```
67
+
68
+ * Using custom dict options to load an input from a file.
69
+ ```python
70
+ import json
71
+
72
+ import nextroute
73
+
74
+ with open("input.json") as f:
75
+ data = json.load(f)
76
+
77
+ input = nextroute.schema.Input.from_dict(data)
78
+ options = {
79
+ "solve": {
80
+ "duration": 2,
81
+ },
82
+ }
83
+ output = nextroute.solve(input, options)
84
+ print(output)
85
+ ```
86
+
87
+
88
+ Parameters
89
+ ----------
90
+ input : Union[schema.Input, Dict[str, Any]]
91
+ The input to the Nextroute engine. If a dictionary is provided, it will
92
+ be converted to an Input object to validate it.
93
+ options : Union[Options, Dict[str, Any]]
94
+ The options for the Nextroute engine. If a dictionary is provided, it
95
+ will be converted to an Options object.
96
+
97
+ Returns
98
+ -------
99
+ schema.Output
100
+ The output of the Nextroute engine. You can call the `to_dict` method
101
+ on this object to get a dictionary representation of the output.
102
+ """
103
+
104
+ if isinstance(input, dict):
105
+ input = Input.from_dict(input)
106
+
107
+ input_stream = json.dumps(input.to_dict())
108
+
109
+ if isinstance(options, dict):
110
+ options = Options.from_dict(options)
111
+
112
+ os_name = platform.system().lower()
113
+ if os_name not in SUPPORTED_OS:
114
+ raise Exception(f'unsupported operating system: "{os_name}", supported os are: {", ".join(SUPPORTED_OS)}')
115
+
116
+ architecture = platform.machine().lower()
117
+ if architecture not in SUPPORTED_ARCHITECTURES:
118
+ raise Exception(
119
+ f'unsupported architecture: "{architecture}", supported arch are: {", ".join(SUPPORTED_ARCHITECTURES)}'
120
+ )
121
+
122
+ executable = os.path.join(os.path.dirname(__file__), "bin", "nextroute.exe")
123
+ if not os.path.exists(executable):
124
+ raise Exception(f"missing Nextroute binary: {executable}")
125
+
126
+ option_args = options.to_args()
127
+ args = [executable] + option_args
128
+
129
+ try:
130
+ result = subprocess.run(
131
+ args,
132
+ env=os.environ,
133
+ check=True,
134
+ text=True,
135
+ capture_output=True,
136
+ input=input_stream,
137
+ )
138
+
139
+ except subprocess.CalledProcessError as e:
140
+ raise Exception(f"error running Nextroute binary: {e.stderr}") from e
141
+
142
+ raw_output = result.stdout
143
+ output = Output.from_dict(json.loads(raw_output))
144
+
145
+ return output
nextroute/version.py ADDED
@@ -0,0 +1,12 @@
1
+ # © 2019-present nextmv.io inc
2
+
3
+ import os
4
+ import subprocess
5
+
6
+
7
+ def nextroute_version() -> str:
8
+ """
9
+ Get the version of the embedded Nextroute binary.
10
+ """
11
+ executable = os.path.join(os.path.dirname(__file__), "bin", "nextroute.exe")
12
+ return subprocess.check_output([executable, "--version"]).decode().strip()
@@ -0,0 +1,87 @@
1
+ # LICENSE
2
+
3
+ Business Source License 1.1
4
+
5
+ Parameters
6
+
7
+ Licensor: nextmv.io inc
8
+ Licensed Work: nextroute
9
+
10
+ Change Date: Four years from the date the Licensed Work is published.
11
+ Change License: GPLv3
12
+
13
+ For information about alternative licensing arrangements for the Software,
14
+ please email info@nextmv.io.
15
+
16
+ Notice
17
+
18
+ The Business Source License (this document, or the “License”) is not an Open
19
+ Source license. However, the Licensed Work will eventually be made available
20
+ under an Open Source License, as stated in this License.
21
+
22
+ License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
23
+ License” is a trademark of MariaDB plc.
24
+
25
+ -----------------------------------------------------------------------------
26
+
27
+ ## Terms
28
+
29
+ The Licensor hereby grants you the right to copy, modify, create derivative
30
+ works, redistribute, and make non-production use of the Licensed Work. The
31
+ Licensor may make an Additional Use Grant, above, permitting limited production
32
+ use.
33
+
34
+ Effective on the Change Date, or the fourth anniversary of the first publicly
35
+ available distribution of a specific version of the Licensed Work under this
36
+ License, whichever comes first, the Licensor hereby grants you rights under the
37
+ terms of the Change License, and the rights granted in the paragraph above
38
+ terminate.
39
+
40
+ If your use of the Licensed Work does not comply with the requirements currently
41
+ in effect as described in this License, you must purchase a commercial license
42
+ from the Licensor, its affiliated entities, or authorized resellers, or you must
43
+ refrain from using the Licensed Work.
44
+
45
+ All copies of the original and modified Licensed Work, and derivative works of
46
+ the Licensed Work, are subject to this License. This License applies separately
47
+ for each version of the Licensed Work and the Change Date may vary for each
48
+ version of the Licensed Work released by Licensor.
49
+
50
+ You must conspicuously display this License on each original or modified copy of
51
+ the Licensed Work. If you receive the Licensed Work in original or modified form
52
+ from a third party, the terms and conditions set forth in this License apply to
53
+ your use of that work.
54
+
55
+ Any use of the Licensed Work in violation of this License will automatically
56
+ terminate your rights under this License for the current and all other versions
57
+ of the Licensed Work.
58
+
59
+ This License does not grant you any right in any trademark or logo of Licensor
60
+ or its affiliates (provided that you may use a trademark or logo of Licensor as
61
+ expressly required by this License).TO THE EXTENT PERMITTED BY APPLICABLE LAW,
62
+ THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL
63
+ WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION)
64
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
65
+ NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this
66
+ License’s text to license your works, and to refer to it using the trademark
67
+ “Business Source License”, as long as you comply with the Covenants of Licensor
68
+ below.
69
+
70
+ ## Covenants of Licensor
71
+
72
+ In consideration of the right to use this License’s text and the “Business
73
+ Source License” name and trademark, Licensor covenants to MariaDB, and to all
74
+ other recipients of the licensed work to be provided by Licensor:
75
+
76
+ To specify as the Change License the GPL Version 2.0 or any later version, or a
77
+ license that is compatible with GPL Version 2.0 or a later version, where
78
+ “compatible” means that software provided under the Change License can be
79
+ included in a program with software provided under GPL Version 2.0 or a later
80
+ version. Licensor may specify additional Change Licenses without limitation. To
81
+ either: (a) specify an additional grant of rights to use that does not impose
82
+ any additional restriction on the right granted in this License, as the
83
+ Additional Use Grant; or (b) insert the text “None” to specify a Change Date.
84
+ Not to modify this License in any other way.
85
+
86
+ License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
87
+ License” is a trademark of MariaDB plc.