PhysLean/scripts/MetaPrograms/informal.lean
2024-12-05 06:49:50 +00:00

493 lines
19 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2024 Joseph Tooby-Smith. All rights reserved.
Released under Apache 2.0 license.
Authors: Joseph Tooby-Smith
-/
import Batteries.Lean.HashSet
import Lean
import Mathlib.Lean.Expr.Basic
import Mathlib.Lean.CoreM
import HepLean.Meta.Informal.Post
import ImportGraph.RequiredModules
/-!
# Extracting information about informal definitions and lemmas
To make the dot file for the dependency graph run:
- lake exe informal mkDot
- dot -Tsvg -o ./Docs/graph.svg ./Docs/InformalDot.dot
- or dot -Tpng -Gdpi=300 -o ./Docs/graph.png ./Docs/InformalDot.dot
-/
open Lean System Meta
def getConst (imp : Import) : IO (Array (Import × ConstantInfo)) := do
let mFile ← findOLean imp.module
let (modData, _) ← readModuleData mFile
pure (modData.constants.map (fun c => (imp, c)))
def getLineNumber (c : Name) : MetaM Nat := do
match ← findDeclarationRanges? c with
| some decl => pure decl.range.pos.line
| none => panic! s!"{c} is a declaration without position"
def getModule (c : Name) : MetaM Name := do
match Lean.Environment.getModuleFor? (← getEnv) c with
| some mod => pure mod
| none => panic! s!"{c} is a declaration without position"
def getConstInfo (n : Name) : MetaM ConstantInfo := do
match (← getEnv).find? n with
| some c => pure c
| none => panic! s!"{n} is not a constant"
/-- Gets the docstring from a name, if it exists, otherwise the string "No docstring."-/
def getDocString (n : Name) : MetaM String := do
match ← Lean.findDocString? (← getEnv) n with
| some doc => pure doc
| none => pure "No docstring."
def depToString (d : Name) : MetaM String := do
let lineNo ← getLineNumber d
let mod ← getModule d
pure s!" * {d}: ./{mod.toString.replace "." "/" ++ ".lean"}:{lineNo}"
def depToWebString (d : Name) : MetaM String := do
let lineNo ← getLineNumber d
let mod ← getModule d
let webPath := "https://github.com/HEPLean/HepLean/blob/master/"++
(mod.toString.replace "." "/") ++ ".lean"
pure s!" * [{d}]({webPath}#L{lineNo})"
unsafe def informalDependencies (c : ConstantInfo) : MetaM (Array Name) := do
if Informal.isInformalLemma c then
let informal ← Informal.constantInfoToInformalLemma c
pure informal.dependencies.toArray
else if Informal.isInformalDef c then
let informal ← Informal.constantInfoToInformalDefinition c
pure informal.dependencies.toArray
else
pure #[]
unsafe def informalLemmaToString (c : Import × ConstantInfo) : MetaM String := do
let lineNo ← getLineNumber c.2.name
let informalLemma ← Informal.constantInfoToInformalLemma c.2
let dep ← informalLemma.dependencies.mapM fun d => depToString d
pure s!"
Informal lemma: {informalLemma.name}
- ./{c.1.module.toString.replace "." "/" ++ ".lean"}:{lineNo}
- Math description: {informalLemma.math}
- Physics description: {informalLemma.physics}
- Proof description: {informalLemma.proof}
- References: {informalLemma.ref}
- Dependencies:\n{String.intercalate "\n" dep}"
unsafe def informalLemmaToWebString (c : Import × ConstantInfo) : MetaM String := do
let lineNo ← getLineNumber c.2.name
let informalLemma ← Informal.constantInfoToInformalLemma c.2
let dep ← informalLemma.dependencies.mapM fun d => depToWebString d
let webPath := "https://github.com/HEPLean/HepLean/blob/master/"++
(c.1.module.toString.replace "." "/") ++ ".lean"
pure s!"
**Informal lemma**: [{informalLemma.name}]({webPath}#L{lineNo}) :=
*{informalLemma.math}*
- Physics description: {informalLemma.physics}
- Proof description: {informalLemma.proof}
- References: {informalLemma.ref}
- Dependencies:\n{String.intercalate "\n" dep}"
unsafe def informalDefToString (c : Import × ConstantInfo) : MetaM String := do
let lineNo ← getLineNumber c.2.name
let informalDef ← Informal.constantInfoToInformalDefinition c.2
let dep ← informalDef.dependencies.mapM fun d => depToString d
pure s!"
Informal def: {informalDef.name}
- ./{c.1.module.toString.replace "." "/" ++ ".lean"}:{lineNo}
- Math description: {informalDef.math}
- Physics description: {informalDef.physics}
- References: {informalDef.ref}
- Dependencies:\n{String.intercalate "\n" dep}"
unsafe def informalDefToWebString (c : Import × ConstantInfo) : MetaM String := do
let lineNo ← getLineNumber c.2.name
let informalDef ← Informal.constantInfoToInformalDefinition c.2
let dep ← informalDef.dependencies.mapM fun d => depToWebString d
let webPath := "https://github.com/HEPLean/HepLean/blob/master/"++
(c.1.module.toString.replace "." "/") ++ ".lean"
pure s!"
**Informal def**: [{informalDef.name}]({webPath}#L{lineNo}) :=
*{informalDef.math}*
- Physics description: {informalDef.physics}
- References: {informalDef.ref}
- Dependencies:\n{String.intercalate "\n" dep}"
unsafe def informalToString (c : Import × ConstantInfo) : MetaM String := do
if Informal.isInformalLemma c.2 then
informalLemmaToString c
else if Informal.isInformalDef c.2 then
informalDefToString c
else
pure ""
unsafe def informalToWebString (c : Import × ConstantInfo) : MetaM String := do
if Informal.isInformalLemma c.2 then
informalLemmaToWebString c
else if Informal.isInformalDef c.2 then
informalDefToWebString c
else
pure ""
def informalFileHeader : String := s!"
# Informal definitions and lemmas
See [informal definitions and lemmas as a dependency graph](https://heplean.github.io/HepLean/graph.svg).
This file is automatically generated using `informal_lemma` and `informal_definition` commands
appearing in the raw Lean code of HepLean.
There is an implicit invitation to the reader to contribute to the formalization of
informal definitions and lemmas. You may also wish to contribute to HepLean by including
informal definitions and lemmas in the raw Lean code, especially if you do not have a
background in Lean.
"
open Informal
/-- Takes an import and outputs the list of `ConstantInfo` corresponding
to an informal definition or lemma in that import, sorted by line number. -/
def importToInformal (i : Import) : MetaM (Array (Import × ConstantInfo)) := do
let constants ← getConst i
let constants := constants.filter (fun c => ¬ Lean.Name.isInternalDetail c.2.name)
let informalConst := constants.filter fun c => Informal.isInformal c.2
let informalConstLineNo ← informalConst.mapM fun c => getLineNumber c.2.name
let informalConstWithLineNo := informalConst.zip informalConstLineNo
let informalConstWithLineNoSort := informalConstWithLineNo.qsort (fun a b => a.2 < b.2)
return informalConstWithLineNoSort.map (fun c => c.1)
unsafe def importToString (i : Import) : MetaM String := do
let informalConst ← importToInformal i
let informalPrint ← (informalConst.mapM informalToString).run'
if informalPrint.isEmpty then
pure ""
else
pure ("\n\n" ++ i.module.toString ++ "\n" ++ String.intercalate "\n\n" informalPrint.toList)
unsafe def importToWebString (i : Import) : MetaM String := do
let informalConst ← importToInformal i
let informalPrint ← (informalConst.mapM informalToWebString).run'
if informalPrint.isEmpty then
pure ""
else
pure ("\n\n## " ++ i.module.toString ++ "\n" ++ String.intercalate "\n\n" informalPrint.toList)
section dotFile
/-!
## Making the dot file for dependency graph.
-/
/-- Turns a formal definition or lemma into a node of a dot graph. -/
def formalToNode (nameSpaces : Array Name) (d : Name) : MetaM String := do
let lineNo ← getLineNumber d
let mod ← getModule d
let webPath := "https://github.com/HEPLean/HepLean/blob/master/"++
(mod.toString.replace "." "/") ++ ".lean"
let docstring ← getDocString d
let prefixName := if nameSpaces.contains d then d else
d.getPrefix
let nodeStr := s!"\"{d}\"[label=\"{d}\", shape=box, style=filled, fillcolor=steelblue,
tooltip=\"{docstring}\"]"
if prefixName = Lean.Name.anonymous then
pure nodeStr
else
pure ("subgraph cluster_" ++ prefixName.toString.replace "." "_" ++ " { " ++ nodeStr ++ "; }")
unsafe def informalLemmaToNode (nameSpaces : Array Name) (c : Import × ConstantInfo) : MetaM String := do
let lineNo ← getLineNumber c.2.name
let webPath := "https://github.com/HEPLean/HepLean/blob/master/"++
(c.1.module.toString.replace "." "/") ++ ".lean"
let informalLemma ← (Informal.constantInfoToInformalLemma c.2)
let prefixName := if nameSpaces.contains c.2.name then c.2.name else
c.2.name.getPrefix
let nodeStr := s!"\"{c.2.name}\"[label=\"{c.2.name}\", shape=ellipse, style=filled, fillcolor=lightgray,
tooltip=\"{informalLemma.math}\"]"
if prefixName = Lean.Name.anonymous then
pure nodeStr
else
pure ("subgraph cluster_" ++ prefixName.toString.replace "." "_" ++ " { " ++ nodeStr ++ "; }")
unsafe def informalDefToNode (nameSpaces : Array Name) (c : Import × ConstantInfo) : MetaM String := do
let lineNo ← getLineNumber c.2.name
let webPath := "https://github.com/HEPLean/HepLean/blob/master/"++
(c.1.module.toString.replace "." "/") ++ ".lean"
let informalDef ← (constantInfoToInformalDefinition c.2)
let prefixName := if nameSpaces.contains c.2.name then c.2.name else
c.2.name.getPrefix
let nodeStr := s!"\"{c.2.name}\"[label=\"{c.2.name}\", shape=box, style=filled, fillcolor=lightgray,
tooltip=\"{informalDef.math}\"]"
if prefixName = Lean.Name.anonymous then
pure nodeStr
else
pure ("subgraph cluster_" ++ prefixName.toString.replace "." "_" ++ " { " ++ nodeStr ++ "; }")
unsafe def informalToNode (nameSpaces : Array Name) (c : Import × ConstantInfo) : MetaM String := do
if Informal.isInformalLemma c.2 then
informalLemmaToNode nameSpaces c
else if Informal.isInformalDef c.2 then
informalDefToNode nameSpaces c
else
pure ""
unsafe def informalLemmaToEdges (c : Import × ConstantInfo) : MetaM (String) := do
let informalLemma ← constantInfoToInformalLemma c.2
let deps := informalLemma.dependencies
let edge := deps.map (fun d => s!"\"{d}\" -> \"{c.2.name}\"")
pure (String.intercalate "\n" edge)
unsafe def informalDefToEdges (c : Import × ConstantInfo) : MetaM (String) := do
let informalDef ← constantInfoToInformalDefinition c.2
let deps := informalDef.dependencies
let edge := deps.map (fun d => s!"\"{d}\" -> \"{c.2.name}\"")
pure (String.intercalate "\n" edge)
unsafe def informalToEdges (c : Import × ConstantInfo) : MetaM (String) := do
if Informal.isInformalLemma c.2 then
informalLemmaToEdges c
else if Informal.isInformalDef c.2 then
informalDefToEdges c
else
pure ""
unsafe def namespaceToCluster (name : Name) : MetaM String := do
let nameUnder := name.toString.replace "." "_"
if name = Lean.Name.anonymous then
pure ""
else
pure ("subgraph cluster_" ++ nameUnder ++ "
{
label=\"" ++ name.toString ++ "\";
color=steelblue;
}")
unsafe def mkDot (imports : Array Import) : MetaM String := do
let informal ← imports.mapM importToInformal
let informal := informal.flatten
let deps ← (informal.map (fun c => c.2)).mapM informalDependencies
let deps := deps.flatten
let informal_name := informal.map (fun c => c.2.name)
let informalNameSpaces := informal.map fun c => c.2.name.getPrefix
let clusters ← informalNameSpaces.mapM fun c => namespaceToCluster c
let clusters := String.intercalate "\n" clusters.toList.eraseDups
let formal_deps := deps.filter (fun d => d ∉ informal_name)
let formal_nodes ← formal_deps.mapM (formalToNode informalNameSpaces)
let nodes := String.intercalate "\n" formal_nodes.toList
let informalNodes ← informal.mapM (informalToNode informalNameSpaces)
let informalNodes := String.intercalate "\n" informalNodes.toList
let edges ← informal.mapM informalToEdges
let edges := String.intercalate "\n" edges.toList
let header := "strict digraph G {
graph [
pack=true;
packmode=\"array1\";
];
tooltip = \"Informal HepLean graph\";
node [margin=\"0.2,0.05\"; fontsize=10; fontname=\"Georgia\", height=0.1];
bgcolor=\"white\";
labelloc=\"t\";
labeljust=\"l\";
edge [arrowhead=vee];"
let footer := "}"
pure (header ++ "\n" ++clusters ++ "\n" ++ nodes ++ "\n" ++
informalNodes ++ "\n" ++ edges ++ "\n" ++ footer)
end dotFile
section htmlFile
/-!
## Making the html file for dependency graph.
-/
def formalToHTML (d : Name) : MetaM String := do
let lineNo ← getLineNumber d
let mod ← getModule d
let webPath := "https://github.com/HEPLean/HepLean/blob/master/"++
(mod.toString.replace "." "/") ++ ".lean"
let docstring ← getDocString d
pure s!"
<div id=\"{d}\" class=\"node-text\">
<b><a href=\"{webPath ++ "#L" ++ toString lineNo}\">{d}</a></b><br>
<b>Docstring: </b>{docstring}
</div>"
unsafe def informalLemmaToHTML (c : Import × ConstantInfo) : MetaM String := do
let lineNo ← getLineNumber c.2.name
let webPath := "https://github.com/HEPLean/HepLean/blob/master/"++
(c.1.module.toString.replace "." "/") ++ ".lean"
let informalLemma ← (constantInfoToInformalLemma c.2)
pure s!"
<div id=\"{c.2.name}\" class=\"node-text\">
<b><a href=\"{webPath ++ "#L" ++ toString lineNo}\">{c.2.name}</a></b><br>
<b>Math description: </b>{informalLemma.math}<br>
<b>Physics description: </b>{informalLemma.physics}<br>
</div>"
unsafe def informalDefToHTML (c : Import × ConstantInfo) : MetaM String := do
let lineNo ← getLineNumber c.2.name
let webPath := "https://github.com/HEPLean/HepLean/blob/master/"++
(c.1.module.toString.replace "." "/") ++ ".lean"
let informalDef ← (constantInfoToInformalDefinition c.2)
pure s!"
<div id=\"{c.2.name}\" class=\"node-text\">
<b><a href=\"{webPath ++ "#L" ++ toString lineNo}\">{c.2.name}</a></b><br>
<b>Math description: </b>{informalDef.math}<br>
<b>Physics description: </b>{informalDef.physics}<br>
</div>"
unsafe def informalToHTML (c : Import × ConstantInfo) : MetaM String := do
if Informal.isInformalLemma c.2 then
informalLemmaToHTML c
else if Informal.isInformalDef c.2 then
informalDefToHTML c
else
pure ""
unsafe def toHTML (imports : Array Import) : MetaM String := do
let informal ← imports.mapM importToInformal
let informal := informal.flatten
let deps ← (informal.map (fun c => c.2)).mapM informalDependencies
let deps := deps.flatten
let informal_name := informal.map (fun c => c.2.name)
let formal_deps := deps.filter (fun d => d ∉ informal_name)
let formal_nodes ← formal_deps.mapM (formalToHTML)
let nodes := String.intercalate "\n" formal_nodes.toList
let informalNodes ← informal.mapM (informalToHTML)
let informalNodes := String.intercalate "\n" informalNodes.toList
let header := "---
layout: default
---
<!DOCTYPE html>
<html>
<head>
<title>Informal dependency graph for HepLean</title>
<style>
/* Optional styling for the message display */
#message {
margin-top: 20px;
font-size: 1.2em;
color: #333;
}
/* Optional: Hide the text content divs */
.node-text {
display: none;
font-family: 'Times New Roman', Times, serif;
}
</style>
</head>
<body>
<!-- Include D3.js -->
<script src=\"https://d3js.org/d3.v5.min.js\"></script>
<!-- Include hpcc-js/wasm for Graphviz layout -->
<script src=\"https://unpkg.com/@hpcc-js/wasm@0.3.11/dist/index.min.js\"></script>
<!-- Include d3-graphviz for rendering Graphviz graphs -->
<script src=\"https://unpkg.com/d3-graphviz@3.0.5/build/d3-graphviz.js\"></script>
<!-- Add a title to the page -->
<h1 style=\"text-align: center;\">Informal dependency graph for HepLean</h1>
<p style=\"text-align: center;\">Click on a node to display the text associated with it.</p>
<p style=\"text-align: center;\">This graph only shows informal results (gray) and their direct formal dependencies (blue).
It does not show all results formalised into HepLean.</p>
<!-- Div to display the graph -->
<div id=\"graph\" style=\"width: 75vw; height: 75vh; border: 1px solid black\"></div>
<!-- Hidden divs containing the text to display when nodes are clicked -->
<!-- Add more divs for other nodes as needed, each with id equal to the node name -->
<div id=\"includedContent\"></div>
<!-- Div to display the message when a node is clicked -->
<div id=\"message\"></div>"
let footer := "
<script type=\"text/javascript\">
// Load the DOT file and render the graph
d3.text(\"InformalDot.dot\").then(function(dotSrc) {
var width = 0.75 * window.innerWidth;
var height = 0.75 * window.innerHeight;
// Initialize the graphviz renderer
var graphviz = d3.select(\"#graph\")
.graphviz()
.width(width)
.height(height)
.fit(true);
// Render the graph and attach event listeners
graphviz.renderDot(dotSrc).on(\"end\", function() {
// Attach click event to nodes
d3.selectAll('.node').on('click', function() {
// Get the node's name from the title element
var nodeName = d3.select(this).select('title').text();
// Get the text associated with the nodeName
var nodeTextElement = document.getElementById(nodeName);
if (nodeTextElement) {
var nodeText = nodeTextElement.innerHTML;
// Display the text in the #message div
document.getElementById('message').innerHTML = nodeText;
} else {
// If no element with the nodeName ID exists
document.getElementById('message').innerHTML = 'No information available for node: ' + nodeName;
}
});
});
});
</script>
</body>
</html>
"
pure (header ++ "\n" ++ nodes ++ "\n" ++
informalNodes ++ "\n" ++ footer)
end htmlFile
/-!
## Main function
-/
unsafe def main (args : List String) : IO UInt32 := do
initSearchPath (← findSysroot)
let mods : Name := `HepLean
let imp : Import := {module := mods}
let mFile ← findOLean imp.module
unless (← mFile.pathExists) do
throw <| IO.userError s!"object file '{mFile}' of module {imp.module} does not exist"
let (hepLeanMod, _) ← readModuleData mFile
let imports := hepLeanMod.imports.filter (fun c => c.module ≠ `Init)
let importString ← CoreM.withImportModules #[`HepLean] (imports.mapM importToString).run'
IO.println (String.intercalate "" importString.toList)
/- Writing out informal file. -/
let fileOut : System.FilePath := {toString := "./docs/Informal.md"}
if "mkFile" ∈ args then
let importWebString ← CoreM.withImportModules #[`HepLean] (imports.mapM importToWebString).run'
let out := String.intercalate "\n" importWebString.toList
IO.println (s!"Informal file made.")
IO.FS.writeFile fileOut (informalFileHeader ++ out)
/- Making the dot file. -/
if "mkDot" ∈ args then
let dot ← CoreM.withImportModules #[`HepLean] (mkDot imports).run'
let dotFile : System.FilePath := {toString := "./docs/InformalDot.dot"}
IO.FS.writeFile dotFile dot
IO.println (s!"Dot file made.")
/- Making the html file. -/
if "mkHTML" ∈ args then
let html ← CoreM.withImportModules #[`HepLean] (toHTML imports).run'
let htmlFile : System.FilePath := {toString := "./docs/InformalGraph.html"}
IO.FS.writeFile htmlFile html
IO.println (s!"HTML file made.")
pure 0