// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // LLL.hpp // // LLL 格子基底簡約 (Lenstra-Lenstra-Lovász Lattice Basis Reduction) // // 整数格子の基底を「短く、直交に近い」形に変換する。 // 暗号解析、整数関係式発見 (PSLQ)、Diophantine 近似に応用。 // // 主な API: // lllReduce(basis, delta) — LLL 簡約 (Gram-Schmidt 直交化ベース) // isLLLReduced(basis, delta) — LLL 簡約条件の検証 // shortestVector(basis) — 最短ベクトル (簡約後の b_1) // findIntegerRelation(x, C) — PSLQ 風の整数関係式発見 #ifndef CALX_LINALG_LLL_HPP #define CALX_LINALG_LLL_HPP #include #include #include #include #include #include #include namespace calx { /** * @brief LLL 簡約の結果 */ struct LLLResult { std::vector> basis; ///< 簡約された基底 std::size_t swaps; ///< スワップ回数 }; namespace detail { // 内積 inline double dot(const std::vector& a, const std::vector& b) { double s = 0; for (std::size_t i = 0; i < a.size(); ++i) s += a[i] * b[i]; return s; } // ノルム² inline double norm2(const std::vector& v) { return dot(v, v); } // v -= c * u inline void sub_scaled(std::vector& v, const std::vector& u, double c) { for (std::size_t i = 0; i < v.size(); ++i) v[i] -= c * u[i]; } } // namespace detail /** * @brief LLL 格子基底簡約 * * 入力: 基底ベクトル b_0, ..., b_{n-1} ∈ R^d (行ベクトル) * 出力: LLL-簡約された基底 * * LLL 条件: * (1) |μ_{i,j}| ≤ 1/2 (サイズ簡約) * (2) δ·||b*_k||² ≤ ||b*_{k+1} + μ_{k+1,k}·b*_k||² (Lovász 条件) * * @param basis 基底ベクトルの配列 (n×d) * @param delta Lovász パラメータ (1/4 < δ ≤ 1, デフォルト 3/4) * @return LLLResult */ [[nodiscard]] inline LLLResult lllReduce(std::vector> basis, double delta = 0.75) { const std::size_t n = basis.size(); if (n == 0) return {basis, 0}; const std::size_t d = basis[0].size(); // Gram-Schmidt 直交化係数と直交ベクトルのノルム² std::vector> mu(n, std::vector(n, 0.0)); std::vector B(n, 0.0); // B[i] = ||b*_i||² std::vector> bstar(n, std::vector(d, 0.0)); // Gram-Schmidt を計算 auto computeGS = [&]() { for (std::size_t i = 0; i < n; ++i) { bstar[i] = basis[i]; for (std::size_t j = 0; j < i; ++j) { mu[i][j] = detail::dot(basis[i], bstar[j]) / B[j]; detail::sub_scaled(bstar[i], bstar[j], mu[i][j]); } B[i] = detail::norm2(bstar[i]); } }; computeGS(); std::size_t k = 1; std::size_t swaps = 0; while (k < n) { // サイズ簡約: |μ_{k,j}| ≤ 1/2 for (std::size_t j = k; j > 0; --j) { std::size_t jj = j - 1; if (std::abs(mu[k][jj]) > 0.5) { double r = std::round(mu[k][jj]); detail::sub_scaled(basis[k], basis[jj], r); // μ を更新 for (std::size_t i = 0; i <= jj; ++i) { mu[k][i] -= r * mu[jj][i]; } mu[k][jj] -= r; } } // Lovász 条件チェック double lhs = delta * B[k - 1]; double rhs = B[k] + mu[k][k - 1] * mu[k][k - 1] * B[k - 1]; if (lhs > rhs) { // スワップ b_k と b_{k-1} std::swap(basis[k], basis[k - 1]); ++swaps; // Gram-Schmidt を再計算 (効率化のため部分更新) // 簡易版: 全体再計算 computeGS(); if (k > 1) --k; } else { ++k; } } return {std::move(basis), swaps}; } /** * @brief LLL 簡約条件を検証する * * @param basis 基底ベクトル * @param delta Lovász パラメータ * @return true なら LLL 簡約済み */ [[nodiscard]] inline bool isLLLReduced(const std::vector>& basis, double delta = 0.75) { const std::size_t n = basis.size(); if (n <= 1) return true; const std::size_t d = basis[0].size(); // Gram-Schmidt std::vector> bstar(n, std::vector(d)); std::vector> mu(n, std::vector(n, 0.0)); std::vector B(n); for (std::size_t i = 0; i < n; ++i) { bstar[i] = basis[i]; for (std::size_t j = 0; j < i; ++j) { mu[i][j] = detail::dot(basis[i], bstar[j]) / B[j]; detail::sub_scaled(bstar[i], bstar[j], mu[i][j]); } B[i] = detail::norm2(bstar[i]); } // (1) サイズ簡約: |μ_{i,j}| ≤ 1/2 for (std::size_t i = 1; i < n; ++i) for (std::size_t j = 0; j < i; ++j) if (std::abs(mu[i][j]) > 0.5 + 1e-10) return false; // (2) Lovász 条件 for (std::size_t k = 1; k < n; ++k) { double lhs = delta * B[k - 1]; double rhs = B[k] + mu[k][k - 1] * mu[k][k - 1] * B[k - 1]; if (lhs > rhs + 1e-10) return false; } return true; } /** * @brief 格子の最短ベクトル (簡約後の第1基底ベクトル) * * LLL 簡約後の b_0 は最短ベクトルの 2^{(n-1)/2} 近似。 */ [[nodiscard]] inline std::vector shortestVector(const std::vector>& basis, double delta = 0.75) { auto result = lllReduce(basis, delta); return result.basis[0]; } /** * @brief 整数関係式発見 (PSLQ 風) * * 実数ベクトル x = (x_0, ..., x_{n-1}) に対して、 * 整数ベクトル m = (m_0, ..., m_{n-1}) で Σ m_i x_i ≈ 0 となるものを探す。 * * 方法: 拡大格子を LLL 簡約する。 * 格子基底 = [[C·x_0, 1, 0, ..., 0], * [C·x_1, 0, 1, ..., 0], * ... * [C·x_{n-1}, 0, 0, ..., 1]] * C は精度パラメータ (大きいほど高精度だが数値的に不安定)。 * * @param x 実数ベクトル * @param C 精度パラメータ (デフォルト 1e10) * @return 整数係数ベクトル m (最短の関係式) */ [[nodiscard]] inline std::vector findIntegerRelation( const std::vector& x, double C = 1e10) { const std::size_t n = x.size(); if (n < 2) throw std::invalid_argument("findIntegerRelation: requires at least 2 elements"); // 拡大格子の構築: n × (n+1) std::vector> basis(n, std::vector(n + 1, 0.0)); for (std::size_t i = 0; i < n; ++i) { basis[i][0] = C * x[i]; basis[i][i + 1] = 1.0; } auto result = lllReduce(basis); // 最短ベクトルの整数部分 (index 1..n) を返す // 最初の列 (C·Σm_i·x_i) がゼロに近い行を選ぶ std::size_t best = 0; double bestNorm = std::abs(result.basis[0][0]); for (std::size_t i = 1; i < n; ++i) { double v = std::abs(result.basis[i][0]); if (v < bestNorm) { bestNorm = v; best = i; } } std::vector m(n); for (std::size_t i = 0; i < n; ++i) { m[i] = static_cast(std::round(result.basis[best][i + 1])); } return m; } } // namespace calx #endif // CALX_LINALG_LLL_HPP