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.
@@ -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)
@@ -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