rhetor 1.9.2__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 (127) hide show
  1. rhetor-1.9.2/LICENSE.txt +21 -0
  2. rhetor-1.9.2/MANIFEST.in +6 -0
  3. rhetor-1.9.2/PKG-INFO +53 -0
  4. rhetor-1.9.2/README.md +230 -0
  5. rhetor-1.9.2/RHETOR.md +16 -0
  6. rhetor-1.9.2/pyopl/__init__.py +21 -0
  7. rhetor-1.9.2/pyopl/__main__.py +10 -0
  8. rhetor-1.9.2/pyopl/_pyopl_mcp.py +307 -0
  9. rhetor-1.9.2/pyopl/_rhetor_mcp.py +477 -0
  10. rhetor-1.9.2/pyopl/genai/__init__.py +0 -0
  11. rhetor-1.9.2/pyopl/genai/_strategy_base.py +1107 -0
  12. rhetor-1.9.2/pyopl/genai/genai_pricing.py +308 -0
  13. rhetor-1.9.2/pyopl/genai/model_discovery.py +38 -0
  14. rhetor-1.9.2/pyopl/genai/pyopl_cafa.py +743 -0
  15. rhetor-1.9.2/pyopl/genai/pyopl_cafa.pyi +102 -0
  16. rhetor-1.9.2/pyopl/genai/pyopl_chain_of_experts.py +1019 -0
  17. rhetor-1.9.2/pyopl/genai/pyopl_chain_of_experts.pyi +107 -0
  18. rhetor-1.9.2/pyopl/genai/pyopl_chain_of_thought.py +732 -0
  19. rhetor-1.9.2/pyopl/genai/pyopl_chain_of_thought.pyi +105 -0
  20. rhetor-1.9.2/pyopl/genai/pyopl_generative.py +1064 -0
  21. rhetor-1.9.2/pyopl/genai/pyopl_generative.pyi +144 -0
  22. rhetor-1.9.2/pyopl/genai/pyopl_generative_graphchain.py +871 -0
  23. rhetor-1.9.2/pyopl/genai/pyopl_generative_graphchain.pyi +155 -0
  24. rhetor-1.9.2/pyopl/genai/pyopl_reflexion.py +828 -0
  25. rhetor-1.9.2/pyopl/genai/pyopl_reflexion.pyi +102 -0
  26. rhetor-1.9.2/pyopl/genai/pyopl_standard.py +772 -0
  27. rhetor-1.9.2/pyopl/genai/pyopl_standard.pyi +104 -0
  28. rhetor-1.9.2/pyopl/genai/pyopl_tree_of_thoughts.py +1016 -0
  29. rhetor-1.9.2/pyopl/genai/pyopl_tree_of_thoughts.pyi +104 -0
  30. rhetor-1.9.2/pyopl/genai/rag_helper.py +118 -0
  31. rhetor-1.9.2/pyopl/grammars/JSON_SCHEMA_AST.json +734 -0
  32. rhetor-1.9.2/pyopl/grammars/PyOPL grammar.md +602 -0
  33. rhetor-1.9.2/pyopl/grammars/PyOPL_GBNF +302 -0
  34. rhetor-1.9.2/pyopl/gurobi_codegen.py +3636 -0
  35. rhetor-1.9.2/pyopl/icon/__init__.py +1 -0
  36. rhetor-1.9.2/pyopl/icon/gear.png +0 -0
  37. rhetor-1.9.2/pyopl/icon/mindset.png +0 -0
  38. rhetor-1.9.2/pyopl/linear_problem.py +20 -0
  39. rhetor-1.9.2/pyopl/linear_problem_highs.py +141 -0
  40. rhetor-1.9.2/pyopl/milp_equivalence.py +907 -0
  41. rhetor-1.9.2/pyopl/opl_models/assignment/assignment.dat +3 -0
  42. rhetor-1.9.2/pyopl/opl_models/assignment/assignment.mod +57 -0
  43. rhetor-1.9.2/pyopl/opl_models/assignment/assignment.txt +2 -0
  44. rhetor-1.9.2/pyopl/opl_models/crew_pairing/crew_pairing.dat +5 -0
  45. rhetor-1.9.2/pyopl/opl_models/crew_pairing/crew_pairing.mod +88 -0
  46. rhetor-1.9.2/pyopl/opl_models/crew_pairing/crew_pairing.txt +1 -0
  47. rhetor-1.9.2/pyopl/opl_models/crew_scheduling/crew_scheduling.dat +5 -0
  48. rhetor-1.9.2/pyopl/opl_models/crew_scheduling/crew_scheduling.mod +63 -0
  49. rhetor-1.9.2/pyopl/opl_models/crew_scheduling/crew_scheduling.txt +1 -0
  50. rhetor-1.9.2/pyopl/opl_models/graph_coloring/graph_coloring.dat +3 -0
  51. rhetor-1.9.2/pyopl/opl_models/graph_coloring/graph_coloring.mod +103 -0
  52. rhetor-1.9.2/pyopl/opl_models/graph_coloring/graph_coloring.txt +1 -0
  53. rhetor-1.9.2/pyopl/opl_models/inventory_routing/inventory_routing.dat +33 -0
  54. rhetor-1.9.2/pyopl/opl_models/inventory_routing/inventory_routing.mod +117 -0
  55. rhetor-1.9.2/pyopl/opl_models/inventory_routing/inventory_routing.txt +1 -0
  56. rhetor-1.9.2/pyopl/opl_models/jobshop/jobshop.dat +9 -0
  57. rhetor-1.9.2/pyopl/opl_models/jobshop/jobshop.mod +106 -0
  58. rhetor-1.9.2/pyopl/opl_models/jobshop/jobshop.txt +1 -0
  59. rhetor-1.9.2/pyopl/opl_models/knapsack/knapsack.dat +4 -0
  60. rhetor-1.9.2/pyopl/opl_models/knapsack/knapsack.mod +93 -0
  61. rhetor-1.9.2/pyopl/opl_models/knapsack/knapsack.txt +1 -0
  62. rhetor-1.9.2/pyopl/opl_models/knapsack/knapsackp.dat +11 -0
  63. rhetor-1.9.2/pyopl/opl_models/knapsack/knapsackp.mod +88 -0
  64. rhetor-1.9.2/pyopl/opl_models/knapsack/knapsackp.txt +1 -0
  65. rhetor-1.9.2/pyopl/opl_models/lot_sizing/lot_sizing.dat +5 -0
  66. rhetor-1.9.2/pyopl/opl_models/lot_sizing/lot_sizing.mod +65 -0
  67. rhetor-1.9.2/pyopl/opl_models/lot_sizing/lot_sizing.txt +1 -0
  68. rhetor-1.9.2/pyopl/opl_models/maintenance/maintenance.dat +58 -0
  69. rhetor-1.9.2/pyopl/opl_models/maintenance/maintenance.mod +74 -0
  70. rhetor-1.9.2/pyopl/opl_models/maintenance/maintenance.txt +68 -0
  71. rhetor-1.9.2/pyopl/opl_models/on_off_outsourcing/on_off_outsourcing.dat +6 -0
  72. rhetor-1.9.2/pyopl/opl_models/on_off_outsourcing/on_off_outsourcing.mod +108 -0
  73. rhetor-1.9.2/pyopl/opl_models/on_off_outsourcing/on_off_outsourcing.txt +1 -0
  74. rhetor-1.9.2/pyopl/opl_models/p-dispersion/p-dispersion.dat +12 -0
  75. rhetor-1.9.2/pyopl/opl_models/p-dispersion/p-dispersion.mod +78 -0
  76. rhetor-1.9.2/pyopl/opl_models/p-dispersion/p-dispersion.txt +1 -0
  77. rhetor-1.9.2/pyopl/opl_models/plant_location/plant_location.dat +7 -0
  78. rhetor-1.9.2/pyopl/opl_models/plant_location/plant_location.mod +70 -0
  79. rhetor-1.9.2/pyopl/opl_models/plant_location/plant_location.txt +1 -0
  80. rhetor-1.9.2/pyopl/opl_models/production/production.dat +5 -0
  81. rhetor-1.9.2/pyopl/opl_models/production/production.mod +79 -0
  82. rhetor-1.9.2/pyopl/opl_models/production/production.txt +1 -0
  83. rhetor-1.9.2/pyopl/opl_models/set_covering/set_covering.dat +8 -0
  84. rhetor-1.9.2/pyopl/opl_models/set_covering/set_covering.mod +50 -0
  85. rhetor-1.9.2/pyopl/opl_models/set_covering/set_covering.txt +1 -0
  86. rhetor-1.9.2/pyopl/opl_models/set_partitioning/set_partitioning.dat +6 -0
  87. rhetor-1.9.2/pyopl/opl_models/set_partitioning/set_partitioning.mod +64 -0
  88. rhetor-1.9.2/pyopl/opl_models/set_partitioning/set_partitioning.txt +1 -0
  89. rhetor-1.9.2/pyopl/opl_models/stochastic_production/stochastic_production.dat +15 -0
  90. rhetor-1.9.2/pyopl/opl_models/stochastic_production/stochastic_production.mod +42 -0
  91. rhetor-1.9.2/pyopl/opl_models/stochastic_production/stochastic_production.txt +1 -0
  92. rhetor-1.9.2/pyopl/opl_models/stochastic_scheduling/stochastic_scheduling.dat +37 -0
  93. rhetor-1.9.2/pyopl/opl_models/stochastic_scheduling/stochastic_scheduling.mod +110 -0
  94. rhetor-1.9.2/pyopl/opl_models/stochastic_scheduling/stochastic_scheduling.txt +12 -0
  95. rhetor-1.9.2/pyopl/opl_models/transportation/transportation.dat +5 -0
  96. rhetor-1.9.2/pyopl/opl_models/transportation/transportation.mod +88 -0
  97. rhetor-1.9.2/pyopl/opl_models/transportation/transportation.txt +1 -0
  98. rhetor-1.9.2/pyopl/opl_models/tsp/tsp.dat +7 -0
  99. rhetor-1.9.2/pyopl/opl_models/tsp/tsp.mod +83 -0
  100. rhetor-1.9.2/pyopl/opl_models/tsp/tsp.txt +1 -0
  101. rhetor-1.9.2/pyopl/opl_models/vehicle_routing/vehicle_routing.dat +9 -0
  102. rhetor-1.9.2/pyopl/opl_models/vehicle_routing/vehicle_routing.mod +96 -0
  103. rhetor-1.9.2/pyopl/opl_models/vehicle_routing/vehicle_routing.txt +1 -0
  104. rhetor-1.9.2/pyopl/opl_models/warehouse_location/warehouse_location.dat +8 -0
  105. rhetor-1.9.2/pyopl/opl_models/warehouse_location/warehouse_location.mod +79 -0
  106. rhetor-1.9.2/pyopl/opl_models/warehouse_location/warehouse_location.txt +1 -0
  107. rhetor-1.9.2/pyopl/opl_models/workforce_planning/workforce_planning.dat +44 -0
  108. rhetor-1.9.2/pyopl/opl_models/workforce_planning/workforce_planning.mod +225 -0
  109. rhetor-1.9.2/pyopl/opl_models/workforce_planning/workforce_planning.txt +1 -0
  110. rhetor-1.9.2/pyopl/pyopl_cli.py +449 -0
  111. rhetor-1.9.2/pyopl/pyopl_core.py +5817 -0
  112. rhetor-1.9.2/pyopl/pyopl_ide_bootstrap.py +6338 -0
  113. rhetor-1.9.2/pyopl/pyopl_mcp.py +10 -0
  114. rhetor-1.9.2/pyopl/rhetor_mcp.py +10 -0
  115. rhetor-1.9.2/pyopl/scipy_codegen.py +30 -0
  116. rhetor-1.9.2/pyopl/scipy_codegen_base.py +25 -0
  117. rhetor-1.9.2/pyopl/scipy_codegen_csc.py +7608 -0
  118. rhetor-1.9.2/pyopl/semantic_error.py +80 -0
  119. rhetor-1.9.2/pyopl/tuple_set_helper.py +34 -0
  120. rhetor-1.9.2/pyproject.toml +110 -0
  121. rhetor-1.9.2/rhetor.egg-info/PKG-INFO +53 -0
  122. rhetor-1.9.2/rhetor.egg-info/SOURCES.txt +125 -0
  123. rhetor-1.9.2/rhetor.egg-info/dependency_links.txt +1 -0
  124. rhetor-1.9.2/rhetor.egg-info/entry_points.txt +2 -0
  125. rhetor-1.9.2/rhetor.egg-info/requires.txt +19 -0
  126. rhetor-1.9.2/rhetor.egg-info/top_level.txt +1 -0
  127. rhetor-1.9.2/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 University of Edinburgh
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,6 @@
1
+ recursive-include pyopl/icon *.png
2
+ recursive-include pyopl *.pyi
3
+ recursive-include pyopl/grammars *
4
+ recursive-include pyopl/opl_models *
5
+ prune test
6
+ global-exclude .DS_Store
rhetor-1.9.2/PKG-INFO ADDED
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: rhetor
3
+ Version: 1.9.2
4
+ Summary: A Python library for parsing and solving OPL-like mathematical programming models using multiple solvers.
5
+ Author-email: Roberto Rossi <robros@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://gwr3n.github.io/rhetor/
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE.txt
17
+ Requires-Dist: sly>=0.5
18
+ Requires-Dist: numpy>=1.26
19
+ Requires-Dist: scipy>=1.14
20
+ Requires-Dist: gurobipy>=11.0
21
+ Requires-Dist: highspy>=1.11
22
+ Requires-Dist: Pillow>=10
23
+ Requires-Dist: PyMuPDF>=1.24
24
+ Requires-Dist: ttkbootstrap>=1.14
25
+ Requires-Dist: platformdirs>=4.1
26
+ Requires-Dist: openai>=1.99
27
+ Requires-Dist: google-generativeai>=0.8.5
28
+ Requires-Dist: google-genai>=1.0.0
29
+ Requires-Dist: ollama>=0.6.0
30
+ Requires-Dist: tensorflow>=2.16.2
31
+ Requires-Dist: sentence-transformers>=5.1.1
32
+ Requires-Dist: tf-keras>=2.16.0
33
+ Requires-Dist: tiktoken>=0.12.0
34
+ Requires-Dist: mcp>=1.27.0
35
+ Requires-Dist: networkx>=3.4
36
+ Dynamic: license-file
37
+
38
+ # Rhetor
39
+
40
+ **GenAI-assisted Integrated Modelling Environment for Mixed-Integer Linear Programming**
41
+
42
+ Rhetor is an Integrated Modelling Environment for modelling and solving mixed-integer linear programming problems. Rhetor supports a subset of the Optimisation Programming Language (OPL) syntax and it is built on [``pyopl``](https://github.com/gwr3n/pyopl), a Python library for parsing and solving OPL-like mathematical programming models using Gurobi or Scipy (Highs). Rhetor integrates GenAI features to support and automate the modelling process.
43
+
44
+ ![Demo](https://github.com/gwr3n/rhetor/raw/main/sudoku.gif)
45
+
46
+ ## Learn More
47
+
48
+ - [Project Website](https://gwr3n.github.io/rhetor)
49
+ - [Project Repository](https://github.com/gwr3n/rhetor)
50
+ - [PyOPL Repository](https://github.com/gwr3n/pyopl)
51
+ - [PyOPL User Guide](https://github.com/gwr3n/rhetor/blob/main/docs/PyOPL%20user%20guide.md)
52
+ - [Examples Overview](https://github.com/gwr3n/rhetor/blob/main/docs/PyOPL%20examples%20overview.md)
53
+ - [Sample PyOPL Models](https://github.com/gwr3n/rhetor/tree/main/opl_models)
rhetor-1.9.2/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # pyopl - Python Optimisation Programming Language
2
+
3
+ Core package badges:
4
+
5
+ [![Codecov](https://img.shields.io/codecov/c/gh/gwr3n/pyopl/main)](https://codecov.io/gh/gwr3n/pyopl)
6
+ [![Python package](https://img.shields.io/github/actions/workflow/status/gwr3n/pyopl/python-package.yml?branch=main&label=python%20package)](https://github.com/gwr3n/pyopl/actions/workflows/python-package.yml)
7
+ [![Lint and type-check](https://img.shields.io/github/actions/workflow/status/gwr3n/pyopl/lint-type.yml?branch=main&label=lint%20%2B%20type-check)](https://github.com/gwr3n/pyopl/actions/workflows/lint-type.yml)
8
+ [![Rhetor on PyPI](https://img.shields.io/pypi/v/rhetor)](https://pypi.org/project/rhetor/)
9
+ [![Python versions](https://img.shields.io/pypi/pyversions/rhetor)](https://pypi.org/project/rhetor/)
10
+ [![Downloads](https://img.shields.io/pypi/dm/rhetor)](https://pypistats.org/packages/rhetor)
11
+ [![Release](https://img.shields.io/github/v/release/gwr3n/pyopl)](https://github.com/gwr3n/pyopl/releases)
12
+ [![Wheel](https://img.shields.io/pypi/wheel/rhetor)](https://pypi.org/project/rhetor/)
13
+ [![License](https://img.shields.io/github/license/gwr3n/pyopl?v=2)](LICENSE.txt)
14
+
15
+ Quality and tooling:
16
+
17
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000?logo=python)](https://github.com/psf/black)
18
+ [![Ruff](https://img.shields.io/badge/lint-ruff-1f79ff?logo=python)](https://github.com/astral-sh/ruff) [![mypy](https://img.shields.io/badge/type--checked-mypy-blue?logo=python)](https://github.com/python/mypy)
19
+ [![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
20
+ [![security: pip-audit](https://img.shields.io/badge/security-pip--audit-008080.svg)](https://github.com/pypa/pip-audit)
21
+ [![secrets: Betterleaks](https://img.shields.io/badge/secrets-Betterleaks-24292e.svg?logo=github)](https://github.com/betterleaks/betterleaks)
22
+
23
+ Project/community:
24
+
25
+ [![Issues](https://img.shields.io/github/issues/gwr3n/pyopl)](https://github.com/gwr3n/pyopl/issues)
26
+ [![PRs](https://img.shields.io/github/issues-pr/gwr3n/pyopl)](https://github.com/gwr3n/pyopl/pulls)
27
+ [![Stars](https://img.shields.io/github/stars/gwr3n/pyopl?style=social)](https://github.com/gwr3n/pyopl/stargazers)
28
+
29
+ Docs:
30
+
31
+ [![Docs](https://img.shields.io/badge/docs-user%20guide-blue)](docs/PyOPL%20user%20guide.md)
32
+
33
+
34
+ `pyopl` is a Python library for parsing and solving OPL-like [1] mathematical programming models using either Gurobi or the open-source SciPy (HiGHS) solver. PyOPL supports a rich subset of Optimisation Programming Language (OPL) syntax for linear and mixed-integer programming.
35
+
36
+ [1] Van Hentenryck, P. (1999). The OPL optimization programming language. London, England: MIT Press.
37
+
38
+ ## Installation
39
+
40
+ The GitHub project and importable compiler package are named `pyopl`; the published PyPI distribution is named `rhetor`.
41
+
42
+ Install via pip (recommended):
43
+
44
+ ```
45
+ pip install rhetor
46
+ ```
47
+
48
+ Or clone the repository and install locally:
49
+
50
+ ```
51
+ git clone https://github.com/gwr3n/pyopl.git
52
+ cd pyopl
53
+ pip install .
54
+ ```
55
+
56
+ Dependencies are managed via `pyproject.toml` and are listed in [`requirements.txt`](./requirements.txt)
57
+
58
+ PyOPL requires Python 3.10+
59
+
60
+ You can use either Gurobi or SciPy/HiGHS as the solver. Both solvers are selectable in the API and the Rhetor IME. PyOPL provides robust support for tuple/nested tuple data, advanced boolean logic, implication, and field access in both models and data files.
61
+
62
+ ## Usage
63
+
64
+ ### Solving an OPL Model
65
+
66
+ You can use the `solve` function to load and solve an OPL model (and optional data file). Choose the solver with the `solver` argument:
67
+
68
+ ```python
69
+ from pyopl import solve
70
+ results = solve('model.mod', 'data.dat', solver='gurobi') # Use Gurobi (default)
71
+ results = solve('model.mod', 'data.dat', solver='scipy') # Use SciPy/HiGHS
72
+ ```
73
+
74
+ #### Example
75
+
76
+ Suppose you have the following files:
77
+
78
+ - `knapsack.mod` (your OPL model)
79
+ - `knapsack.dat` (your data file)
80
+
81
+ You can solve the model as follows:
82
+
83
+
84
+ ```python
85
+ from pyopl import solve
86
+
87
+ model = "knapsack.mod"
88
+ data = "knapsack.dat"
89
+
90
+ results = solve(model, data)
91
+ print(results)
92
+ ```
93
+
94
+
95
+ This will print the parsed AST, the generated solver code, and the solution output from the selected solver. The `solve` function returns a dictionary with the following keys:
96
+ - `status`: The status of the optimization (e.g., 'OPTIMAL', 'INFEASIBLE', etc.)
97
+ - `solution`: A dictionary of variable names and their values (if optimal)
98
+ - `objective_value`: The value of the objective function (if optimal)
99
+ - `stats`: Additional statistics (e.g., MIPGap, Runtime, NodeCount, IterCount)
100
+ - `message`: Any error or status messages
101
+
102
+ Below, we provide model and data file for our knapsack example.
103
+
104
+ Example contents for `knapsack.mod`:
105
+ ```opl
106
+ // knapsack.mod
107
+ int+ n = ...; // non-negative integer parameter
108
+ float+ c = ...; // non-negative float parameter
109
+ float+ w[1..n] = ...; // indexed parameter
110
+ float+ v[1..n] = ...;
111
+
112
+ dvar boolean x[1..n];
113
+
114
+ maximize sum(i in 1..n) v[i] * x[i];
115
+ subject to {
116
+ sum(i in 1..n) w[i] * x[i] <= c;
117
+ forall(i in 1..n) x[i] >= 0;
118
+ }
119
+ ```
120
+
121
+ Example contents for `knapsack.dat`:
122
+ ```opl
123
+ // knapsack.dat
124
+ n = 4;
125
+ c = 10;
126
+ w = [2, 3, 4, 5];
127
+ v = [3, 4, 5, 6];
128
+ ```
129
+
130
+ See `tools/examples.py` for a repository of examples.
131
+
132
+
133
+ ## PyOPL IME
134
+
135
+ PyOPL includes [Rhetor](https://gwr3n.github.io/rhetor), a GenAI-first integrated modelling environment (IME) for creating, revising, solving, exporting, and versioning OPL models and data files.
136
+
137
+ ![Demo](https://github.com/gwr3n/rhetor/raw/main/sudoku.gif)
138
+
139
+ The IME features:
140
+
141
+ - GenAI-first modelling workflows for generating models, revising existing model/data pairs, asking questions about a formulation, and explaining solutions with OpenAI, Google/Gemini, or Ollama models when configured
142
+ - Optional visual prompt attachments for supported GenAI workflows, including images and short PDFs
143
+ - Session-based model version tracking: each run/request can keep a timestamped snapshot that can be previewed, diffed against the current editors, restored, renamed, or deleted
144
+ - Output and session panels for reviewing recent runs, generated artifacts, model/data snapshots, and GenAI interactions
145
+ - Syntax-highlighted model and data editors with open, save, save-as, undo, redo, find, and replace
146
+ - Integrated solve workflow with Gurobi or SciPy/HiGHS selection, solver logs, elapsed-time status, and optional solver-progress display
147
+ - Export support for compiled Python, LP, and MPS artifacts where supported by the selected backend
148
+ - Light/dark themes and configurable editor font sizes, with settings saved between sessions
149
+
150
+ ### Launching the IME
151
+
152
+ Running PyOPL with no subcommand launches the IME:
153
+
154
+ ```sh
155
+ python -m pyopl
156
+ ```
157
+
158
+ If installed as a package, the `pyopl` console command can be used in place of `python -m pyopl`.
159
+
160
+ This opens the Rhetor IME window. You can open `.mod` model files and optional `.dat` data files, generate or revise formulations with GenAI assistance, edit them directly, solve them from the interface, export generated artifacts, and choose either Gurobi or SciPy/HiGHS from the Solve menu.
161
+
162
+ ### Sessions and Model Versions
163
+
164
+ Rhetor keeps an IME session history in a `.pyopl_session` file in the current working directory. Session entries preserve output history and associated model/data snapshots, so you can track how a model changes over an interactive, GenAI-assisted modelling session. From the session list, use the context menu to preview a saved snapshot, diff it against the current editors, restore it into the editors, rename the session entry, or delete it.
165
+
166
+
167
+
168
+ ## PyOPL CLI
169
+
170
+ PyOPL also includes a command-line interface for solving models, exporting generated artifacts, and using GenAI helpers from scripts or terminals.
171
+
172
+ Basic usage:
173
+
174
+ ```sh
175
+ python -m pyopl solve model.mod data.dat --solver highs --out json
176
+ ```
177
+
178
+ The CLI supports:
179
+
180
+ - `python -m pyopl solve <model.mod> [data.dat]` to compile and solve a model
181
+ - `--solver highs|gurobi` to choose the backend solver
182
+ - `--out json|py|lp|mps` to print results, export generated Python, or write LP/MPS solver files
183
+ - `--out-file <path>` to write output to a file
184
+
185
+ Examples:
186
+
187
+ ```sh
188
+ python -m pyopl solve knapsack.mod knapsack.dat --solver highs --out json
189
+ python -m pyopl solve knapsack.mod knapsack.dat --solver highs --out lp --out-file knapsack.lp
190
+ python -m pyopl solve knapsack.mod knapsack.dat --solver highs --out mps --out-file knapsack.mps
191
+ ```
192
+
193
+
194
+
195
+ ## PyOPL MCP
196
+
197
+ PyOPL MCP exposes core PyOPL compiler/solver functionality as MCP tools so external clients (IDEs, editors, automation) can call compile, solve, export, and compare operations over stdio-based MCP servers.
198
+
199
+ - **Purpose**: Provide programmatic access to compilation, solving, Python code export, and model equivalence comparison for OPL models using in-memory model/data strings.
200
+ - **Exposed tools**:
201
+ - **read_pyopl_grammar_tool**: Return the bundled grammar text.
202
+ - **solve_strings_tool**: Compile and solve a model from `model_text` and optional `data_text`.
203
+ - **export_py_strings_tool**: Compile `model_text` and optional `data_text` to generated Python source and return it as a string.
204
+ - **compare_model_strings_tool**: Compare two OPL models from strings using the same MILP equivalence engine as the IDE's Compare models workflow. Returns `status`, `equivalent`, `level`, `reason`, `proof_steps`, and `counterexample`.
205
+ - **Not exposed as MCP tools**: File-path helpers such as `solve_files_tool` and `export_py_files_tool` are retained in the Python module for trusted local/internal use, but are not registered as MCP tools by default for security reasons.
206
+ - **Solver mapping**: Default solver alias `highs` → SciPy/HiGHS; `gurobi` → Gurobi. See the tool `solver` parameter for selection.
207
+ - **Quick start (VS Code MCP example - .vscode/mcp.json)**:
208
+ ```json
209
+ {
210
+ "servers": {
211
+ "PyOPL MCP": {
212
+ "type": "stdio",
213
+ "command": "${workspaceFolder}/venv/bin/python",
214
+ "args": ["-m", "pyopl.pyopl_mcp"]
215
+ }
216
+ }
217
+ }
218
+ ```
219
+ - **Run locally**:
220
+ ```bash
221
+ python -m pyopl.pyopl_mcp
222
+ ```
223
+ - **Files**: Implementation and tool list in pyopl_mcp.py.
224
+ - **Notes**: Errors from the compiler/solver/comparison engine are propagated to the caller; generated Python code is returned (not written) so the client can persist it as desired.
225
+
226
+
227
+
228
+ ## User Guide
229
+
230
+ A comprehensive [User Guide](docs/PyOPL%20user%20guide.md) is available in the `docs` folder of the repository.
rhetor-1.9.2/RHETOR.md ADDED
@@ -0,0 +1,16 @@
1
+ # Rhetor
2
+
3
+ **GenAI-assisted Integrated Modelling Environment for Mixed-Integer Linear Programming**
4
+
5
+ Rhetor is an Integrated Modelling Environment for modelling and solving mixed-integer linear programming problems. Rhetor supports a subset of the Optimisation Programming Language (OPL) syntax and it is built on [``pyopl``](https://github.com/gwr3n/pyopl), a Python library for parsing and solving OPL-like mathematical programming models using Gurobi or Scipy (Highs). Rhetor integrates GenAI features to support and automate the modelling process.
6
+
7
+ ![Demo](https://github.com/gwr3n/rhetor/raw/main/sudoku.gif)
8
+
9
+ ## Learn More
10
+
11
+ - [Project Website](https://gwr3n.github.io/rhetor)
12
+ - [Project Repository](https://github.com/gwr3n/rhetor)
13
+ - [PyOPL Repository](https://github.com/gwr3n/pyopl)
14
+ - [PyOPL User Guide](https://github.com/gwr3n/rhetor/blob/main/docs/PyOPL%20user%20guide.md)
15
+ - [Examples Overview](https://github.com/gwr3n/rhetor/blob/main/docs/PyOPL%20examples%20overview.md)
16
+ - [Sample PyOPL Models](https://github.com/gwr3n/rhetor/tree/main/opl_models)
@@ -0,0 +1,21 @@
1
+ """PyOPL package initialization."""
2
+
3
+ # Ensure 'icon' is a package so importlib.resources works
4
+ import os
5
+ import sys
6
+
7
+ from .genai.pyopl_generative import generative_feedback, generative_solve
8
+ from .pyopl_core import solve
9
+
10
+ __version__ = "1.9.2"
11
+ __year__ = "2026"
12
+ __author__ = "Roberto Rossi"
13
+ __email__ = "robros@gmail.com"
14
+ __description__ = "A Python library for parsing and solving OPL-like mathematical programming models using multiple solvers."
15
+ __license__ = "MIT"
16
+ __url__ = "https://github.com/gwr3n/pyopl"
17
+
18
+ icon_dir = os.path.join(os.path.dirname(__file__), "icon")
19
+ if os.path.isdir(icon_dir) and icon_dir not in sys.path:
20
+ sys.path.append(icon_dir)
21
+ __all__ = ["solve", "generative_solve", "generative_feedback"]
@@ -0,0 +1,10 @@
1
+ from .pyopl_cli import main as cli_main
2
+
3
+
4
+ def main():
5
+ # Delegate to the CLI entrypoint which preserves the IDE-as-default behaviour
6
+ return cli_main()
7
+
8
+
9
+ if __name__ == "__main__":
10
+ main()
@@ -0,0 +1,307 @@
1
+ """FastMCP server exposing PyOPL solver functionality as MCP tools.
2
+
3
+ This module can be run as a standalone server or integrated into an existing
4
+ MCP setup. It exposes tools for:
5
+
6
+ - solving OPL models from files or strings
7
+ - exporting compiled Python code from OPL inputs
8
+
9
+ Example VS Code MCP config (.vscode/mcp.json):
10
+
11
+ {
12
+ "servers": {
13
+ "PyOPL MCP": {
14
+ "type": "stdio",
15
+ "command": "${workspaceFolder}/venv/bin/python",
16
+ "args": ["-m", "pyopl.pyopl_mcp"]
17
+ }
18
+ },
19
+ "inputs": []
20
+ }
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import tempfile
26
+ from importlib.resources import files
27
+ from pathlib import Path
28
+ from typing import Optional, TypeVar, Union
29
+
30
+ from mcp.server.fastmcp import FastMCP
31
+
32
+ from . import solve
33
+ from .milp_equivalence import EquivalenceResult, prove_equivalent
34
+ from .pyopl_core import OPLCompiler, linear_problem_from_opl
35
+
36
+ PathLike = Union[str, Path]
37
+ T = TypeVar("T")
38
+
39
+ DEFAULT_SOLVER = "highs"
40
+ DEFAULT_PROVIDER = "openai"
41
+ DEFAULT_MODEL = "gpt-5"
42
+
43
+ METHODS: list[tuple[str, str]] = [
44
+ ("SyntAGM", "pyopl_generative"),
45
+ ("Standard", "pyopl_standard"),
46
+ ("Chain of Thought", "pyopl_chain_of_thought"),
47
+ ("Tree of Thoughts", "pyopl_tree_of_thoughts"),
48
+ ("CAFA", "pyopl_cafa"),
49
+ ("Chain of Experts", "pyopl_chain_of_experts"),
50
+ ("Reflexion", "pyopl_reflexion"),
51
+ ]
52
+
53
+ mcp = FastMCP("PyOPL MCP")
54
+
55
+
56
+ def _read_text(path: Path) -> str:
57
+ return path.read_text(encoding="utf-8")
58
+
59
+
60
+ def _normalize_solver(solver: Optional[str]) -> str:
61
+ """Normalize solver names for compiler/backend compatibility."""
62
+ if not solver:
63
+ return "scipy"
64
+
65
+ normalized = solver.strip().lower()
66
+
67
+ solver_aliases = {
68
+ "highs": "scipy",
69
+ "scipy": "scipy",
70
+ "gurobi": "gurobi",
71
+ }
72
+ return solver_aliases.get(normalized, normalized)
73
+
74
+
75
+ def _solve_backend(solver: Optional[str]) -> str:
76
+ """Normalize solver names for solve() backend selection."""
77
+ return "gurobi" if _normalize_solver(solver) == "gurobi" else "scipy"
78
+
79
+
80
+ def _compile_to_python(
81
+ model_text: str,
82
+ data_text: Optional[str] = None,
83
+ solver: str = DEFAULT_SOLVER,
84
+ ) -> str:
85
+ """Compile OPL model/data text to Python code."""
86
+ compiler = OPLCompiler()
87
+ _, code_str, _ = compiler.compile_model(
88
+ model_text,
89
+ data_text,
90
+ solver=_normalize_solver(solver),
91
+ )
92
+ return code_str
93
+
94
+
95
+ def solve_from_files(
96
+ model_path: PathLike,
97
+ data_path: Optional[PathLike] = None,
98
+ solver: str = DEFAULT_SOLVER,
99
+ ) -> dict:
100
+ """Solve a model from filesystem paths."""
101
+ model_p = Path(model_path)
102
+ data_p = Path(data_path) if data_path else None
103
+ return solve(
104
+ str(model_p),
105
+ str(data_p) if data_p else None,
106
+ solver=_solve_backend(solver),
107
+ )
108
+
109
+
110
+ def solve_from_strings(
111
+ model_text: str,
112
+ data_text: Optional[str] = None,
113
+ solver: str = DEFAULT_SOLVER,
114
+ ) -> dict:
115
+ """Solve a model provided as strings."""
116
+ with tempfile.TemporaryDirectory() as tmp_dir:
117
+ model_file = Path(tmp_dir) / "model.mod"
118
+ model_file.write_text(model_text, encoding="utf-8")
119
+
120
+ data_file: Optional[Path] = None
121
+ if data_text is not None:
122
+ data_file = Path(tmp_dir) / "data.dat"
123
+ data_file.write_text(data_text, encoding="utf-8")
124
+
125
+ return solve_from_files(model_file, data_file, solver=solver)
126
+
127
+
128
+ def export_py_from_files(
129
+ model_path: PathLike,
130
+ data_path: Optional[PathLike] = None,
131
+ solver: str = DEFAULT_SOLVER,
132
+ ) -> str:
133
+ """Compile a model from files into Python code."""
134
+ model_code = _read_text(Path(model_path))
135
+ data_code = _read_text(Path(data_path)) if data_path else None
136
+ return _compile_to_python(model_code, data_code, solver=solver)
137
+
138
+
139
+ def export_py_from_strings(
140
+ model_text: str,
141
+ data_text: Optional[str] = None,
142
+ solver: str = DEFAULT_SOLVER,
143
+ ) -> str:
144
+ """Compile model/data text into Python code."""
145
+ return _compile_to_python(model_text, data_text, solver=solver)
146
+
147
+
148
+ def _equivalence_result_to_dict(result: EquivalenceResult) -> dict:
149
+ """Convert an equivalence result to an MCP-friendly JSON object."""
150
+ return {
151
+ "status": result.status,
152
+ "equivalent": result.equivalent,
153
+ "level": result.level,
154
+ "reason": result.reason,
155
+ "proof_steps": list(result.proof_steps),
156
+ "counterexample": result.counterexample,
157
+ }
158
+
159
+
160
+ def compare_model_strings(
161
+ left_model_text: str,
162
+ right_model_text: str,
163
+ left_data_text: Optional[str] = None,
164
+ right_data_text: Optional[str] = None,
165
+ ) -> dict:
166
+ """Compare two OPL models provided as strings for MILP equivalence."""
167
+ left_problem = linear_problem_from_opl(left_model_text, left_data_text)
168
+ right_problem = linear_problem_from_opl(right_model_text, right_data_text)
169
+ return _equivalence_result_to_dict(prove_equivalent(left_problem, right_problem))
170
+
171
+
172
+ def read_pyopl_grammar() -> str:
173
+ """Return the PyOPL grammar file contents as a UTF-8 string."""
174
+ return (files(__package__) / "grammars" / "PyOPL grammar.md").read_text(encoding="utf-8")
175
+
176
+
177
+ @mcp.tool()
178
+ def read_pyopl_grammar_tool() -> str:
179
+ """MCP tool returning the PyOPL grammar as a string."""
180
+ return read_pyopl_grammar()
181
+
182
+
183
+ # @mcp.tool()
184
+ def solve_files_tool(
185
+ model_path: str,
186
+ data_path: Optional[str] = None,
187
+ solver: str = DEFAULT_SOLVER,
188
+ ) -> dict:
189
+ """Solve an optimization model from `.mod` and optional `.dat` files.
190
+
191
+ Args:
192
+ model_path: Path to the OPL model file.
193
+ data_path: Optional path to the OPL data file.
194
+ solver: Solver name or alias. Supported values currently map to SciPy/HiGHS
195
+ or Gurobi depending on configuration.
196
+
197
+ Returns:
198
+ A dictionary containing solver outputs (status, objective value, variable
199
+ assignments, and any solver metadata) as produced by :func:`solve`.
200
+ """
201
+ return solve_from_files(model_path, data_path, solver)
202
+
203
+
204
+ # @mcp.tool()
205
+ def export_py_files_tool(
206
+ model_path: str,
207
+ data_path: Optional[str] = None,
208
+ solver: str = DEFAULT_SOLVER,
209
+ ) -> str:
210
+ """Compile an OPL model (and optional data) to Python source.
211
+
212
+ This tool reads the given `.mod` and optional `.dat` file paths,
213
+ compiles them to Python using the internal compiler, and returns the
214
+ generated Python code as a string. It does not write the generated
215
+ code to disk (the caller may do so).
216
+
217
+ Args:
218
+ model_path: Filesystem path to an OPL model file (.mod).
219
+ data_path: Optional path to an OPL data file (.dat).
220
+ solver: Solver name or alias influencing backend codegen.
221
+
222
+ Returns:
223
+ A string containing the compiled Python source.
224
+
225
+ Raises:
226
+ Exceptions from the compiler (syntax/semantic errors) or I/O
227
+ errors reading the input files are propagated.
228
+ """
229
+ return export_py_from_files(model_path, data_path, solver)
230
+
231
+
232
+ @mcp.tool()
233
+ def solve_strings_tool(
234
+ model_text: str,
235
+ data_text: Optional[str] = None,
236
+ solver: str = DEFAULT_SOLVER,
237
+ ) -> dict:
238
+ """Solve an OPL model and optional data provided as strings.
239
+
240
+ Args:
241
+ model_text: OPL model source code as a string.
242
+ data_text: Optional OPL data file contents as a string.
243
+ solver: Solver name or alias. This maps to the internal solver
244
+ backends (for example, ``highs`` -> SciPy/HiGHS or ``gurobi``).
245
+
246
+ Returns:
247
+ A dictionary containing solver outputs (status, objective value,
248
+ variable assignments, and any solver metadata) as produced by
249
+ :func:`solve`.
250
+ """
251
+ return solve_from_strings(model_text, data_text, solver)
252
+
253
+
254
+ @mcp.tool()
255
+ def export_py_strings_tool(
256
+ model_text: str,
257
+ data_text: Optional[str] = None,
258
+ solver: str = DEFAULT_SOLVER,
259
+ ) -> str:
260
+ """Compile OPL model and optional data provided as strings to Python.
261
+
262
+ Useful for programmatic use where model/data are held in memory. The
263
+ function returns the compiled Python source as a string.
264
+
265
+ Args:
266
+ model_text: OPL model source as a string.
267
+ data_text: Optional OPL data contents as a string.
268
+ solver: Solver name or alias affecting code generation.
269
+
270
+ Returns:
271
+ A string with the compiled Python source.
272
+
273
+ Raises:
274
+ Compilation errors or other exceptions raised by the internal
275
+ compiler are propagated to the caller.
276
+ """
277
+ return export_py_from_strings(model_text, data_text, solver)
278
+
279
+
280
+ @mcp.tool()
281
+ def compare_model_strings_tool(
282
+ left_model_text: str,
283
+ right_model_text: str,
284
+ left_data_text: Optional[str] = None,
285
+ right_data_text: Optional[str] = None,
286
+ ) -> dict:
287
+ """Compare two OPL models provided as strings for MILP equivalence.
288
+
289
+ This exposes the same comparison engine used by the GUI's Compare models
290
+ workflow, but avoids filesystem paths by accepting model/data contents
291
+ directly.
292
+
293
+ Args:
294
+ left_model_text: OPL source for the left model.
295
+ right_model_text: OPL source for the right model.
296
+ left_data_text: Optional OPL data contents for the left model.
297
+ right_data_text: Optional OPL data contents for the right model.
298
+
299
+ Returns:
300
+ A dictionary containing status, equivalent, level, reason, proof_steps,
301
+ and counterexample fields.
302
+ """
303
+ return compare_model_strings(left_model_text, right_model_text, left_data_text, right_data_text)
304
+
305
+
306
+ if __name__ == "__main__":
307
+ mcp.run()