mdpsolver 0.9.9__tar.gz → 0.10.0__tar.gz

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.
Files changed (32) hide show
  1. mdpsolver-0.9.9/README.md → mdpsolver-0.10.0/PKG-INFO +20 -22
  2. mdpsolver-0.9.9/src/mdpsolver.egg-info/PKG-INFO → mdpsolver-0.10.0/README.md +2 -39
  3. mdpsolver-0.10.0/pyproject.toml +40 -0
  4. {mdpsolver-0.9.9 → mdpsolver-0.10.0}/src/mdpsolver/model.py +53 -31
  5. mdpsolver-0.10.0/tests/example_model.py +139 -0
  6. mdpsolver-0.10.0/tests/rewards_example2.csv +403 -0
  7. mdpsolver-0.10.0/tests/test1.py +717 -0
  8. mdpsolver-0.10.0/tests/test2.py +94 -0
  9. mdpsolver-0.9.9/tests/test2.py → mdpsolver-0.10.0/tests/test2_pip.py +10 -7
  10. mdpsolver-0.10.0/tests/transitions_example2.csv +41205 -0
  11. mdpsolver-0.9.9/PKG-INFO +0 -170
  12. mdpsolver-0.9.9/pyproject.toml +0 -31
  13. mdpsolver-0.9.9/setup.cfg +0 -4
  14. mdpsolver-0.9.9/src/mdpsolver/problems/__pycache__/cbmaux.cpython-310.pyc +0 -0
  15. mdpsolver-0.9.9/src/mdpsolver/problems/__pycache__/tbmaux.cpython-310.pyc +0 -0
  16. mdpsolver-0.9.9/src/mdpsolver/problems/cbmaux.py +0 -40
  17. mdpsolver-0.9.9/src/mdpsolver/problems/tbmaux.py +0 -40
  18. mdpsolver-0.9.9/src/mdpsolver/solvermodule.cp310-win_amd64.pyd +0 -0
  19. mdpsolver-0.9.9/src/mdpsolver/solvermodule.cp311-win_amd64.pyd +0 -0
  20. mdpsolver-0.9.9/src/mdpsolver/solvermodule.cp312-win_amd64.pyd +0 -0
  21. mdpsolver-0.9.9/src/mdpsolver/solvermodule.cp313-win_amd64.pyd +0 -0
  22. mdpsolver-0.9.9/src/mdpsolver/solvermodule.cpython-310-x86_64-linux-gnu.so +0 -0
  23. mdpsolver-0.9.9/src/mdpsolver/solvermodule.cpython-311-x86_64-linux-gnu.so +0 -0
  24. mdpsolver-0.9.9/src/mdpsolver/solvermodule.cpython-312-x86_64-linux-gnu.so +0 -0
  25. mdpsolver-0.9.9/src/mdpsolver/solvermodule.cpython-313-x86_64-linux-gnu.so +0 -0
  26. mdpsolver-0.9.9/src/mdpsolver.egg-info/SOURCES.txt +0 -24
  27. mdpsolver-0.9.9/src/mdpsolver.egg-info/dependency_links.txt +0 -1
  28. mdpsolver-0.9.9/src/mdpsolver.egg-info/top_level.txt +0 -1
  29. {mdpsolver-0.9.9 → mdpsolver-0.10.0}/LICENSE +0 -0
  30. {mdpsolver-0.9.9 → mdpsolver-0.10.0}/MANIFEST.in +0 -0
  31. {mdpsolver-0.9.9 → mdpsolver-0.10.0}/src/mdpsolver/__init__.py +0 -0
  32. mdpsolver-0.9.9/tests/test1.py → mdpsolver-0.10.0/tests/test1_pip.py +1 -1
@@ -1,12 +1,28 @@
1
- # MDPSolver
1
+ Metadata-Version: 2.2
2
+ Name: mdpsolver
3
+ Version: 0.10.0
4
+ Summary: A fast solver for Markov Decision Processes
5
+ Author-Email: Anders Reenberg Andersen <andersra@live.dk>, Jesper Fink Andersen <jesperfinkandersen@gmail.com>
6
+ Classifier: Development Status :: 4 - Beta
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: Microsoft :: Windows
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Operating System :: MacOS
12
+ Project-URL: Homepage, https://github.com/areenberg/MDPSolver
13
+ Project-URL: Issues, https://github.com/areenberg/MDPSolver/issues
14
+ Requires-Python: >=3.10
15
+ Provides-Extra: test
16
+ Requires-Dist: numpy; extra == "test"
17
+ Description-Content-Type: text/markdown
2
18
 
3
- <img src="https://github.com/areenberg/MDPSolver/blob/anders_development/Images/MDPSolver_logo_3.png" alt="Logo" width="180">
19
+ # MDPSolver
4
20
 
5
21
  MDPSolver is a Python package for large Markov Decision Processes (MDPs) with infinite-horizons.
6
22
 
7
23
  ## Features
8
24
 
9
- * Fast solver: Our C++-based solver is substantially faster than other MDP packages available for Python. See details in the documentation.
25
+ * Fast solver: Our C++-based solver is substantially faster than other CPU-based MDP packages available for Python. See details in the documentation.
10
26
  * Two optimality criteria: *Discounted* and *Average* reward.
11
27
  * Three optimization algorithms: *Value iteration*, *Policy iteration*, and *Modified policy iteration*.
12
28
  * Three value-update methods: *Standard*, *Gauss–Seidel*, and *Successive over-relaxation*.
@@ -15,25 +31,7 @@ MDPSolver is a Python package for large Markov Decision Processes (MDPs) with in
15
31
 
16
32
  # Installation
17
33
 
18
- ## Linux
19
-
20
- Install directly from PyPI with:
21
-
22
- ```
23
- pip install mdpsolver
24
- ```
25
-
26
- MDPSolver works *out of the box* on Ubuntu 22 and newer.
27
-
28
- ### GLIBC not found
29
-
30
- Some users will encounter the `version 'GLIBC_2.32' not found` error when attempting to import MDPSolver in Python. In this case, it might help to manually compile and replace the SO-file for the optimization module in the MDPSolver package. See the steps on how to solve the issue in the [documentation](https://github.com/areenberg/MDPSolver/wiki#how-to-install).
31
-
32
- ## Windows
33
-
34
- Requires [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) (17.9) with MSVC C++ compiler and libraries installed.
35
-
36
- After installing Visual Studio (incl. MSVC C++ compiler and libraries), install directly from PyPI with:
34
+ Prebuilt wheels are available for **Linux**, **Windows**, and **macOS (14+)** across Python 3.10–3.14. Install directly from PyPI with:
37
35
 
38
36
  ```
39
37
  pip install mdpsolver
@@ -1,29 +1,10 @@
1
- Metadata-Version: 2.4
2
- Name: mdpsolver
3
- Version: 0.9.9
4
- Summary: A fast solver for Markov Decision Processes
5
- Author-email: Anders Reenberg Andersen <andersra@live.dk>, Jesper Fink Andersen <jesperfinkandersen@gmail.com>
6
- Project-URL: Homepage, https://github.com/areenberg/MDPSolver
7
- Project-URL: Issues, https://github.com/areenberg/MDPSolver/issues
8
- Classifier: Development Status :: 3 - Alpha
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: Microsoft :: Windows
12
- Classifier: Operating System :: POSIX :: Linux
13
- Requires-Python: >=3.10
14
- Description-Content-Type: text/markdown
15
- License-File: LICENSE
16
- Dynamic: license-file
17
-
18
1
  # MDPSolver
19
2
 
20
- <img src="https://github.com/areenberg/MDPSolver/blob/anders_development/Images/MDPSolver_logo_3.png" alt="Logo" width="180">
21
-
22
3
  MDPSolver is a Python package for large Markov Decision Processes (MDPs) with infinite-horizons.
23
4
 
24
5
  ## Features
25
6
 
26
- * Fast solver: Our C++-based solver is substantially faster than other MDP packages available for Python. See details in the documentation.
7
+ * Fast solver: Our C++-based solver is substantially faster than other CPU-based MDP packages available for Python. See details in the documentation.
27
8
  * Two optimality criteria: *Discounted* and *Average* reward.
28
9
  * Three optimization algorithms: *Value iteration*, *Policy iteration*, and *Modified policy iteration*.
29
10
  * Three value-update methods: *Standard*, *Gauss–Seidel*, and *Successive over-relaxation*.
@@ -32,25 +13,7 @@ MDPSolver is a Python package for large Markov Decision Processes (MDPs) with in
32
13
 
33
14
  # Installation
34
15
 
35
- ## Linux
36
-
37
- Install directly from PyPI with:
38
-
39
- ```
40
- pip install mdpsolver
41
- ```
42
-
43
- MDPSolver works *out of the box* on Ubuntu 22 and newer.
44
-
45
- ### GLIBC not found
46
-
47
- Some users will encounter the `version 'GLIBC_2.32' not found` error when attempting to import MDPSolver in Python. In this case, it might help to manually compile and replace the SO-file for the optimization module in the MDPSolver package. See the steps on how to solve the issue in the [documentation](https://github.com/areenberg/MDPSolver/wiki#how-to-install).
48
-
49
- ## Windows
50
-
51
- Requires [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) (17.9) with MSVC C++ compiler and libraries installed.
52
-
53
- After installing Visual Studio (incl. MSVC C++ compiler and libraries), install directly from PyPI with:
16
+ Prebuilt wheels are available for **Linux**, **Windows**, and **macOS (14+)** across Python 3.10–3.14. Install directly from PyPI with:
54
17
 
55
18
  ```
56
19
  pip install mdpsolver
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["scikit-build-core>=0.10", "pybind11>=2.13"]
3
+ build-backend = "scikit_build_core.build"
4
+
5
+ [tool.scikit-build]
6
+ # The CMake project lives outside Python/, next to the rest of the C++ sources.
7
+ cmake.source-dir = "../CPP_Source_Code"
8
+ build-dir = "build/{wheel_tag}"
9
+ # CMakeLists.txt installs the extension to DESTINATION mdpsolver itself,
10
+ # so wheel.install-dir stays at the default (wheel root) here.
11
+ sdist.include = ["src/mdpsolver/*.py"]
12
+
13
+ [tool.scikit-build.cmake.define]
14
+ CMAKE_BUILD_TYPE = "Release"
15
+
16
+ [project]
17
+ name = "mdpsolver"
18
+ version = "0.10.0"
19
+ authors = [
20
+ { name="Anders Reenberg Andersen", email="andersra@live.dk" },
21
+ { name="Jesper Fink Andersen", email="jesperfinkandersen@gmail.com" },
22
+ ]
23
+ description = "A fast solver for Markov Decision Processes"
24
+ readme = "README.md"
25
+ requires-python = ">=3.10"
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Programming Language :: Python :: 3",
29
+ "License :: OSI Approved :: MIT License",
30
+ "Operating System :: Microsoft :: Windows",
31
+ "Operating System :: POSIX :: Linux",
32
+ "Operating System :: MacOS"
33
+ ]
34
+
35
+ [project.optional-dependencies]
36
+ test = ["numpy"]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/areenberg/MDPSolver"
40
+ Issues = "https://github.com/areenberg/MDPSolver/issues"
@@ -6,9 +6,9 @@ and solve Markov Decision Processes (MDPs). It provides methods to configure the
6
6
  solve it, and extract the resulting policy and value vectors.
7
7
  """
8
8
 
9
+ import sys
9
10
  from mdpsolver import solvermodule
10
11
 
11
-
12
12
  class model:
13
13
  """
14
14
  Creates an MDPSolver object.
@@ -27,19 +27,19 @@ class model:
27
27
 
28
28
  def solve(
29
29
  self,
30
- algorithm="mpi",
31
- tolerance=1e-3,
32
- update="standard",
33
- criterion="discounted",
34
- parIterLim=100,
35
- SORrelaxation=1.0,
36
- initPolicy=list(),
37
- initValueVector=list(),
38
- verbose=False,
39
- postProcessing=True,
40
- makeFinalCheck=True,
41
- parallel=True,
42
- ):
30
+ algorithm: str = "mpi",
31
+ tolerance: float = 1e-3,
32
+ update: str = "standard",
33
+ criterion: str = "discounted",
34
+ parIterLim: int = 100,
35
+ SORrelaxation: float = 1.0,
36
+ initPolicy: list = list(),
37
+ initValueVector: list = list(),
38
+ verbose: bool = False,
39
+ postProcessing: bool = True,
40
+ makeFinalCheck: bool = True,
41
+ parallel: bool = True,
42
+ ) -> None:
43
43
  """
44
44
  Derive an epsilon-optimal policy for the selected MDP model.
45
45
 
@@ -60,6 +60,20 @@ class model:
60
60
  Returns:
61
61
  None
62
62
  """
63
+
64
+ if not (algorithm == "mpi" or algorithm == "pi" or algorithm == "vi"):
65
+ sys.exit("Error: Algorithm type not recognized. Select either: 'mpi', 'pi', or 'vi'.")
66
+ if tolerance <= 0.0:
67
+ sys.exit("Error: The tolerance needs to be a positive number.")
68
+ if not (update == "standard" or update == "gs" or update == "sor"):
69
+ sys.exit("Error: Update method not recognized. Select either: 'standard', 'gs', or 'sor'.")
70
+ if not (criterion == "discounted" or criterion == "average"):
71
+ sys.exit("Error: Optimality criterion not recognized. Select either: 'discounted' or 'average'.")
72
+ if parIterLim <= 0 and algorithm == "mpi":
73
+ sys.exit("Error: The partial evaluation limit needs to be a positive number.")
74
+ if (SORrelaxation <= 0.0 or SORrelaxation >= 2.0) and update == "sor":
75
+ sys.exit("Error: The relaxation parameter needs to be between 0.0 and 2.0 (i.e. 0 < SORrelaxation < 2).")
76
+
63
77
  self.mdl.solve(
64
78
  algorithm=algorithm,
65
79
  tolerance=tolerance,
@@ -75,7 +89,7 @@ class model:
75
89
  parallel=parallel,
76
90
  )
77
91
 
78
- def getRuntime(self):
92
+ def getRuntime(self) -> float:
79
93
  """
80
94
  Get the runtime of the last solver execution.
81
95
 
@@ -84,15 +98,15 @@ class model:
84
98
  """
85
99
  return self.mdl.getRuntime()
86
100
 
87
- def printPolicy(self):
101
+ def printPolicy(self) -> None:
88
102
  """Print the entire policy to the terminal."""
89
103
  self.mdl.printPolicy()
90
104
 
91
- def printValueVector(self):
105
+ def printValueVector(self) -> None:
92
106
  """Print the entire value vector to the terminal."""
93
107
  self.mdl.printValueVector()
94
108
 
95
- def getAction(self, stateIndex=0):
109
+ def getAction(self, stateIndex: int = 0) -> int:
96
110
  """
97
111
  Get the action from the optimized policy for a specific state.
98
112
 
@@ -104,7 +118,7 @@ class model:
104
118
  """
105
119
  return self.mdl.getAction(stateIndex=stateIndex)
106
120
 
107
- def getValue(self, stateIndex=0):
121
+ def getValue(self, stateIndex: int = 0) -> float:
108
122
  """
109
123
  Get the value from the optimized value vector for a specific state.
110
124
 
@@ -116,7 +130,7 @@ class model:
116
130
  """
117
131
  return self.mdl.getValue(stateIndex=stateIndex)
118
132
 
119
- def getPolicy(self):
133
+ def getPolicy(self) -> list:
120
134
  """
121
135
  Get the entire optimized policy.
122
136
 
@@ -134,7 +148,7 @@ class model:
134
148
  """
135
149
  return self.mdl.getValueVector()
136
150
 
137
- def saveToFile(self, fileName="result.csv", type="policy"):
151
+ def saveToFile(self, fileName: str = "result.csv", type: str = "policy") -> None:
138
152
  """
139
153
  Save the optimized policy or value vector to a file.
140
154
 
@@ -145,20 +159,24 @@ class model:
145
159
  Returns:
146
160
  None
147
161
  """
162
+
163
+ if not (type == "policy" or type == "value"):
164
+ sys.exit("Error: Type not recognized. Select either: 'policy' or 'value'.")
165
+
148
166
  return self.mdl.saveToFile(fileName=fileName, type=type)
149
167
 
150
168
  def mdp(
151
169
  self,
152
- discount=0.99,
153
- rewards=list(),
154
- rewardsElementwise=list(),
155
- rewardsFromFile="rewards.csv",
156
- tranMatWithZeros=list(),
157
- tranMatElementwise=list(),
158
- tranMatProbs=list(),
159
- tranMatColumns=list(),
160
- tranMatFromFile="transitions.csv",
161
- ):
170
+ discount: float = 0.99,
171
+ rewards: list = list(),
172
+ rewardsElementwise: list = list(),
173
+ rewardsFromFile: str = "rewards.csv",
174
+ tranMatWithZeros: list = list(),
175
+ tranMatElementwise: list = list(),
176
+ tranMatProbs: list = list(),
177
+ tranMatColumns: list = list(),
178
+ tranMatFromFile: str = "transitions.csv",
179
+ ) -> None:
162
180
  """
163
181
  Define the generic MDP model.
164
182
 
@@ -176,6 +194,10 @@ class model:
176
194
  Returns:
177
195
  None
178
196
  """
197
+
198
+ if discount <= 0.0 or discount >= 1.0:
199
+ sys.exit("Error: The discount needs to be in the interval between 0.0 and 1.0 (i.e. 0 < discount < 1).")
200
+
179
201
  self.mdl.mdp(
180
202
  discount=discount,
181
203
  rewards=rewards,
@@ -0,0 +1,139 @@
1
+ import numpy as np
2
+ import os
3
+
4
+ # Create transition probability matrix and rewards for the 'mdpsolver' examples.
5
+
6
+
7
+ # -----------------------------
8
+ # FUNCTIONS
9
+ # -----------------------------
10
+
11
+
12
+ def exitDist(startK, arrRate, serRate, tol=1e-9):
13
+ # calculate distribution over states observed by
14
+ # the next arrival assuming the system currently
15
+ # contains 'startK' customers.
16
+
17
+ g = [i * serRate / (i * serRate + arrRate) for i in range(startK, -1, -1)]
18
+ d = [arrRate / (i * serRate + arrRate) for i in range(startK, -1, -1)]
19
+
20
+ Dmat = np.diag(d)
21
+ Zmat = np.zeros((len(g), len(d)))
22
+ Imat = np.eye(len(g))
23
+ Gmat = np.zeros((len(g), len(g)))
24
+ for i in range(startK):
25
+ Gmat[i, i + 1] = g[i]
26
+ mat = np.block([[Gmat, Dmat], [Zmat, Imat]])
27
+ p = np.zeros((1, (startK + 1) * 2))
28
+ p[0, 0] = 1
29
+
30
+ while np.sum(p[0, : startK + 1]) >= tol:
31
+ p = np.matmul(p, mat)
32
+
33
+ return p[0, startK + 1 :]
34
+
35
+
36
+ def tranMat(arrRate1, arrRate2, serRate, capacity, fileName):
37
+ # calculate transition probabilities and
38
+ # and save them in a file
39
+
40
+ ns = capacity + 1
41
+ arrRate = arrRate1 + arrRate2
42
+ pc1 = arrRate1 / arrRate
43
+ pc2 = 1 - pc1
44
+
45
+ if os.path.exists(fileName):
46
+ os.remove(fileName)
47
+ with open(fileName, "a") as file:
48
+ file.write(f"stateFrom,action,stateTo,probability\n")
49
+ for sidx in range(ns): # stateFrom (customer type 1)
50
+ for aidx in range(2):
51
+ if aidx == 0: # reject
52
+ if sidx > 0:
53
+ dist = exitDist(sidx, arrRate, serRate)
54
+ else:
55
+ dist = np.array([1])
56
+ dist1 = pc1 * dist
57
+ dist2 = pc2 * dist
58
+ for jidx in range(sidx + 1):
59
+ out = [sidx, aidx, jidx, dist1[sidx - jidx]]
60
+ str_out = ",".join(str(num) for num in out)
61
+ file.write(str_out + "\n")
62
+ for jidx in range(ns, ns + sidx + 1):
63
+ out = [sidx, aidx, jidx, dist2[sidx - (jidx - ns)]]
64
+ str_out = ",".join(str(num) for num in out)
65
+ file.write(str_out + "\n")
66
+ elif aidx == 1 and sidx < capacity: # accept
67
+ startK = sidx + 1
68
+ dist = exitDist(startK, arrRate, serRate)
69
+ dist1 = pc1 * dist
70
+ dist2 = pc2 * dist
71
+ for jidx in range(startK + 1):
72
+ out = [sidx, aidx, jidx, dist1[startK - jidx]]
73
+ str_out = ",".join(str(num) for num in out)
74
+ file.write(str_out + "\n")
75
+ for jidx in range(ns, ns + startK + 1):
76
+ out = [sidx, aidx, jidx, dist2[startK - (jidx - ns)]]
77
+ str_out = ",".join(str(num) for num in out)
78
+ file.write(str_out + "\n")
79
+
80
+ for sidx in range(ns): # stateFrom (customer type 2)
81
+ for aidx in range(2):
82
+ if aidx == 0: # reject
83
+ if sidx > 0:
84
+ dist = exitDist(sidx, arrRate, serRate)
85
+ else:
86
+ dist = np.array([1])
87
+ dist1 = pc1 * dist
88
+ dist2 = pc2 * dist
89
+ for jidx in range(sidx + 1):
90
+ out = [sidx + ns, aidx, jidx, dist1[sidx - jidx]]
91
+ str_out = ",".join(str(num) for num in out)
92
+ file.write(str_out + "\n")
93
+ for jidx in range(ns, ns + sidx + 1):
94
+ out = [sidx + ns, aidx, jidx, dist2[sidx - (jidx - ns)]]
95
+ str_out = ",".join(str(num) for num in out)
96
+ file.write(str_out + "\n")
97
+ elif aidx == 1 and sidx < capacity: # accept
98
+ startK = sidx + 1
99
+ dist = exitDist(startK, arrRate, serRate)
100
+ dist1 = pc1 * dist
101
+ dist2 = pc2 * dist
102
+ for jidx in range(startK + 1):
103
+ out = [sidx + ns, aidx, jidx, dist1[startK - jidx]]
104
+ str_out = ",".join(str(num) for num in out)
105
+ file.write(str_out + "\n")
106
+ for jidx in range(ns, ns + startK + 1):
107
+ out = [sidx + ns, aidx, jidx, dist2[startK - (jidx - ns)]]
108
+ str_out = ",".join(str(num) for num in out)
109
+ file.write(str_out + "\n")
110
+
111
+
112
+ def rewards(capacity, rewardCust1, rewardCust2, fileName):
113
+
114
+ ns = capacity + 1
115
+
116
+ if os.path.exists(fileName):
117
+ os.remove(fileName)
118
+ with open(fileName, "a") as file:
119
+ file.write(f"stateFrom,action,reward\n")
120
+ for sidx in range(ns): # stateFrom (customer type 1)
121
+ for aidx in range(2):
122
+ if aidx == 0:
123
+ out = [sidx, aidx, 0]
124
+ str_out = ",".join(str(num) for num in out)
125
+ file.write(str_out + "\n")
126
+ elif aidx == 1 and sidx < capacity:
127
+ out = [sidx, aidx, rewardCust1]
128
+ str_out = ",".join(str(num) for num in out)
129
+ file.write(str_out + "\n")
130
+ for sidx in range(ns): # stateFrom (customer type 2)
131
+ for aidx in range(2):
132
+ if aidx == 0:
133
+ out = [sidx + ns, aidx, 0]
134
+ str_out = ",".join(str(num) for num in out)
135
+ file.write(str_out + "\n")
136
+ elif aidx == 1 and sidx < capacity:
137
+ out = [sidx + ns, aidx, rewardCust2]
138
+ str_out = ",".join(str(num) for num in out)
139
+ file.write(str_out + "\n")