refactor: Pipeline-Stufen in Ordner mit ihren Prompts

Im Wurzelverzeichnis liegt nur noch TextToModel.py (Ablauf und CLI).
Jede Stufe liegt mit Code und Prompt in einem eigenen Ordner: core,
understanding, modeling, checks, solving, reporting, data, benchmark;
Tests unter tests/, Aufgaben unter tasks/. understand_task,
text_to_model/revise_model und review_assumptions/needs_work/select_best
sind aus TextToModel.py zu ihren Prompts gewandert, inhaltlich
unveraendert. Prompt-Kennung des Benchmarks bleibt 93744636095a.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-17 12:11:51 +02:00
commit 7459690bf5

View file

@ -1,194 +0,0 @@
"""Eigenständiges, dokumentiertes Solver-Skript aus einem PipelineResult bauen (für --code und Reports).
Aufbau des Skripts:
#!/usr/bin/env python3
r\"\"\" Aufgabe / Mathematisches Modell / Algorithmus \"\"\"
<generierter Code: Imports, Daten, Funktionen, main()>
<report()-Hilfsfunktion>
if __name__ == "__main__":
main()
Das JSON-Ergebnis enthält weiterhin nur den rohen generierten Code (solver_code.code).
"""
import re
import textwrap
from model_schema import PipelineResult, ProblemClass, TargetStack
from solver_stage import assemble_script
WIDTH = 88
# Verfahren je Solver-Stack (feste Texte, nicht vom LLM erzeugt)
ALGORITHMS = {
TargetStack.HIGHS: (
"HiGHS (highspy)",
"Lineare Relaxationen werden mit dem dualen revidierten Simplex-Verfahren (alternativ "
"Innere-Punkte-Verfahren) gelöst. Ganzzahlige und binäre Variablen behandelt HiGHS mit "
"Branch-and-Cut: Das Problem wird schrittweise in Teilprobleme zerlegt, deren LP-Relaxation "
"Schranken liefert; Schnittebenen und Heuristiken verkleinern den Suchbaum. Der Status "
"'optimal' bedeutet, dass die Optimalitätslücke (im Rahmen der Toleranz) geschlossen ist.",
),
TargetStack.OR_TOOLS_CPSAT: (
"Google OR-Tools CP-SAT",
"CP-SAT kombiniert Constraint-Propagation mit einem SAT-Solver (Lazy Clause Generation, "
"konfliktgesteuertes Klausellernen) und LP-Relaxationen; mehrere Suchstrategien laufen parallel. "
"Logische Bedingungen (Implikationen, 'nicht gemeinsam', Reihenfolgen) werden direkt und exakt "
"verarbeitet. Der Status 'optimal' bedeutet, dass die Optimalität bewiesen wurde. CP-SAT arbeitet "
"nur mit ganzzahligen Werten.",
),
TargetStack.CVXPY: (
"CVXPY",
"CVXPY prüft, ob das Modell den Regeln der disziplinierten konvexen Programmierung (DCP) genügt, "
"formt es in ein konisches Standardproblem um und übergibt es einem passenden Solver – für "
"Kegel- und quadratische Probleme typischerweise einem Innere-Punkte-Verfahren (z. B. Clarabel) "
"oder OSQP. Bei konvexen Problemen ist jedes lokale Optimum zugleich global optimal.",
),
TargetStack.SCIP: (
"SCIP (pyscipopt)",
"SCIP löst gemischt-ganzzahlige nichtlineare Probleme mit Branch-and-Bound bzw. Branch-and-Cut: "
"Ganzzahlige Variablen werden verzweigt, quadratische Terme durch lineare Unterschätzer (Schnittebenen, "
"bei nichtkonvexen Termen räumliches Verzweigen) relaxiert; Propagation, Presolve und Heuristiken "
"verkleinern den Suchbaum. Der Status 'optimal' bedeutet, dass die Optimalitätslücke im Rahmen der "
"Toleranz geschlossen ist. Da SCIP nur lineare Zielfunktionen verarbeitet, wird ein quadratisches Ziel "
"über eine Hilfsvariable (Epigraph-Formulierung) abgebildet.",
),
TargetStack.IPOPT: (
"Ipopt (cyipopt)",
"Ipopt ist ein Innere-Punkte-Verfahren für glatte nichtlineare Optimierung: Es folgt einer Folge von "
"Barriereproblemen, nutzt Ableitungen (hier numerisch bestimmt) und konvergiert gegen einen Punkt, der "
"die Karush-Kuhn-Tucker-Bedingungen erfüllt. Das ist ein LOKALES Optimum – bei konvexen Problemen "
"zugleich das globale, sonst nicht. Mehrere Startpunkte verringern das Risiko, in einem schlechten "
"lokalen Punkt zu landen; ganzzahlige Variablen kann Ipopt nicht behandeln.",
),
TargetStack.NETWORKX: (
"NetworkX (Dijkstra)",
"Der Dijkstra-Algorithmus markiert Knoten schrittweise: Er nimmt stets den unmarkierten Knoten mit "
"der kleinsten vorläufigen Entfernung, erklärt dessen Marke für endgültig und verbessert damit die "
"Marken seiner Nachbarn. Da jede Marke beim Endgültigwerden bereits minimal ist, steht das Ergebnis "
"nach einem Durchlauf fest – ohne Suchbaum und ohne Relaxation. Voraussetzung sind nicht negative "
"Bogengewichte: bei negativen Gewichten liefert das Verfahren stillschweigend ein falsches Ergebnis.",
),
TargetStack.SCIPY: (
"SciPy scipy.optimize.linprog (method='highs')",
"linprog nutzt die HiGHS-Verfahren für lineare Programme (duales Simplex- oder "
"Innere-Punkte-Verfahren). Das Optimum eines LP liegt an einer Ecke des zulässigen Polyeders "
"und wird exakt (im Rahmen der Toleranz) gefunden. Ganzzahligkeit wird nicht unterstützt.",
),
}
# Warum die Problemklasse zum Verfahren passt (feste Texte)
CLASS_FIT = {
ProblemClass.LP: "Zielfunktion und Restriktionen sind linear, alle Variablen kontinuierlich – ein LP, "
"für das Simplex-/Innere-Punkte-Verfahren beweisbar optimale Lösungen liefern.",
ProblemClass.MILP: "Zielfunktion und Restriktionen sind linear, aber einige Variablen sind ganzzahlig "
"oder binär – ein MILP, für das Branch-and-Cut das Standardverfahren ist.",
ProblemClass.CP: "Das Problem besteht aus diskreten Entscheidungen mit logischen Verknüpfungen – "
"typisch für Constraint Programming, das solche Bedingungen direkt propagiert.",
ProblemClass.QP: "Die Zielfunktion ist quadratisch und konvex (positiv semidefinite Matrix), die "
"Restriktionen sind linear – ein konvexes QP mit global optimaler Lösung.",
ProblemClass.MIQP: "Das Problem verbindet quadratische Terme (z. B. Varianz/Risiko) mit ganzzahligen oder "
"binären Entscheidungen (z. B. Auswahl- oder Kardinalitätsbedingungen) – ein MIQP, das "
"weder reine Kegelsolver (keine Ganzzahligkeit) noch CP-SAT (keine kontinuierlichen "
"Variablen) abdecken, wohl aber SCIPs Branch-and-Cut für nichtlineare Probleme.",
ProblemClass.NLP: "Zielfunktion oder Restriktionen sind glatt nichtlinear und alle Variablen stetig – ein NLP, "
"für das Innere-Punkte-Verfahren (Ipopt) effizient einen KKT-Punkt liefern; globale Optimalität "
"ist nur bei konvexen Problemen garantiert.",
ProblemClass.SHORTEST_PATH: "Gesucht ist eine Folge von Entscheidungen, die sich als Weg durch einen Graphen "
"lesen lässt: Knoten sind Zustände (Zeitpunkte, Orte), Bögen die Übergänge mit ihren "
"Kosten. Für solche Probleme gibt es mit dem Dijkstra-Algorithmus ein exaktes "
"Verfahren, das ohne Ganzzahligkeitsbedingungen auskommt.",
ProblemClass.SOCP: "Das Problem enthält Normen bzw. Wurzeln quadratischer Formen – ein konvexes "
"Kegelproblem (SOCP), das Innere-Punkte-Verfahren effizient und global optimal lösen.",
}
def _plain(text) -> str:
"""Inline-Mathe-Begrenzer \\( \\) \\[ \\] entfernen – im Docstring stehen Formeln als Klartext."""
return re.sub(r"\\[()\[\]]", "", str(text))
def _wrap(text: str, indent: str = "") -> str:
paragraphs = [p for p in _plain(text).strip().split("\n") if p.strip()] or [""]
return "\n".join(
textwrap.fill(p.strip(), width=WIDTH, initial_indent=indent, subsequent_indent=indent) for p in paragraphs
)
def _heading(title: str) -> str:
return f"{title}\n{'=' * len(title)}"
def build_header(result: PipelineResult) -> str:
spec = result.specification
parts = []
if result.task.strip():
parts += [_heading("AUFGABE"), result.task.strip()]
model = [_heading("MATHEMATISCHES MODELL"),
f"Problemklasse: {spec.problem_class.value}",
"",
_wrap(spec.executive_summary)]
if spec.indices:
model += ["", "Mengen und Indizes:"] + [f" - {_plain(i)}" for i in spec.indices]
if spec.parameters:
model += ["", "Parameter:"]
for p in spec.parameters:
model.append(_wrap(f"- {p.symbol} [{p.dimension}]: {p.description}", " "))
if p.extracted_values:
model.append(_wrap(f"Werte: {p.extracted_values}", " "))
model += ["", "Entscheidungsvariablen:"]
for v in spec.variables:
model.append(_wrap(f"- {v.name} ({v.var_type.value}, {v.domain}): {v.description}", " "))
model += ["", f"Zielfunktion ({spec.objective.sense.value}):",
f" {_plain(spec.objective.latex_formula)}",
_wrap(spec.objective.description, " "),
"", "Restriktionen:"]
for n, c in enumerate(spec.constraints, 1):
soft = "" if c.is_hard_constraint else " (weich)"
model += [f" ({n}) {c.identifier}{soft}:", f" {_plain(c.latex_formula)}", _wrap(c.description, " ")]
if spec.open_assumptions:
model += ["", "Annahmen (Aufgabentext war hier nicht eindeutig):"]
for a in spec.open_assumptions:
model.append(_wrap(f"- {a.unclear_point}: {a.assumption} (Auswirkung: {a.impact})", " "))
if result.solver_code and result.solver_code.data_assumptions:
model += ["", "Angenommene Daten (nicht im Aufgabentext enthalten):"]
model += [_wrap(f"- {d}", " ") for d in result.solver_code.data_assumptions]
model += ["", "Formeln in LaTeX-Schreibweise."]
parts.append("\n".join(model))
name, method = ALGORITHMS[spec.target_stack]
parts.append("\n".join([
_heading("ALGORITHMUS"),
f"Solver: {name}",
"",
"Verfahren:",
_wrap(method, " "),
"",
"Warum dieses Verfahren hier passt:",
_wrap(CLASS_FIT[spec.problem_class], " "),
_wrap(spec.routing_rationale, " "),
]))
if result.solver_run:
run = result.solver_run
parts.append("\n".join([
_heading("ERGEBNIS BEI DER ERZEUGUNG"),
f"Status: {run.status.value}, Zielwert: {run.objective_value}",
"Aufruf: python3 <diese_datei>.py",
]))
return "\n\n".join(parts)
def _docstring(text: str) -> str:
# Roh-String, damit LaTeX-Backslashes keine Escape-Warnungen erzeugen
text = text.replace('"""', "'''").rstrip()
if text.endswith("\\"):
text += " "
return f'r"""\n{text}\n"""\n'
def build_solver_script(result: PipelineResult) -> str:
if not result.solver_code:
raise ValueError("Ergebnis enthält keinen Solver-Code (Lauf mit --no-solve?)")
return "#!/usr/bin/env python3\n" + _docstring(build_header(result)) + "\n" + assemble_script(result.solver_code.code)