gmpl-tex 0.1.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.
- gmpl_tex-0.1.0/PKG-INFO +68 -0
- gmpl_tex-0.1.0/README.md +60 -0
- gmpl_tex-0.1.0/examples/model.mod +44 -0
- gmpl_tex-0.1.0/pyproject.toml +22 -0
- gmpl_tex-0.1.0/src/gmpltex/DataModels/RenderModels.py +65 -0
- gmpl_tex-0.1.0/src/gmpltex/DataModels/__init__.py +0 -0
- gmpl_tex-0.1.0/src/gmpltex/Modules/LatexAssembler.py +144 -0
- gmpl_tex-0.1.0/src/gmpltex/Modules/__init__.py +0 -0
- gmpl_tex-0.1.0/src/gmpltex/Transformers/LatexTransformer.py +565 -0
- gmpl_tex-0.1.0/src/gmpltex/Transformers/LookupTableBuilder.py +59 -0
- gmpl_tex-0.1.0/src/gmpltex/Transformers/__init__.py +0 -0
- gmpl_tex-0.1.0/src/gmpltex/__init__.py +0 -0
- gmpl_tex-0.1.0/src/gmpltex/__main__.py +4 -0
- gmpl_tex-0.1.0/src/gmpltex/grammar.lark +346 -0
- gmpl_tex-0.1.0/src/gmpltex/main.py +134 -0
gmpl_tex-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gmpl-tex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert GMPL (GNU MathProg) models into LaTeX.
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: lark>=1.1
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# gmpl-tex
|
|
10
|
+
|
|
11
|
+
Convert a subset of **GMPL** (GNU MathProg) optimization models into **LaTeX**, so
|
|
12
|
+
the constraints, sets, parameters, variables and objectives you wrote for a solver
|
|
13
|
+
can go straight into a paper with readable, renamed symbols.
|
|
14
|
+
|
|
15
|
+
The tool works in two phases so you stay in control of the notation:
|
|
16
|
+
|
|
17
|
+
1. **Lookup-table generation** - parse `model.mod` into a JSON table listing every
|
|
18
|
+
set, parameter, variable, constraint and objective name. Each name maps to a
|
|
19
|
+
label you can edit.
|
|
20
|
+
2. **LaTeX generation** - render the model to LaTeX, substituting your edited
|
|
21
|
+
labels from the JSON table.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
No Python project setup required for users - pick whichever you have:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# with uv (no install, runs in a throwaway environment)
|
|
29
|
+
uvx --from git+https://github.com/<you>/gmpl-tex gmpl-tex --help
|
|
30
|
+
|
|
31
|
+
# with pipx (installs the gmpl-tex command onto your PATH)
|
|
32
|
+
pipx install git+https://github.com/<you>/gmpl-tex
|
|
33
|
+
|
|
34
|
+
# with plain pip, into a virtual environment
|
|
35
|
+
pip install git+https://github.com/<you>/gmpl-tex
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
gmpl-tex model.mod [lookup.json] --json
|
|
42
|
+
gmpl-tex model.mod [lookup.json] [output.tex] --latex
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A full run, start to finish:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 1. generate the editable lookup table (writes model.json)
|
|
49
|
+
gmpl-tex model.mod --json
|
|
50
|
+
|
|
51
|
+
# 2. open model.json and edit the label on the right-hand side of each entry,
|
|
52
|
+
# e.g. "finishTime": "fTime"
|
|
53
|
+
|
|
54
|
+
# 3. render LaTeX using your edited labels (writes model.tex)
|
|
55
|
+
gmpl-tex model.mod model.json --latex
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you skip step 1 and run `gmpl-tex model.mod --latex` directly, a default table
|
|
59
|
+
is created automatically (labels equal to the raw names) and used. If a
|
|
60
|
+
`model.json` already exists next to the model, it is reused as-is and never
|
|
61
|
+
overwritten.
|
|
62
|
+
|
|
63
|
+
An example model is included under [`examples/model.mod`](examples/model.mod).
|
|
64
|
+
|
|
65
|
+
## Requirements
|
|
66
|
+
|
|
67
|
+
- Python 3.10 or newer
|
|
68
|
+
- [`lark`](https://github.com/lark-parser/lark) (installed automatically)
|
gmpl_tex-0.1.0/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# gmpl-tex
|
|
2
|
+
|
|
3
|
+
Convert a subset of **GMPL** (GNU MathProg) optimization models into **LaTeX**, so
|
|
4
|
+
the constraints, sets, parameters, variables and objectives you wrote for a solver
|
|
5
|
+
can go straight into a paper with readable, renamed symbols.
|
|
6
|
+
|
|
7
|
+
The tool works in two phases so you stay in control of the notation:
|
|
8
|
+
|
|
9
|
+
1. **Lookup-table generation** - parse `model.mod` into a JSON table listing every
|
|
10
|
+
set, parameter, variable, constraint and objective name. Each name maps to a
|
|
11
|
+
label you can edit.
|
|
12
|
+
2. **LaTeX generation** - render the model to LaTeX, substituting your edited
|
|
13
|
+
labels from the JSON table.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
No Python project setup required for users - pick whichever you have:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# with uv (no install, runs in a throwaway environment)
|
|
21
|
+
uvx --from git+https://github.com/<you>/gmpl-tex gmpl-tex --help
|
|
22
|
+
|
|
23
|
+
# with pipx (installs the gmpl-tex command onto your PATH)
|
|
24
|
+
pipx install git+https://github.com/<you>/gmpl-tex
|
|
25
|
+
|
|
26
|
+
# with plain pip, into a virtual environment
|
|
27
|
+
pip install git+https://github.com/<you>/gmpl-tex
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
gmpl-tex model.mod [lookup.json] --json
|
|
34
|
+
gmpl-tex model.mod [lookup.json] [output.tex] --latex
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A full run, start to finish:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 1. generate the editable lookup table (writes model.json)
|
|
41
|
+
gmpl-tex model.mod --json
|
|
42
|
+
|
|
43
|
+
# 2. open model.json and edit the label on the right-hand side of each entry,
|
|
44
|
+
# e.g. "finishTime": "fTime"
|
|
45
|
+
|
|
46
|
+
# 3. render LaTeX using your edited labels (writes model.tex)
|
|
47
|
+
gmpl-tex model.mod model.json --latex
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If you skip step 1 and run `gmpl-tex model.mod --latex` directly, a default table
|
|
51
|
+
is created automatically (labels equal to the raw names) and used. If a
|
|
52
|
+
`model.json` already exists next to the model, it is reused as-is and never
|
|
53
|
+
overwritten.
|
|
54
|
+
|
|
55
|
+
An example model is included under [`examples/model.mod`](examples/model.mod).
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
- Python 3.10 or newer
|
|
60
|
+
- [`lark`](https://github.com/lark-parser/lark) (installed automatically)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# A TRANSPORTATION PROBLEM
|
|
2
|
+
#
|
|
3
|
+
# This problem finds a least cost shipping schedule that meets
|
|
4
|
+
# requirements at markets and supplies at factories.
|
|
5
|
+
#
|
|
6
|
+
# References:
|
|
7
|
+
# Dantzig G B, "Linear Programming and Extensions."
|
|
8
|
+
# Princeton University Press, Princeton, New Jersey, 1963,
|
|
9
|
+
# Chapter 3-3.
|
|
10
|
+
set I;
|
|
11
|
+
/* canning plants */
|
|
12
|
+
set J;
|
|
13
|
+
/* markets */
|
|
14
|
+
param a{i in I};
|
|
15
|
+
/* capacity of plant i in cases */
|
|
16
|
+
param b{j in J};
|
|
17
|
+
/* demand at market j in cases */
|
|
18
|
+
param d{i in I, j in J};
|
|
19
|
+
/* distance in thousands of miles */
|
|
20
|
+
param f;
|
|
21
|
+
/* freight in dollars per case per thousand miles */
|
|
22
|
+
param c{i in I, j in J} := f * d[i,j] / 1000;
|
|
23
|
+
/* transport cost in thousands of dollars per case */
|
|
24
|
+
var x{i in I, j in J} >= 0;
|
|
25
|
+
/* shipment quantities in cases */
|
|
26
|
+
minimize cost: sum{i in I, j in J} c[i,j] * x[i,j];
|
|
27
|
+
/* total transportation costs in thousands of dollars */
|
|
28
|
+
s.t. supply{i in I}: sum{j in J} x[i,j] <= a[i];
|
|
29
|
+
/* observe supply limit at plant i */
|
|
30
|
+
s.t. demand{j in J}: sum{i in I} x[i,j] >= b[j];
|
|
31
|
+
/* satisfy demand at market j */
|
|
32
|
+
data;
|
|
33
|
+
set I := Seattle San-Diego;
|
|
34
|
+
set J := New-York Chicago Topeka;
|
|
35
|
+
param a := Seattle 350
|
|
36
|
+
San-Diego 600;
|
|
37
|
+
param b := New-York 325
|
|
38
|
+
Chicago 300
|
|
39
|
+
Topeka 275;
|
|
40
|
+
param d : New-York Chicago Topeka :=
|
|
41
|
+
Seattle 2.5 1.7 1.8
|
|
42
|
+
San-Diego 2.5 1.8 1.4 ;
|
|
43
|
+
param f := 90;
|
|
44
|
+
end;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gmpl-tex"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Convert GMPL (GNU MathProg) models into LaTeX."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = ["lark>=1.1"]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
gmpl-tex = "gmpltex.main:main"
|
|
15
|
+
|
|
16
|
+
[tool.hatch.build.targets.wheel]
|
|
17
|
+
packages = ["src/gmpltex"]
|
|
18
|
+
|
|
19
|
+
# Guarantee the grammar travels inside the wheel even if
|
|
20
|
+
# the default file discovery ever changes.
|
|
21
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
22
|
+
"src/gmpltex/grammar.lark" = "gmpltex/grammar.lark"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from abc import ABC
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Section(ABC):
|
|
7
|
+
"""Base for every renderable declaration. `line` is the 1-based source line,
|
|
8
|
+
used for the `% Line N` comments in the generated LaTeX."""
|
|
9
|
+
line: int | None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class AttributedStatement(Section):
|
|
14
|
+
name: str
|
|
15
|
+
domain: str | None
|
|
16
|
+
attributes: list[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Set(AttributedStatement):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Parameter(AttributedStatement):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Variable(AttributedStatement):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Constraint(Section):
|
|
35
|
+
"""A relation rendered as a labelled equation.
|
|
36
|
+
|
|
37
|
+
single bound: lhs op_left rhs
|
|
38
|
+
double bound: lhs op_left middle op_right rhs (e.g. lb <= e <= ub)
|
|
39
|
+
|
|
40
|
+
`middle` / `op_right` stay None for a single-sided constraint.
|
|
41
|
+
"""
|
|
42
|
+
name: str
|
|
43
|
+
domain: str | None
|
|
44
|
+
lhs: str
|
|
45
|
+
op_left: str
|
|
46
|
+
rhs: str
|
|
47
|
+
middle: str | None = None
|
|
48
|
+
op_right: str | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class Objective(Section):
|
|
53
|
+
verb: str
|
|
54
|
+
name: str
|
|
55
|
+
domain: str | None
|
|
56
|
+
formula: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class Model:
|
|
61
|
+
sets: list[Set]
|
|
62
|
+
parameters: list[Parameter]
|
|
63
|
+
variables: list[Variable]
|
|
64
|
+
constraints: list[Constraint]
|
|
65
|
+
objectives: list[Objective]
|
|
File without changes
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from ..DataModels.RenderModels import *
|
|
2
|
+
|
|
3
|
+
_PREAMBLE = """\
|
|
4
|
+
\\documentclass{article}
|
|
5
|
+
\\usepackage[utf8]{inputenc}
|
|
6
|
+
\\usepackage{amsmath}
|
|
7
|
+
\\usepackage{amssymb}
|
|
8
|
+
\\usepackage{xcolor}
|
|
9
|
+
|
|
10
|
+
\\begin{document}
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def _wrap_into_latex_skeleton(body: str) -> str:
|
|
14
|
+
"""Wrap math blocks in a compilable LaTeX document skeleton."""
|
|
15
|
+
return _PREAMBLE + body + "\n\\end{document}"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def assemble_latex(model: Model, standalone: bool = True) -> str:
|
|
19
|
+
"""Render a whole model to LaTeX.
|
|
20
|
+
|
|
21
|
+
With ``standalone`` (the default) the output is a compilable document --
|
|
22
|
+
documentclass, packages and ``document`` environment included. Pass
|
|
23
|
+
``standalone=False`` to emit only the math blocks, e.g. for \\input-ing into
|
|
24
|
+
an existing paper.
|
|
25
|
+
"""
|
|
26
|
+
sections: list[str] = []
|
|
27
|
+
|
|
28
|
+
sections.append(f"\\section{{Input data}}\n")
|
|
29
|
+
sections.append(f"\\subsection{{Sets}}\n")
|
|
30
|
+
sections.append(set_param_block(model.sets)) # type: ignore
|
|
31
|
+
|
|
32
|
+
sections.append(f"\\subsection{{Parameters}}\n")
|
|
33
|
+
sections.append(set_param_block(model.parameters)) # type: ignore
|
|
34
|
+
|
|
35
|
+
sections.append(f"\\section{{Model}}\n")
|
|
36
|
+
sections.append(f"\\subsection{{Variables}}\n")
|
|
37
|
+
sections.append(variables_block(model.variables))
|
|
38
|
+
|
|
39
|
+
sections.append(f"\\subsection{{Constraints}}\n")
|
|
40
|
+
sections.append(constraints_block(model.constraints))
|
|
41
|
+
|
|
42
|
+
sections.append(f"\\subsection{{Objectives}}\n")
|
|
43
|
+
sections.append(objectives_block(model.objectives))
|
|
44
|
+
|
|
45
|
+
body = "\n".join(sections)
|
|
46
|
+
|
|
47
|
+
return _wrap_into_latex_skeleton(body) if standalone else body
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def itemize_block(items: list[AttributedStatement]) -> str:
|
|
51
|
+
if not items:
|
|
52
|
+
return ""
|
|
53
|
+
|
|
54
|
+
list_items = "\n".join(
|
|
55
|
+
f"% Line {item.line}\n"
|
|
56
|
+
"\\item $" + item.name
|
|
57
|
+
+ ", \\quad ".join(
|
|
58
|
+
[f"{attr}" for attr in item.attributes]
|
|
59
|
+
+ ([item.domain] if item.domain else [])
|
|
60
|
+
) + "$"
|
|
61
|
+
for item in items
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return f"""\
|
|
65
|
+
\\begin{{itemize}}
|
|
66
|
+
{list_items}
|
|
67
|
+
\\end{{itemize}}
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def math_block(items: list[AttributedStatement]) -> str:
|
|
72
|
+
"""Render set or parameter declarations, one display-math block each.
|
|
73
|
+
|
|
74
|
+
Both share the same shape (name, optional attributes, optional domain),
|
|
75
|
+
so sets and parameters go through here.
|
|
76
|
+
"""
|
|
77
|
+
blocks = []
|
|
78
|
+
for item in items:
|
|
79
|
+
attrs = [a for a in item.attributes if a]
|
|
80
|
+
content = ", \n\\quad ".join(f"{item.name} {attr}" for attr in attrs) if attrs else item.name
|
|
81
|
+
if item.domain:
|
|
82
|
+
content += f",\n\\quad {item.domain}"
|
|
83
|
+
blocks.append(f"""\
|
|
84
|
+
% Line {item.line}
|
|
85
|
+
\\[
|
|
86
|
+
{content}
|
|
87
|
+
\\]
|
|
88
|
+
""")
|
|
89
|
+
return "\n".join(blocks)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def set_param_block(items: list[AttributedStatement]) -> str:
|
|
93
|
+
|
|
94
|
+
if not items:
|
|
95
|
+
return ""
|
|
96
|
+
|
|
97
|
+
blocks = []
|
|
98
|
+
declarations = [i for i in items if not any(":=" in a for a in (i.attributes or []))]
|
|
99
|
+
definitions = [i for i in items if any(":=" in a for a in (i.attributes or []))]
|
|
100
|
+
|
|
101
|
+
if declarations:
|
|
102
|
+
blocks.append(itemize_block(declarations))
|
|
103
|
+
|
|
104
|
+
if definitions:
|
|
105
|
+
blocks.append(math_block(definitions))
|
|
106
|
+
|
|
107
|
+
return "\n".join(blocks)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def variables_block(variables: list[Variable]) -> str:
|
|
111
|
+
"""Render all variable declarations as a single itemize list."""
|
|
112
|
+
return itemize_block(variables) # type: ignore
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def constraints_block(constraints: list[Constraint]) -> str:
|
|
116
|
+
"""Render each constraint as a labelled equation."""
|
|
117
|
+
blocks = []
|
|
118
|
+
for c in constraints:
|
|
119
|
+
blocks.append(f"""\
|
|
120
|
+
% Line {c.line}
|
|
121
|
+
\\begin{{equation}}
|
|
122
|
+
\\label{{Eq:{c.name}}}
|
|
123
|
+
{c.lhs}
|
|
124
|
+
{c.op_left}
|
|
125
|
+
{c.middle + "\n" if c.middle else ""} {c.op_right if c.op_right else ""} {c.rhs}
|
|
126
|
+
{c.domain + "\n" if c.domain else ""}\\end{{equation}}
|
|
127
|
+
""")
|
|
128
|
+
return "\n".join(blocks)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def objectives_block(objectives: list[Objective]) -> str:
|
|
132
|
+
"""Render each objective as a labelled equation (formula → sense)."""
|
|
133
|
+
blocks = []
|
|
134
|
+
for obj in objectives:
|
|
135
|
+
blocks.append(f"""\
|
|
136
|
+
% Line {obj.line}
|
|
137
|
+
\\begin{{equation}}
|
|
138
|
+
\\label{{Eq:{obj.name}}}
|
|
139
|
+
{obj.formula}
|
|
140
|
+
\\to
|
|
141
|
+
{obj.verb}
|
|
142
|
+
\\end{{equation}}
|
|
143
|
+
""")
|
|
144
|
+
return "\n".join(blocks)
|
|
File without changes
|