Вопрос или проблема
Как работает логика доказательств в Agda в случае завершения программы? Есть ли способ доказать нечетность в Agda? Я хотел бы доказать, что для любого числа n существует число m, такое что количество делителей m больше, чем количество делителей n.
Я пытаюсь решить задачу Эйлера №12, связанную с нахождением первого треугольного числа, имеющего более 500 делителей. Я реализовал этот алгоритм в Agda, но получил сообщение об ошибке о завершении программы. Насколько я понимаю, в Agda нет доказательства того, что ((numberOfDivisors ∘ triangleNumber) n) > 500 когда-либо сработает. Есть ли способ доказать, что это произойдет, или, возможно, существует способ переписать эту задачу?
natRange : Nat → List Nat
natRange zero = []
natRange (suc n) = n ∷ natRange n
numberOfDivisors : Nat → Nat
numberOfDivisors n = (length ∘₂ filterᵇ) (λ {zero → false ; x@(suc _) → n % x == zero}) (natRange (suc n))
-- получить треугольное число
triangleNumber : Nat → Nat
triangleNumber zero = zero
triangleNumber (suc n) = suc n + triangleNumber n
-- рекурсивный алгоритм
findTriangleNumberRecursively : (trNum : Nat) .{{_ : NonZero trNum}} → Nat
findTriangleNumberRecursively n =
if ((numberOfDivisors ∘ triangleNumber) n) < 500
then findTriangleNumberRecursively (suc n)
else triangleNumber n
main : IO ⊤
main = (putStrLn ∘ show) (findTriangleNumberRecursively (suc zero))
Сообщение об ошибке
Проверка завершения не удалась для следующих функций:
findTriangleNumberRecursively
Проблемные вызовы:
findTriangleNumberRecursively (suc n)
Ответ или решение
Вам требуется доказать, что для любого натурального числа ( n ) существует такое число ( m ), что количество делителей числа ( m ) больше, чем количество делителей числа ( n ). Это можно сделать, проанализировав поведение функции, вычисляющей количество делителей, и соответствующий алгоритм в Agda.
Доказательство существования числа с большим количеством делителей
Количество делителей числа ( n ) можно определить через его разложение на простые множители. Если ( n = p_1^{e_1} p_2^{e_2} \ldots p_k^{e_k} ), то количество делителей можно вычислить по формуле:
[
d(n) = (e_1 + 1)(e_2 + 1) \cdots (e_k + 1)
]
Чтобы доказать, что для любого ( n ) существует ( m ) с ( d(m) > d(n) ), достаточно заметить, что увеличивая ( n ) на 1, мы большую вероятность дадим нам большее количество делителей. Например, если ( m = n + 1 ), то для простых ( n ) мы имеем больше возможностей для разложения.
Пример в Agda
- Использование количества делителей: Ваша реализация функции
numberOfDivisors
корректна, но можно улучшить понятие о делителях. - Проблема с терминальностью: Ошибка "Termination checking failed" возникает из-за того, что Agda не может гарантировать, что ваш рекурсивный вызов всегда приведет к базовому случаю.
Вы можете переписать функцию с использованием механизма, который бы зафиксировал максимум для ( n ), например так:
findTriangleNumberRecursively : (trNum : Nat) .{{ _ : NonZero trNum }} → Nat
findTriangleNumberRecursively n =
let d = numberOfDivisors (triangleNumber n)
in if d < 500 then
findTriangleNumberRecursively (suc n)
else
triangleNumber n
Изменение условия рекурсии
Чтобы убедиться в том, что рекурсивный вызов может завершиться, вы можете добавить доказательство, что количество делителей будет расти. Например, вы можете использовать индукцию для того, чтобы показать, что:
- При увеличении ( n ), количество делителей ( d(n) ) будет либо не уменьшаться, либо возрастать. Просто добавьте дополнительное следствие:
{R : Nat → Set} → (∀ n → R n → R (suc n)) → R zero → (n : Nat) → R n
Это поможет доказать, что количество делителей будет изменяться при переходе к ( n + 1 ).
Заключение
Ваша основная проблема заключается в том, что Agda не может проверить, что функция вызывается на меньших значениях достаточное количество раз для завершения. Обеспечив сильное основание индукции и параллельно контролируя количество делителей, вы сможете обеспечить корректность вашей функции.