Skip to content

Commit f68161d

Browse files
authored
Fix LEAN evaluation error for formulas with non-self-recursive mutual recursion (#623)
1 parent 5070993 commit f68161d

File tree

4 files changed

+67
-0
lines changed

4 files changed

+67
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### Bugfixes
1010

1111
* Fix PARI evaluation error for formulas with constant identity functions (e.g., A026765)
12+
* Fix LEAN evaluation error for formulas with mutually recursive functions that are not self-recursive (e.g., A001636)
1213

1314
## v25.11.29
1415

src/form/formula_util.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,59 @@ bool FormulaUtil::isRecursive(const Formula& formula,
111111
return false;
112112
}
113113

114+
// Helper function to check if a function depends on another
115+
static bool dependsOn(const std::multimap<std::string, std::string>& deps,
116+
const std::string& from, const std::string& to) {
117+
auto range = deps.equal_range(from);
118+
for (auto it = range.first; it != range.second; ++it) {
119+
if (it->second == to) {
120+
return true;
121+
}
122+
}
123+
return false;
124+
}
125+
126+
bool FormulaUtil::hasMutualRecursion(const Formula& formula,
127+
Expression::Type type) {
128+
// Get transitive dependencies between functions
129+
auto deps = getDependencies(formula, type, true, false);
130+
131+
// Build a set of all function names
132+
std::set<std::string> funcNames;
133+
for (const auto& e : formula.entries) {
134+
if (e.first.type == type && !e.first.name.empty()) {
135+
funcNames.insert(e.first.name);
136+
}
137+
}
138+
139+
// Check for mutual recursion: A depends on B and B depends on A (where A !=
140+
// B), and neither A nor B is self-recursive.
141+
// If at least one function in the cycle is self-recursive, the LEAN code
142+
// generator will use Nat domain with pattern offsets, which can prove
143+
// termination.
144+
for (auto itA = funcNames.begin(); itA != funcNames.end(); ++itA) {
145+
for (auto itB = std::next(itA); itB != funcNames.end(); ++itB) {
146+
const auto& funcA = *itA;
147+
const auto& funcB = *itB;
148+
149+
// Check for mutual dependency (A->B and B->A)
150+
if (dependsOn(deps, funcA, funcB) && dependsOn(deps, funcB, funcA)) {
151+
// Check if either A or B is self-recursive
152+
// Use non-transitive check for self-recursion
153+
bool aIsSelfRecursive = isRecursive(formula, funcA, type);
154+
bool bIsSelfRecursive = isRecursive(formula, funcB, type);
155+
156+
// If neither is self-recursive, LEAN can't prove termination
157+
// because the domain will be Int instead of Nat
158+
if (!aIsSelfRecursive && !bIsSelfRecursive) {
159+
return true;
160+
}
161+
}
162+
}
163+
}
164+
return false;
165+
}
166+
114167
Number FormulaUtil::getMinimumBaseCase(const Formula& formula,
115168
const std::string& funcName) {
116169
Number minBaseCase = Number::INF;

src/form/formula_util.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ class FormulaUtil {
1717
static bool isRecursive(const Formula& formula, const std::string& funcName,
1818
Expression::Type type = Expression::Type::FUNCTION);
1919

20+
// Check if the formula contains mutually recursive functions (cycles of
21+
// length > 1 in the dependency graph, e.g., A calls B, B calls A)
22+
static bool hasMutualRecursion(
23+
const Formula& formula,
24+
Expression::Type type = Expression::Type::FUNCTION);
25+
2026
static Number getMinimumBaseCase(const Formula& formula,
2127
const std::string& funcName);
2228

src/form/lean.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ bool LeanFormula::convert(const Formula& formula, int64_t offset,
172172
if (as_vector) {
173173
return false;
174174
}
175+
176+
// Check for mutual recursion - LEAN can't prove termination automatically
177+
// for mutually recursive functions without explicit termination proofs
178+
if (FormulaUtil::hasMutualRecursion(formula)) {
179+
return false;
180+
}
181+
175182
lean_formula = {};
176183
lean_formula.domain = "Int";
177184
lean_formula.funcNames =

0 commit comments

Comments
 (0)