gymcts 1.0.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.
gymcts-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Alexander Nasuta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ # MANIFEST.in
gymcts-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,634 @@
1
+ Metadata-Version: 2.2
2
+ Name: gymcts
3
+ Version: 1.0.0
4
+ Summary: A minimalistic implementation of the Monte Carlo Tree Search algorithm for planning problems fomulated as gymnaisum reinforcement learning environments.
5
+ Author: Alexander Nasuta
6
+ Author-email: Alexander Nasuta <alexander.nasuta@wzl-iqs.rwth-aachen.de>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2025 Alexander Nasuta
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ Project-URL: Homepage, https://github.com/Alexander-Nasuta/pypitemplate
29
+ Platform: unix
30
+ Platform: linux
31
+ Platform: osx
32
+ Platform: cygwin
33
+ Platform: win32
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python
36
+ Classifier: Programming Language :: Python :: 3
37
+ Requires-Python: >=3.9
38
+ Description-Content-Type: text/markdown
39
+ License-File: LICENSE
40
+ Requires-Dist: rich
41
+ Requires-Dist: numpy
42
+ Requires-Dist: gymnasium
43
+ Requires-Dist: matplotlib<3.9
44
+ Provides-Extra: examples
45
+ Requires-Dist: jsp-instance-utils; extra == "examples"
46
+ Requires-Dist: graph-matrix-jsp-env; extra == "examples"
47
+ Requires-Dist: graph-jsp-env; extra == "examples"
48
+ Provides-Extra: dev
49
+ Requires-Dist: jsp-instance-utils; extra == "dev"
50
+ Requires-Dist: graph-matrix-jsp-env; extra == "dev"
51
+ Requires-Dist: graph-jsp-env; extra == "dev"
52
+ Requires-Dist: JSSEnv; extra == "dev"
53
+ Requires-Dist: pip-tools; extra == "dev"
54
+ Requires-Dist: pytest; extra == "dev"
55
+ Requires-Dist: pytest-cov; extra == "dev"
56
+ Requires-Dist: mypy; extra == "dev"
57
+ Requires-Dist: flake8; extra == "dev"
58
+ Requires-Dist: stable_baselines3; extra == "dev"
59
+ Requires-Dist: sphinx; extra == "dev"
60
+ Requires-Dist: myst-parser; extra == "dev"
61
+ Requires-Dist: sphinx-autobuild; extra == "dev"
62
+ Requires-Dist: furo; extra == "dev"
63
+ Requires-Dist: twine; extra == "dev"
64
+ Requires-Dist: sphinx-copybutton; extra == "dev"
65
+ Requires-Dist: nbsphinx; extra == "dev"
66
+
67
+ # Graph Matrix Job Shop Env
68
+
69
+ A Monte Carlo Tree Search Implementation for Gymnasium-style Environments.
70
+
71
+ - Github: [GYMCTS on Github](https://github.com/Alexander-Nasuta/GraphMatrixJobShopEnv)
72
+ - Pypi: [GYMCTS on PyPi](https://pypi.org/project/graph-matrix-jsp-env/)
73
+ - Documentation: [GYMCTS Docs](https://graphmatrixjobshopenv.readthedocs.io/en/latest/)
74
+
75
+ ## Description
76
+
77
+ This project provides a Monte Carlo Tree Search (MCTS) implementation for Gymnasium-style environments as an installable Python package.
78
+ The package is designed to be used with the Gymnasium interface.
79
+ It is especially useful for combinatorial optimization problems or planning problems, such as the Job Shop Scheduling Problem (JSP).
80
+ The documentation provides numerous examples on how to use the package with different environments, while focusing on scheduling problems.
81
+
82
+ A minimal working example is provided in the [Quickstart](#quickstart) section.
83
+
84
+ It comes with a variety of visualisation options, which is useful for research and debugging purposes.
85
+ It aims to be a base for further research and development for neural guided search algorithms.
86
+ ## Quickstart
87
+ To use the package, install it via pip:
88
+
89
+ ```shell
90
+ pip install gymcts
91
+ ```
92
+ The usage of a MCTS agent can roughly organised into the following steps:
93
+
94
+ - Create a Gymnasium-style environment
95
+ - Wrap the environment with a GymCTS wrapper
96
+ - Create a MCTS agent
97
+ - Solve the environment with the MCTS agent
98
+ - Render the solution
99
+
100
+ The GYMCTS package provides a two types of wrappers for Gymnasium-style environments:
101
+ - `NaiveSoloMCTSGymEnvWrapper`: A wrapper that uses deepcopies of the environment to save a snapshot of the environment state for each node in the MCTS tree.
102
+ - `DeterministicSoloMCTSGymEnvWrapper`: A wrapper that saves the action sequence that lead to the current state in the MCTS node.
103
+
104
+ These wrappers can be used with the `SoloMCTSAgent` to solve the environment.
105
+ The wrapper implement methods that are required by the `SoloMCTSAgent` to interact with the environment.
106
+ GYMCTS is designed to use a single environment instance and reconstructing the environment state form a state snapshot, when needed.
107
+
108
+ NOTE: MCTS works best when the return of an episode is in the range of [-1, 1]. Please adjust the reward function of the environment accordingly (or change the ubc-scaling parameter of the MCTS agent).
109
+ Adjusting the reward function of the environment is easily done with a [NormalizeReward](https://gymnasium.farama.org/api/wrappers/reward_wrappers/#gymnasium.wrappers.NormalizeReward) or [TransformReward](https://gymnasium.farama.org/api/wrappers/reward_wrappers/#gymnasium.wrappers.TransformReward) Wrapper.
110
+
111
+ NormalizeReward(env, gamma=0.99, epsilon=1e-8)
112
+ env = TransformReward(env, lambda r: r / 36)
113
+ ### FrozenLake Example (NaiveSoloMCTSGymEnvWrapper)
114
+
115
+ A minimal example of how to use the package with the FrozenLake environment and the NaiveSoloMCTSGymEnvWrapper is provided in the following code snippet below.
116
+ The NaiveSoloMCTSGymEnvWrapper can be used with non-deterministic environments, such as the FrozenLake environment with slippery ice.
117
+
118
+ ```python
119
+ import gymnasium as gym
120
+
121
+ from gymcts.gymcts_agent import SoloMCTSAgent
122
+ from gymcts.gymcts_naive_wrapper import NaiveSoloMCTSGymEnvWrapper
123
+
124
+ from gymcts.logger import log
125
+
126
+ # set log level to 20 (INFO)
127
+ # set log level to 10 (DEBUG) to see more detailed information
128
+ log.setLevel(20)
129
+
130
+ if __name__ == '__main__':
131
+ # 0. create the environment
132
+ env = gym.make('FrozenLake-v1', desc=None, map_name="4x4", is_slippery=True, render_mode="ansi")
133
+ env.reset()
134
+
135
+ # 1. wrap the environment with the naive wrapper or a custom gymcts wrapper
136
+ env = NaiveSoloMCTSGymEnvWrapper(env)
137
+
138
+ # 2. create the agent
139
+ agent = SoloMCTSAgent(
140
+ env=env,
141
+ clear_mcts_tree_after_step=False,
142
+ render_tree_after_step=True,
143
+ number_of_simulations_per_step=50,
144
+ exclude_unvisited_nodes_from_render=True
145
+ )
146
+
147
+ # 3. solve the environment
148
+ actions = agent.solve()
149
+
150
+ # 4. render the environment solution in the terminal
151
+ print(env.render())
152
+ for a in actions:
153
+ obs, rew, term, trun, info = env.step(a)
154
+ print(env.render())
155
+
156
+ # 5. print the solution
157
+ # read the solution from the info provided by the RecordEpisodeStatistics wrapper
158
+ # (that NaiveSoloMCTSGymEnvWrapper uses internally)
159
+ episode_length = info["episode"]["l"]
160
+ episode_return = info["episode"]["r"]
161
+
162
+ if episode_return == 1.0:
163
+ print(f"Environment solved in {episode_length} steps.")
164
+ else:
165
+ print(f"Environment not solved in {episode_length} steps.")
166
+ ```
167
+
168
+ ### FrozenLake Example (DeterministicSoloMCTSGymEnvWrapper)
169
+
170
+ A minimal example of how to use the package with the FrozenLake environment and the DeterministicSoloMCTSGymEnvWrapper is provided in the following code snippet below.
171
+ The DeterministicSoloMCTSGymEnvWrapper can be used with deterministic environments, such as the FrozenLake environment without slippery ice.
172
+
173
+ The DeterministicSoloMCTSGymEnvWrapper saves the action sequence that lead to the current state in the MCTS node.
174
+
175
+ ```python
176
+ import gymnasium as gym
177
+
178
+ from gymcts.gymcts_agent import SoloMCTSAgent
179
+ from gymcts.gymcts_deterministic_wrapper import DeterministicSoloMCTSGymEnvWrapper
180
+
181
+ from gymcts.logger import log
182
+
183
+ # set log level to 20 (INFO)
184
+ # set log level to 10 (DEBUG) to see more detailed information
185
+ log.setLevel(20)
186
+
187
+ if __name__ == '__main__':
188
+ # 0. create the environment
189
+ env = gym.make('FrozenLake-v1', desc=None, map_name="4x4", is_slippery=False, render_mode="ansi")
190
+ env.reset()
191
+
192
+ # 1. wrap the environment with the wrapper
193
+ env = DeterministicSoloMCTSGymEnvWrapper(env)
194
+
195
+ # 2. create the agent
196
+ agent = SoloMCTSAgent(
197
+ env=env,
198
+ clear_mcts_tree_after_step=False,
199
+ render_tree_after_step=True,
200
+ number_of_simulations_per_step=50,
201
+ exclude_unvisited_nodes_from_render=True
202
+ )
203
+
204
+ # 3. solve the environment
205
+ actions = agent.solve()
206
+
207
+ # 4. render the environment solution in the terminal
208
+ print(env.render())
209
+ for a in actions:
210
+ obs, rew, term, trun, info = env.step(a)
211
+ print(env.render())
212
+
213
+ # 5. print the solution
214
+ # read the solution from the info provided by the RecordEpisodeStatistics wrapper
215
+ # (that DeterministicSoloMCTSGymEnvWrapper uses internally)
216
+ episode_length = info["episode"]["l"]
217
+ episode_return = info["episode"]["r"]
218
+
219
+ if episode_return == 1.0:
220
+ print(f"Environment solved in {episode_length} steps.")
221
+ else:
222
+ print(f"Environment not solved in {episode_length} steps.")
223
+ ```
224
+
225
+
226
+ ### FrozenLake Video Example
227
+
228
+ ![FrozenLake Video as .gif](./resources/frozenlake_4x4-episode-0-video-to-gif-converted.gif)
229
+
230
+ To create a video of the solution of the FrozenLake environment, you can use the following code snippet:
231
+
232
+ ```python
233
+ import gymnasium as gym
234
+
235
+ from gymcts.gymcts_agent import SoloMCTSAgent
236
+ from gymcts.gymcts_naive_wrapper import NaiveSoloMCTSGymEnvWrapper
237
+
238
+ from gymcts.logger import log
239
+
240
+ log.setLevel(20)
241
+
242
+ from gymnasium.envs.toy_text.frozen_lake import FrozenLakeEnv
243
+
244
+ if __name__ == '__main__':
245
+ log.debug("Starting example")
246
+
247
+ # 0. create the environment
248
+ env = gym.make('FrozenLake-v1', desc=None, map_name="4x4", is_slippery=False, render_mode="rgb_array")
249
+ env.reset()
250
+
251
+ # 1. wrap the environment with the naive wrapper or a custom gymcts wrapper
252
+ env = NaiveSoloMCTSGymEnvWrapper(env)
253
+
254
+ # 2. create the agent
255
+ agent = SoloMCTSAgent(
256
+ env=env,
257
+ clear_mcts_tree_after_step=False,
258
+ render_tree_after_step=True,
259
+ number_of_simulations_per_step=200,
260
+ exclude_unvisited_nodes_from_render=True
261
+ )
262
+
263
+ # 3. solve the environment
264
+ actions = agent.solve()
265
+
266
+ # 4. render the environment solution
267
+ env = gym.wrappers.RecordVideo(
268
+ env,
269
+ video_folder="./videos",
270
+ episode_trigger=lambda episode_id: True,
271
+ name_prefix="frozenlake_4x4"
272
+ )
273
+ env.reset()
274
+
275
+ for a in actions:
276
+ obs, rew, term, trun, info = env.step(a)
277
+ env.close()
278
+
279
+ # 5. print the solution
280
+ # read the solution from the info provided by the RecordEpisodeStatistics wrapper (that NaiveSoloMCTSGymEnvWrapper wraps internally)
281
+ episode_length = info["episode"]["l"]
282
+ episode_return = info["episode"]["r"]
283
+
284
+ if episode_return == 1.0:
285
+ print(f"Environment solved in {episode_length} steps.")
286
+ else:
287
+ print(f"Environment not solved in {episode_length} steps.")
288
+ ```
289
+
290
+ ### Job Shop Scheduling (CustomWrapper)
291
+
292
+ ![](https://github.com/Alexander-Nasuta/GraphMatrixJobShopEnv/raw/master/resources/default-render.gif)
293
+
294
+ The following code snippet shows how to use the package with the [graph-jsp-env](https://github.com/Alexander-Nasuta/graph-jsp-env) environment.
295
+
296
+ First, install the environment via pip:
297
+
298
+ ```shell
299
+ pip install graph-jsp-env
300
+ ```
301
+
302
+ and a utility package for JSP instances:
303
+
304
+ ```shell
305
+ pip install jsp-instance-utils
306
+ ```
307
+
308
+ Then, you can use the following code snippet to solve the environment with the MCTS agent:
309
+ ```
310
+
311
+ ```python
312
+ from typing import Any
313
+
314
+ import random
315
+
316
+ import gymnasium as gym
317
+
318
+ from graph_jsp_env.disjunctive_graph_jsp_env import DisjunctiveGraphJspEnv
319
+ from jsp_instance_utils.instances import ft06, ft06_makespan
320
+
321
+ from gymcts.gymcts_agent import SoloMCTSAgent
322
+ from gymcts.gymcts_gym_env import SoloMCTSGymEnv
323
+
324
+ from gymcts.logger import log
325
+
326
+
327
+ class GraphJspGYMCTSWrapper(SoloMCTSGymEnv, gym.Wrapper):
328
+
329
+ def __init__(self, env: DisjunctiveGraphJspEnv):
330
+ gym.Wrapper.__init__(self, env)
331
+
332
+ def load_state(self, state: Any) -> None:
333
+ self.env.reset()
334
+ for action in state:
335
+ self.env.step(action)
336
+
337
+ def is_terminal(self) -> bool:
338
+ return self.env.unwrapped.is_terminal()
339
+
340
+ def get_valid_actions(self) -> list[int]:
341
+ return list(self.env.unwrapped.valid_actions())
342
+
343
+ def rollout(self) -> float:
344
+ terminal = env.is_terminal()
345
+
346
+ if terminal:
347
+ lower_bound = env.unwrapped.reward_function_parameters['scaling_divisor']
348
+ return - env.unwrapped.get_makespan() / lower_bound + 2
349
+
350
+ reward = 0
351
+ while not terminal:
352
+ action = random.choice(self.get_valid_actions())
353
+ obs, reward, terminal, truncated, _ = env.step(action)
354
+
355
+ return reward + 2
356
+
357
+ def get_state(self) -> Any:
358
+ return env.unwrapped.get_action_history()
359
+
360
+
361
+ if __name__ == '__main__':
362
+ log.setLevel(20)
363
+
364
+ env_kwargs = {
365
+ "jps_instance": ft06,
366
+ "default_visualisations": ["gantt_console", "graph_console"],
367
+ "reward_function_parameters": {
368
+ "scaling_divisor": ft06_makespan
369
+ },
370
+ "reward_function": "nasuta",
371
+ }
372
+
373
+ env = DisjunctiveGraphJspEnv(**env_kwargs)
374
+ env.reset()
375
+
376
+ env = GraphJspGYMCTSWrapper(env)
377
+
378
+ agent = SoloMCTSAgent(
379
+ env=env,
380
+ clear_mcts_tree_after_step=True,
381
+ render_tree_after_step=True,
382
+ exclude_unvisited_nodes_from_render=True,
383
+ number_of_simulations_per_step=50,
384
+ )
385
+
386
+ root = agent.search_root_node.get_root()
387
+
388
+ actions = agent.solve(render_tree_after_step=True)
389
+ for a in actions:
390
+ obs, rew, term, trun, info = env.step(a)
391
+
392
+ env.render()
393
+ makespan = env.unwrapped.get_makespan()
394
+ print(f"makespan: {makespan}")
395
+
396
+ ```
397
+
398
+ ## Visualizations
399
+
400
+ The MCTS agent provides a visualisation of the MCTS tree.
401
+ Below is an example code snippet that shows how to use the visualisation options of the MCTS agent.
402
+
403
+ The following metrics are displayed in the visualisation:
404
+ - `N`: the number of visits of the node
405
+ - `Q_v`: the average return of the node
406
+ - `ubc`: the upper confidence bound of the node
407
+ - `a`: the action that leads to the node
408
+ - `best`: the highest return of any rollout from the node
409
+
410
+ `Q_v` and `ubc` have a color gradient from red to green, where red indicates a low value and green indicates a high value.
411
+ The color gradient is based on the minimum and maximum values of the respective metric in the tree.
412
+
413
+ The visualisation is rendered in the terminal and can be limited to a certain depth of the tree.
414
+ The default depth is 2.
415
+
416
+
417
+ ```python
418
+ import gymnasium as gym
419
+
420
+ from gymcts.gymcts_agent import SoloMCTSAgent
421
+ from gymcts.gymcts_deterministic_wrapper import DeterministicSoloMCTSGymEnvWrapper
422
+ from gymcts.gymcts_naive_wrapper import NaiveSoloMCTSGymEnvWrapper
423
+
424
+ from gymcts.logger import log
425
+
426
+ # set log level to 20 (INFO)
427
+ # set log level to 10 (DEBUG) to see more detailed information
428
+ log.setLevel(20)
429
+
430
+ if __name__ == '__main__':
431
+ # create the environment
432
+ env = gym.make('FrozenLake-v1', desc=None, map_name="4x4", is_slippery=False, render_mode="ansi")
433
+ env.reset()
434
+
435
+ # wrap the environment with the naive wrapper or a custom gymcts wrapper
436
+ env = DeterministicSoloMCTSGymEnvWrapper(env)
437
+
438
+ # create the agent
439
+ agent = SoloMCTSAgent(
440
+ env=env,
441
+ clear_mcts_tree_after_step=False,
442
+ render_tree_after_step=False,
443
+ number_of_simulations_per_step=50,
444
+ exclude_unvisited_nodes_from_render=True, # weather to exclude unvisited nodes from the render
445
+ render_tree_max_depth=2 # the maximum depth of the tree to render
446
+ )
447
+
448
+ # solve the environment
449
+ actions = agent.solve()
450
+
451
+ # render the MCTS tree from the root
452
+ # search_root_node is the node that corresponds to the current state of the environment in the search process
453
+ # since we called agent.solve() we are at the end of the search process
454
+ log.info(f"MCTS Tree starting at the final state of the environment (actions: {agent.search_root_node.state})")
455
+ agent.show_mcts_tree(
456
+ start_node=agent.search_root_node,
457
+ )
458
+
459
+ # the parent of the terminal node (which we are rendering below) is the search root node of the previous step in the
460
+ # MCTS solving process
461
+ log.info(
462
+ f"MCTS Tree starting at the pre-final state of the environment (actions: {agent.search_root_node.parent.state})")
463
+ agent.show_mcts_tree(
464
+ start_node=agent.search_root_node.parent,
465
+ )
466
+
467
+ # render the MCTS tree from the root
468
+ log.info(f"MCTS Tree starting at the root state (actions: {agent.search_root_node.get_root().state})")
469
+ agent.show_mcts_tree(
470
+ start_node=agent.search_root_node.get_root(),
471
+ # you can limit the depth of the tree to render to any number
472
+ tree_max_depth=1
473
+ )
474
+ ```
475
+
476
+ ![visualsiation example on the frozenlanke environment](./resources/mcts_visualisation.png)
477
+
478
+
479
+ ## State of the Project
480
+
481
+ This project is complementary material for a research paper. It will not be frequently updated.
482
+ Minor updates might occur.
483
+ Significant further development will most likely result in a new project. In that case, a note with a link will be added in the `README.md` of this project.
484
+
485
+ ## Dependencies
486
+
487
+ This project specifies multiple requirements files.
488
+ `requirements.txt` contains the dependencies for the environment to work. These requirements will be installed automatically when installing the environment via `pip`.
489
+ `requirements_dev.txt` contains the dependencies for development purposes. It includes the dependencies for testing, linting, and building the project on top of the dependencies in `requirements.txt`.
490
+ `requirements_examples.txt` contains the dependencies for running the examples inside the project. It includes the dependencies in `requirements.txt` and additional dependencies for the examples.
491
+
492
+ In this Project the dependencies are specified in the `pyproject.toml` file with as little version constraints as possible.
493
+ The tool `pip-compile` translates the `pyproject.toml` file into a `requirements.txt` file with pinned versions.
494
+ That way version conflicts can be avoided (as much as possible) and the project can be built in a reproducible way.
495
+
496
+ ## Development Setup
497
+
498
+ If you want to check out the code and implement new features or fix bugs, you can set up the project as follows:
499
+
500
+ ### Clone the Repository
501
+
502
+ clone the repository in your favorite code editor (for example PyCharm, VSCode, Neovim, etc.)
503
+
504
+ using https:
505
+ ```shell
506
+ git clone https://github.com/Alexander-Nasuta/todo
507
+ ```
508
+ or by using the GitHub CLI:
509
+ ```shell
510
+ gh repo clone Alexander-Nasuta/todo
511
+ ```
512
+
513
+ if you are using PyCharm, I recommend doing the following additional steps:
514
+
515
+ - mark the `src` folder as source root (by right-clicking on the folder and selecting `Mark Directory as` -> `Sources Root`)
516
+ - mark the `tests` folder as test root (by right-clicking on the folder and selecting `Mark Directory as` -> `Test Sources Root`)
517
+ - mark the `resources` folder as resources root (by right-clicking on the folder and selecting `Mark Directory as` -> `Resources Root`)
518
+
519
+ at the end your project structure should look like this:
520
+
521
+ todo
522
+
523
+ ### Create a Virtual Environment (optional)
524
+
525
+ Most Developers use a virtual environment to manage the dependencies of their projects.
526
+ I personally use `conda` for this purpose.
527
+
528
+ When using `conda`, you can create a new environment with the name 'my-graph-jsp-env' following command:
529
+
530
+ ```shell
531
+ conda create -n gymcts python=3.11
532
+ ```
533
+
534
+ Feel free to use any other name for the environment or an more recent version of python.
535
+ Activate the environment with the following command:
536
+
537
+ ```shell
538
+ conda activate gymcts
539
+ ```
540
+
541
+ Replace `gymcts` with the name of your environment, if you used a different name.
542
+
543
+ You can also use `venv` or `virtualenv` to create a virtual environment. In that case please refer to the respective documentation.
544
+
545
+ ### Install the Dependencies
546
+
547
+ To install the dependencies for development purposes, run the following command:
548
+
549
+ ```shell
550
+ pip install -r requirements_dev.txt
551
+ pip install tox
552
+ ```
553
+
554
+ The testing package `tox` is not included in the `requirements_dev.txt` file, because it sometimes causes issues when
555
+ using github actions.
556
+ Github Actions uses an own tox environment (namely 'tox-gh-actions'), which can cause conflicts with the tox environment on your local machine.
557
+
558
+ Reference: [Automated Testing in Python with pytest, tox, and GitHub Actions](https://www.youtube.com/watch?v=DhUpxWjOhME).
559
+
560
+ ### Install the Project in Editable Mode
561
+
562
+ To install the project in editable mode, run the following command:
563
+
564
+ ```shell
565
+ pip install -e .
566
+ ```
567
+
568
+ This will install the project in editable mode, so you can make changes to the code and test them immediately.
569
+
570
+ ### Run the Tests
571
+
572
+ This project uses `pytest` for testing. To run the tests, run the following command:
573
+
574
+ ```shell
575
+ pytest
576
+ ```
577
+ Here is a screenshot of what the output might look like:
578
+
579
+ ![](https://github.com/Alexander-Nasuta/GraphMatrixJobShopEnv/raw/master/resources/pytest-screenshot.png)
580
+
581
+ For testing with `tox` run the following command:
582
+
583
+ ```shell
584
+ tox
585
+ ```
586
+
587
+ Here is a screenshot of what the output might look like:
588
+
589
+ ![](https://github.com/Alexander-Nasuta/GraphMatrixJobShopEnv/raw/master/resources/tox-screenshot.png)
590
+
591
+ Tox will run the tests in a separate environment and will also check if the requirements are installed correctly.
592
+
593
+ ### Builing and Publishing the Project to PyPi
594
+
595
+ In order to publish the project to PyPi, the project needs to be built and then uploaded to PyPi.
596
+
597
+ To build the project, run the following command:
598
+
599
+ ```shell
600
+ python -m build
601
+ ```
602
+
603
+ It is considered good practice use the tool `twine` for checking the build and uploading the project to PyPi.
604
+ By default the build command creates a `dist` folder with the built project files.
605
+ To check all the files in the `dist` folder, run the following command:
606
+
607
+ ```shell
608
+ twine check dist/**
609
+ ```
610
+
611
+ If the check is successful, you can upload the project to PyPi with the following command:
612
+
613
+ ```shell
614
+ twine upload dist/**
615
+ ```
616
+
617
+ ### Documentation
618
+ This project uses `sphinx` for generating the documentation.
619
+ It also uses a lot of sphinx extensions to make the documentation more readable and interactive.
620
+ For example the extension `myst-parser` is used to enable markdown support in the documentation (instead of the usual .rst-files).
621
+ It also uses the `sphinx-autobuild` extension to automatically rebuild the documentation when changes are made.
622
+ By running the following command, the documentation will be automatically built and served, when changes are made (make sure to run this command in the root directory of the project):
623
+
624
+ ```shell
625
+ sphinx-autobuild ./docs/source/ ./docs/build/html/
626
+ ```
627
+
628
+ This project features most of the extensions featured in this Tutorial: [Document Your Scientific Project With Markdown, Sphinx, and Read the Docs | PyData Global 2021](https://www.youtube.com/watch?v=qRSb299awB0).
629
+
630
+
631
+
632
+ ## Contact
633
+
634
+ If you have any questions or feedback, feel free to contact me via [email](mailto:alexander.nasuta@wzl-iqs.rwth-aachen.de) or open an issue on repository.