diff --git a/libs/math/include/nil/crypto3/math/polynomial/distinct_degree_factorization.hpp b/libs/math/include/nil/crypto3/math/polynomial/distinct_degree_factorization.hpp index 3cc737d08..035d039d4 100644 --- a/libs/math/include/nil/crypto3/math/polynomial/distinct_degree_factorization.hpp +++ b/libs/math/include/nil/crypto3/math/polynomial/distinct_degree_factorization.hpp @@ -36,6 +36,50 @@ namespace nil::crypto3::math { + namespace detail { + + /** A callback that controls staged distinct-degree factorization after receiving one degree group. */ + template + concept DistinctDegreeFactorCallback = + CoefficientPolynomial && + requires(FactorCallback &callback, const distinct_degree_factor &factor) { + { callback(factor) } -> std::same_as; + }; + + /** + * Normalize a distinct-degree factorization input and verify that every irreducible factor has multiplicity + * one. The original leading coefficient is returned separately. Constant inputs return false; nonconstant + * square-free inputs return true and monic_input receives their monic form. + * + * @throws std::invalid_argument if a nonconstant input is not square-free. + * @pre input is a nonempty coefficient polynomial. + */ + template + bool prepare_distinct_degree_factorization_input( + typename Backend::polynomial_type &monic_input, + typename Backend::polynomial_type::value_type &leading_coefficient, + const typename Backend::polynomial_type &input, + polynomial_arithmetic::polynomial_context &arithmetic_context) { + monic_input = input; + condense(monic_input); + leading_coefficient = monic_input.back(); + if (monic_input.size() == 1) { + return false; + } + make_monic(monic_input, monic_input); + + typename Backend::polynomial_type input_derivative; + derivative(input_derivative, monic_input); + typename Backend::polynomial_type repeated_factor; + gcd(repeated_factor, monic_input, input_derivative, arithmetic_context); + if (repeated_factor.size() > 1) { + throw std::invalid_argument("distinct-degree factorization requires a square-free polynomial"); + } + return true; + } + + } // namespace detail + /** * Split a square-free polynomial into products of irreducible factors of equal degree using the classical * distinct-degree algorithm. This implementation is intended as a correctness reference for faster blocked @@ -67,10 +111,7 @@ namespace nil::crypto3::math { * @pre input is a nonempty coefficient polynomial. */ template - requires requires(FactorCallback &callback, - const distinct_degree_factor &factor) { - { callback(factor) } -> std::same_as; - } + requires detail::DistinctDegreeFactorCallback distinct_degree_factorization_result distinct_degree_factorization_reference(const typename Backend::polynomial_type &input, polynomial_arithmetic::polynomial_context &arithmetic_context, @@ -80,21 +121,11 @@ namespace nil::crypto3::math { using result_type = distinct_degree_factorization_result; result_type result; - polynomial_type monic_input(input); - condense(monic_input); - result.leading_coefficient = monic_input.back(); - if (monic_input.size() == 1) { + polynomial_type monic_input; + if (!detail::prepare_distinct_degree_factorization_input(monic_input, result.leading_coefficient, + input, arithmetic_context)) { return result; } - make_monic(monic_input, monic_input); - - polynomial_type input_derivative; - derivative(input_derivative, monic_input); - polynomial_type repeated_factor; - gcd(repeated_factor, monic_input, input_derivative, arithmetic_context); - if (repeated_factor.size() > 1) { - throw std::invalid_argument("distinct-degree factorization requires a square-free polynomial"); - } const polynomial_type x = {value_type {}, value_type::one()}; polynomial_type frobenius_power(x); diff --git a/libs/math/include/nil/crypto3/math/polynomial/kaltofen_shoup_distinct_degree_factorization.hpp b/libs/math/include/nil/crypto3/math/polynomial/kaltofen_shoup_distinct_degree_factorization.hpp new file mode 100644 index 000000000..41c112a69 --- /dev/null +++ b/libs/math/include/nil/crypto3/math/polynomial/kaltofen_shoup_distinct_degree_factorization.hpp @@ -0,0 +1,409 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#ifndef CRYPTO3_MATH_KALTOFEN_SHOUP_DISTINCT_DEGREE_FACTORIZATION_HPP +#define CRYPTO3_MATH_KALTOFEN_SHOUP_DISTINCT_DEGREE_FACTORIZATION_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace nil::crypto3::math::detail { + + /** + * Select the Kaltofen-Shoup degree-block size for a degree-n polynomial. Distinct-degree factorization explicitly + * examines degrees only through n / 2; any factor left afterward is irreducible. Choosing + * + * b = ceil(sqrt(n / 2)) + * + * balances the b baby Frobenius steps against approximately n / (2b) giant-step blocks: the sum + * b + n / (2b) is minimized at b = sqrt(n / 2). The computation rounds n / 2 upward before applying the integer + * ceiling square root, which gives the same integer b without using floating-point arithmetic. Constant and + * linear inputs use the minimum valid block size of one. + */ + constexpr std::size_t kaltofen_shoup_block_size(std::size_t polynomial_degree) { + if (polynomial_degree <= 1) { + return 1; + } + const std::size_t half_degree_rounded_up = polynomial_degree / 2 + polynomial_degree % 2; + return ceil_sqrt(half_degree_rounded_up); + } + + /** + * Frobenius precomputation for one Kaltofen-Shoup degree block. Let the coefficient field contain Q elements, let + * B be the modulus polynomial stored in frobenius_context, and let b be the degree-block size. The precomputation + * stores the baby steps + * + * h[i] = X^(Q^i) mod B, 0 <= i <= b, + * + * and a modular-composition precomputation for h[b]. Composing a reduced polynomial A with h[b] applies b + * Frobenius iterations at once: + * + * A(h[b]) mod B = A(X)^(Q^b) mod B. + * + * Reusing that composition advances consecutive giant steps with one modular composition per block. + * + * Constructing the baby-step table performs b Frobenius maps and stores b + 1 residues, using + * O(b * degree(B)) field elements. Construction also builds the existing modular-composition precomputation for + * h[b]. + * + * @throws std::invalid_argument if block_size is zero. + */ + template + class kaltofen_shoup_frobenius_precomputation { + public: + using backend_type = Backend; + using polynomial_type = typename backend_type::polynomial_type; + using value_type = typename polynomial_type::value_type; + + kaltofen_shoup_frobenius_precomputation( + std::size_t block_size, const polynomial_frobenius_context &frobenius_context, + polynomial_arithmetic::polynomial_context &arithmetic_context) : + block_size_(block_size), baby_steps_(make_baby_steps(block_size_, frobenius_context, arithmetic_context)), + block_frobenius_precomputation_(baby_steps_.back(), + // A residue modulo degree-d polynomial B has at most d coefficients. The + // composition precomputation requires a positive limit for constant B. + std::max(1, frobenius_context.divisor_context().degree()), + frobenius_context.divisor_context(), arithmetic_context) { + } + + std::size_t block_size() const { + return block_size_; + } + + /** Return h[index]. @pre index <= block_size(). */ + const polynomial_type &baby_step(std::size_t index) const { + return baby_steps_[index]; + } + + /** + * Apply b Frobenius iterations with one modular composition. Output may alias input. + * + * @pre input is reduced modulo B. + * @pre frobenius_context represents the same B used to construct this precomputation. + */ + void apply_block_frobenius(polynomial_type &output, const polynomial_type &input, + const polynomial_frobenius_context &frobenius_context, + polynomial_arithmetic::polynomial_context &arithmetic_context) const { + compose_mod(output, input, block_frobenius_precomputation_, frobenius_context.divisor_context(), + arithmetic_context); + } + + private: + static std::vector + make_baby_steps(std::size_t block_size, const polynomial_frobenius_context &frobenius_context, + polynomial_arithmetic::polynomial_context &arithmetic_context) { + if (block_size == 0) { + throw std::invalid_argument("the Kaltofen-Shoup block size must be positive"); + } + + std::vector baby_steps; + baby_steps.reserve(block_size + 1); + polynomial_type reduced_x = {value_type {}, value_type::one()}; + const auto &divisor_context = frobenius_context.divisor_context(); + if (divisor_context.degree() == 0) { + reduced_x.assign(1, value_type {}); + } else if (reduced_x.size() >= divisor_context.divisor().size()) { + remainder(reduced_x, reduced_x, divisor_context, arithmetic_context); + } + baby_steps.emplace_back(std::move(reduced_x)); + for (std::size_t index = 1; index <= block_size; ++index) { + polynomial_type next_step; + frobenius_map(next_step, baby_steps.back(), frobenius_context, arithmetic_context); + baby_steps.emplace_back(std::move(next_step)); + } + return baby_steps; + } + + std::size_t block_size_; + std::vector baby_steps_; + polynomial_composition_precomputation block_frobenius_precomputation_; + }; + + /** + * Extract one coarse Kaltofen-Shoup degree block. Let the coefficient field contain Q elements, let B be the + * modulus in frobenius_context, and let precomputation store h[i] = X^(Q^i) mod B for a block of size b. For + * zero-based block j, the giant-step exponent is e = (j + 1) * b. The number c = degree_count satisfies + * 1 <= c <= b and allows the final block to process fewer than b degrees. Let R denote the polynomial passed as + * remaining. If giant_step = X^(Q^e) mod B, form + * + * interval_product = product(giant_step - h[b - 1 - i]) mod B, 0 <= i < c. + * + * These differences correspond to degrees e - b + 1 through e - b + c. Their product permits one GCD with R to + * extract every irreducible factor in that interval. A difference may also contain factors whose degrees divide + * the target degree, so R must already have all lower-degree factors removed. + * + * For example, with b = 2, the first giant step is X^(Q^2): subtracting h[1] targets degree one and subtracting + * h[0] targets degree two. The next giant step is X^(Q^4): the same subtractions target degrees three and four. + * + * @throws std::invalid_argument if degree_count is zero or greater than the block size. + * @pre remaining is square-free, divides B, and has all earlier degree factors removed. + * @pre giant_step is reduced modulo B. + * @pre frobenius_context represents the same B used to construct precomputation. + */ + template + void kaltofen_shoup_coarse_block_factor(typename Backend::polynomial_type &output, + const typename Backend::polynomial_type &remaining, + const typename Backend::polynomial_type &giant_step, + std::size_t degree_count, + const kaltofen_shoup_frobenius_precomputation &precomputation, + const polynomial_frobenius_context &frobenius_context, + polynomial_arithmetic::polynomial_context &arithmetic_context) { + using polynomial_type = typename Backend::polynomial_type; + using value_type = typename polynomial_type::value_type; + + if (degree_count == 0 || degree_count > precomputation.block_size()) { + throw std::invalid_argument("the coarse Kaltofen-Shoup degree count must be within the block"); + } + + polynomial_type interval_product = {value_type::one()}; + polynomial_type difference; + for (std::size_t offset = 0; offset < degree_count; ++offset) { + const std::size_t baby_step_index = precomputation.block_size() - 1 - offset; + subtraction(difference, giant_step, precomputation.baby_step(baby_step_index)); + mulmod(interval_product, interval_product, difference, frobenius_context.divisor_context(), + arithmetic_context); + } + gcd(output, remaining, interval_product, arithmetic_context); + } + + /** + * Split one coarse Kaltofen-Shoup block into exact-degree groups. Let the coefficient field contain Q elements, + * let B be the modulus used to construct precomputation, let b = precomputation.block_size(), and let + * h[i] = X^(Q^i) mod B. If giant_step = X^(Q^e) mod B, the first degree in its block is + * + * first_factor_degree = e - b + 1. + * + * The polynomial passed as coarse_block contains only irreducible factors whose degrees range from + * first_factor_degree through first_factor_degree + degree_count - 1. At offset i, subtracting h[b - 1 - i] from + * giant_step targets degree + * + * e - (b - 1 - i) = first_factor_degree + i. + * + * After the lower degrees in this block have been removed, taking the GCD with coarse_block therefore extracts + * exactly the factors of that degree. + * + * The extracted groups are appended in increasing degree order. The callback is invoked immediately after each + * group is appended and may stop the split early. + * + * @return stop_factorization if the callback requests an early stop; continue_factorization otherwise. + * @throws std::invalid_argument if first_factor_degree is zero, or if degree_count is zero or greater than the + * block size. + * @pre coarse_block is monic and square-free, divides the modulus used for precomputation, and contains only + * factors in the stated degree interval. + * @pre giant_step and first_factor_degree describe the same block as precomputation. + */ + template + requires DistinctDegreeFactorCallback + factorization_control kaltofen_shoup_split_coarse_block( + std::vector> &output, + typename Backend::polynomial_type coarse_block, const typename Backend::polynomial_type &giant_step, + std::size_t first_factor_degree, std::size_t degree_count, + const kaltofen_shoup_frobenius_precomputation &precomputation, + polynomial_arithmetic::polynomial_context &arithmetic_context, FactorCallback &&factor_callback) { + using polynomial_type = typename Backend::polynomial_type; + + if (first_factor_degree == 0 || degree_count == 0 || degree_count > precomputation.block_size()) { + throw std::invalid_argument( + "the first factor degree and degree count must be positive, and the degree count must not exceed the " + "block size"); + } + + polynomial_type difference; + polynomial_type factor; + polynomial_type quotient; + for (std::size_t offset = 0; offset < degree_count && coarse_block.size() > 1; ++offset) { + const std::size_t baby_step_index = precomputation.block_size() - 1 - offset; + subtraction(difference, giant_step, precomputation.baby_step(baby_step_index)); + gcd(factor, coarse_block, difference, arithmetic_context); + if (factor.size() == 1) { + continue; + } + + output.push_back({std::move(factor), first_factor_degree + offset}); + if (factor_callback(output.back()) == factorization_control::stop_factorization) { + return factorization_control::stop_factorization; + } + + factorization_exact_quotient(quotient, coarse_block, output.back().polynomial, arithmetic_context); + coarse_block = std::move(quotient); + } + + if (coarse_block.size() > 1) { + throw std::invalid_argument("the coarse block contains factors outside the stated degree interval"); + } + return factorization_control::continue_factorization; + } + + /** + * Factor a monic square-free polynomial into distinct-degree groups using an explicit Kaltofen-Shoup block size. + * For each consecutive degree block, the algorithm computes one coarse GCD, removes that block from the + * unclassified polynomial, and immediately fine-splits it. Interleaving the coarse and fine phases avoids storing + * a giant-step polynomial for every block while preserving increasing degree order and immediate callback stops. + * + * Once the smallest unprocessed factor degree is greater than half the degree of the unclassified polynomial, + * that polynomial is either constant or irreducible. Otherwise it would contain at least two factors of at least + * the smallest unprocessed degree, whose combined degree would exceed the polynomial's degree. The irreducible + * remainder can therefore be emitted directly. + * + * @return stop_factorization if the callback requests an early stop; continue_factorization otherwise. + * @throws std::invalid_argument if block_size is zero. + * @pre input is monic, square-free, nonzero, and nonconstant. + */ + template + requires DistinctDegreeFactorCallback + factorization_control kaltofen_shoup_factor_monic_square_free( + std::vector> &output, + typename Backend::polynomial_type input, std::size_t block_size, + polynomial_arithmetic::polynomial_context &arithmetic_context, FactorCallback &&factor_callback) { + using polynomial_type = typename Backend::polynomial_type; + + if (block_size == 0) { + throw std::invalid_argument("the Kaltofen-Shoup block size must be positive"); + } + // A linear polynomial is already one degree-one factor, so no Frobenius precomputation is needed. + if (input.size() == 2) { + output.push_back({std::move(input), 1}); + return factor_callback(output.back()); + } + + polynomial_frobenius_context frobenius_context(input, arithmetic_context); + kaltofen_shoup_frobenius_precomputation precomputation(block_size, frobenius_context, + arithmetic_context); + polynomial_type giant_step = precomputation.baby_step(block_size); + polynomial_type unclassified(std::move(input)); + + std::size_t first_factor_degree = 1; + while (first_factor_degree <= (unclassified.size() - 1) / 2) { + const std::size_t maximum_degree_to_test = (unclassified.size() - 1) / 2; + const std::size_t degree_count = std::min(block_size, maximum_degree_to_test - first_factor_degree + 1); + + polynomial_type coarse_block; + kaltofen_shoup_coarse_block_factor(coarse_block, unclassified, giant_step, degree_count, precomputation, + frobenius_context, arithmetic_context); + if (coarse_block.size() > 1) { + polynomial_type quotient; + factorization_exact_quotient(quotient, unclassified, coarse_block, arithmetic_context); + unclassified = std::move(quotient); + + const std::size_t coarse_block_degree = coarse_block.size() - 1; + // Every irreducible factor in this block has degree at least first_factor_degree. If the block's total + // degree is less than twice that bound, it cannot contain two factors and is itself irreducible. + if (coarse_block_degree / 2 < first_factor_degree) { + output.push_back({std::move(coarse_block), coarse_block_degree}); + if (factor_callback(output.back()) == factorization_control::stop_factorization) { + return factorization_control::stop_factorization; + } + } else if (kaltofen_shoup_split_coarse_block(output, std::move(coarse_block), giant_step, + first_factor_degree, degree_count, precomputation, + arithmetic_context, factor_callback) == + factorization_control::stop_factorization) { + return factorization_control::stop_factorization; + } + } + + // Giant-step exponents remain aligned to fixed-size blocks, including when the final tested interval is + // shorter than block_size. + first_factor_degree += block_size; + if (first_factor_degree <= (unclassified.size() - 1) / 2) { + precomputation.apply_block_frobenius(giant_step, giant_step, frobenius_context, arithmetic_context); + } + } + + if (unclassified.size() > 1) { + const std::size_t irreducible_factor_degree = unclassified.size() - 1; + output.push_back({std::move(unclassified), irreducible_factor_degree}); + if (factor_callback(output.back()) == factorization_control::stop_factorization) { + return factorization_control::stop_factorization; + } + } + return factorization_control::continue_factorization; + } + +} // namespace nil::crypto3::math::detail + +namespace nil::crypto3::math { + + /** + * Split a square-free polynomial into products of irreducible factors of equal degree using the blocked + * Kaltofen-Shoup distinct-degree algorithm. The input is normalized to monic form while its original leading + * coefficient is preserved in the result. Zero and constant inputs produce no factors. + * + * The block size is selected automatically to balance baby and giant Frobenius steps. Factors are emitted in + * increasing irreducible-factor degree. After each factor is appended, factor_callback may request an early stop; + * the stopped result includes that factor and has complete set to false. + * + * @throws std::invalid_argument if a nonconstant input is not square-free. + * @pre input is a nonempty coefficient polynomial. + */ + template + requires detail::DistinctDegreeFactorCallback + distinct_degree_factorization_result + distinct_degree_factorization_kaltofen_shoup( + const typename Backend::polynomial_type &input, + polynomial_arithmetic::polynomial_context &arithmetic_context, + FactorCallback &&factor_callback) { + using polynomial_type = typename Backend::polynomial_type; + using result_type = distinct_degree_factorization_result; + + result_type result; + polynomial_type monic_input; + if (!detail::prepare_distinct_degree_factorization_input(monic_input, result.leading_coefficient, + input, arithmetic_context)) { + return result; + } + + const std::size_t block_size = detail::kaltofen_shoup_block_size(monic_input.size() - 1); + if (detail::kaltofen_shoup_factor_monic_square_free(result.factors, std::move(monic_input), block_size, + arithmetic_context, factor_callback) == + factorization_control::stop_factorization) { + result.complete = false; + } + return result; + } + + /** Compute the complete Kaltofen-Shoup distinct-degree factorization without a staged callback. */ + template + distinct_degree_factorization_result + distinct_degree_factorization_kaltofen_shoup( + const typename Backend::polynomial_type &input, + polynomial_arithmetic::polynomial_context &arithmetic_context) { + using factor_type = distinct_degree_factor; + return distinct_degree_factorization_kaltofen_shoup( + input, arithmetic_context, + [](const factor_type &) { return factorization_control::continue_factorization; }); + } + +} // namespace nil::crypto3::math + +#endif // CRYPTO3_MATH_KALTOFEN_SHOUP_DISTINCT_DEGREE_FACTORIZATION_HPP diff --git a/libs/math/test/distinct_degree_factorization.cpp b/libs/math/test/distinct_degree_factorization.cpp index 69c7f0483..e0e969ffd 100644 --- a/libs/math/test/distinct_degree_factorization.cpp +++ b/libs/math/test/distinct_degree_factorization.cpp @@ -25,13 +25,17 @@ #define BOOST_TEST_MODULE distinct_degree_factorization_test #include +#include #include #include +#include #include #include +#include +#include #include namespace { @@ -70,11 +74,22 @@ namespace { } return reconstructed; } + + template + void check_reference_and_kaltofen_shoup_agree( + const typename Backend::polynomial_type &input, + polynomial_arithmetic::polynomial_context &reference_context, + polynomial_arithmetic::polynomial_context &kaltofen_shoup_context) { + const auto reference = math::distinct_degree_factorization_reference(input, reference_context); + const auto kaltofen_shoup = + math::distinct_degree_factorization_kaltofen_shoup(input, kaltofen_shoup_context); + BOOST_CHECK(kaltofen_shoup == reference); + } } // namespace BOOST_AUTO_TEST_SUITE(distinct_degree_factorization_test_suite) -BOOST_AUTO_TEST_CASE(reference_factorization_separates_known_irreducible_degrees) { +BOOST_AUTO_TEST_CASE(reference_and_kaltofen_shoup_factorizations_separate_known_irreducible_degrees) { using backend_type = polynomial_arithmetic::schoolbook_backend; using polynomial_type = typename backend_type::polynomial_type; @@ -98,6 +113,8 @@ BOOST_AUTO_TEST_CASE(reference_factorization_separates_known_irreducible_degrees polynomial_arithmetic::polynomial_context arithmetic_context; const auto result = math::distinct_degree_factorization_reference(input, arithmetic_context); + const auto kaltofen_shoup_result = + math::distinct_degree_factorization_kaltofen_shoup(input, arithmetic_context); BOOST_CHECK(result.complete); BOOST_CHECK(result.leading_coefficient == leading_coefficient); @@ -106,9 +123,10 @@ BOOST_AUTO_TEST_CASE(reference_factorization_separates_known_irreducible_degrees BOOST_CHECK(result.factors[1] == math::distinct_degree_factor({degree_two_factor, 2})); BOOST_CHECK(result.factors[2] == math::distinct_degree_factor({degree_three_factor, 3})); BOOST_CHECK(reconstruct(backend, result) == input); + BOOST_CHECK(kaltofen_shoup_result == result); } -BOOST_AUTO_TEST_CASE(staged_factorization_stops_after_the_reported_degree) { +BOOST_AUTO_TEST_CASE(staged_reference_and_kaltofen_shoup_factorizations_stop_after_the_reported_degree) { using backend_type = polynomial_arithmetic::schoolbook_backend; using polynomial_type = typename backend_type::polynomial_type; @@ -120,24 +138,35 @@ BOOST_AUTO_TEST_CASE(staged_factorization_stops_after_the_reported_degree) { polynomial_type input = multiply(backend, degree_one_factor, degree_two_factor); input = multiply(backend, input, degree_three_factor); - std::size_t callback_count = 0; - polynomial_arithmetic::polynomial_context arithmetic_context; - const auto result = math::distinct_degree_factorization_reference( - input, arithmetic_context, [&](const math::distinct_degree_factor &factor) { + auto make_callback = [](std::size_t &callback_count) { + return [&callback_count](const math::distinct_degree_factor &factor) { ++callback_count; return factor.irreducible_factor_degree == 2 ? math::factorization_control::stop_factorization : math::factorization_control::continue_factorization; - }); + }; + }; - BOOST_CHECK(!result.complete); - BOOST_CHECK_EQUAL(callback_count, 2); - BOOST_REQUIRE_EQUAL(result.factors.size(), 2); - BOOST_CHECK_EQUAL(result.factors[0].irreducible_factor_degree, 1); - BOOST_CHECK_EQUAL(result.factors[1].irreducible_factor_degree, 2); - BOOST_CHECK(result.factors[1].polynomial == degree_two_factor); + polynomial_arithmetic::polynomial_context arithmetic_context; + std::size_t reference_callback_count = 0; + auto reference_callback = make_callback(reference_callback_count); + const auto reference_result = + math::distinct_degree_factorization_reference(input, arithmetic_context, reference_callback); + + std::size_t kaltofen_shoup_callback_count = 0; + auto kaltofen_shoup_callback = make_callback(kaltofen_shoup_callback_count); + const auto kaltofen_shoup_result = math::distinct_degree_factorization_kaltofen_shoup( + input, arithmetic_context, kaltofen_shoup_callback); + + BOOST_CHECK(!reference_result.complete); + BOOST_CHECK_EQUAL(reference_callback_count, 2); + BOOST_CHECK_EQUAL(kaltofen_shoup_callback_count, 2); + BOOST_CHECK(kaltofen_shoup_result == reference_result); + BOOST_REQUIRE_EQUAL(reference_result.factors.size(), 2); + BOOST_CHECK(reference_result.factors[0] == math::distinct_degree_factor({degree_one_factor, 1})); + BOOST_CHECK(reference_result.factors[1] == math::distinct_degree_factor({degree_two_factor, 2})); } -BOOST_AUTO_TEST_CASE(reference_factorization_rejects_a_repeated_factor) { +BOOST_AUTO_TEST_CASE(reference_and_kaltofen_shoup_factorizations_reject_a_repeated_factor) { using backend_type = polynomial_arithmetic::schoolbook_backend; using polynomial_type = typename backend_type::polynomial_type; @@ -148,6 +177,8 @@ BOOST_AUTO_TEST_CASE(reference_factorization_rejects_a_repeated_factor) { BOOST_CHECK_THROW(math::distinct_degree_factorization_reference(input, arithmetic_context), std::invalid_argument); + BOOST_CHECK_THROW(math::distinct_degree_factorization_kaltofen_shoup(input, arithmetic_context), + std::invalid_argument); } BOOST_AUTO_TEST_CASE(zero_and_constant_polynomials_have_no_distinct_degree_factors) { @@ -157,33 +188,403 @@ BOOST_AUTO_TEST_CASE(zero_and_constant_polynomials_have_no_distinct_degree_facto polynomial_arithmetic::polynomial_context arithmetic_context; const auto zero = math::distinct_degree_factorization_reference( polynomial_type {fq_value_type::zero()}, arithmetic_context); + const auto kaltofen_shoup_zero = math::distinct_degree_factorization_kaltofen_shoup( + polynomial_type {fq_value_type::zero()}, arithmetic_context); BOOST_CHECK(zero.complete); BOOST_CHECK(zero.leading_coefficient == fq_value_type::zero()); BOOST_CHECK(zero.factors.empty()); + BOOST_CHECK(kaltofen_shoup_zero == zero); const auto constant = math::distinct_degree_factorization_reference( polynomial_type {fq_value_type(13)}, arithmetic_context); + const auto kaltofen_shoup_constant = math::distinct_degree_factorization_kaltofen_shoup( + polynomial_type {fq_value_type(13)}, arithmetic_context); BOOST_CHECK(constant.complete); BOOST_CHECK(constant.leading_coefficient == fq_value_type(13)); BOOST_CHECK(constant.factors.empty()); + BOOST_CHECK(kaltofen_shoup_constant == constant); } -BOOST_AUTO_TEST_CASE(reference_factorization_supports_native_extension_field_coefficients) { - using backend_type = polynomial_arithmetic::schoolbook_backend; +BOOST_AUTO_TEST_CASE(kaltofen_shoup_matches_the_reference_across_square_free_degrees) { + using backend_type = polynomial_arithmetic::schoolbook_backend; using polynomial_type = typename backend_type::polynomial_type; - backend_type backend; + polynomial_arithmetic::polynomial_context reference_context; + polynomial_arithmetic::polynomial_context kaltofen_shoup_context; + // Each polynomial is c*X^n - 3 with nonzero c and n below the field characteristic. Any common root of the + // polynomial and its derivative would have to be zero, but zero is not a root, so every input is square-free. + for (const std::size_t degree : {1U, 2U, 3U, 4U, 5U, 8U, 9U, 12U, 20U}) { + BOOST_TEST_CONTEXT("degree = " << degree) { + polynomial_type input(degree + 1, fq_value_type::zero()); + input.front() = -fq_value_type(3); + input.back() = fq_value_type(degree + 1); + check_reference_and_kaltofen_shoup_agree(input, reference_context, kaltofen_shoup_context); + } + } +} + +BOOST_AUTO_TEST_CASE(reference_and_kaltofen_shoup_factorizations_support_native_extension_field_coefficients) { + using schoolbook_backend = polynomial_arithmetic::schoolbook_backend; + using mixed_radix_backend = polynomial_arithmetic::mixed_radix_backend; + using polynomial_type = typename schoolbook_backend::polynomial_type; + + schoolbook_backend backend; const polynomial_type first_linear_factor = {fq12_value(1), fq12_value_type::one()}; const polynomial_type second_linear_factor = {fq12_value(20), fq12_value_type::one()}; const polynomial_type input = multiply(backend, first_linear_factor, second_linear_factor); + polynomial_arithmetic::polynomial_context reference_context; + polynomial_arithmetic::polynomial_context kaltofen_shoup_context; + const auto reference = math::distinct_degree_factorization_reference(input, reference_context); + const auto kaltofen_shoup = + math::distinct_degree_factorization_kaltofen_shoup(input, kaltofen_shoup_context); + + // Products of representatives modulo this degree-two polynomial contain at most three coefficients. + polynomial_arithmetic::polynomial_context mixed_radix_context {mixed_radix_backend(3)}; + const auto mixed_radix_kaltofen_shoup = + math::distinct_degree_factorization_kaltofen_shoup(input, mixed_radix_context); + + BOOST_CHECK(reference.complete); + BOOST_REQUIRE_EQUAL(reference.factors.size(), 1); + BOOST_CHECK_EQUAL(reference.factors.front().irreducible_factor_degree, 1); + BOOST_CHECK(reference.factors.front().polynomial == input); + BOOST_CHECK(kaltofen_shoup == reference); + BOOST_CHECK(mixed_radix_kaltofen_shoup == reference); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_block_size_balances_baby_and_giant_steps) { + BOOST_CHECK_EQUAL(math::detail::kaltofen_shoup_block_size(0), 1); + BOOST_CHECK_EQUAL(math::detail::kaltofen_shoup_block_size(1), 1); + BOOST_CHECK_EQUAL(math::detail::kaltofen_shoup_block_size(2), 1); + BOOST_CHECK_EQUAL(math::detail::kaltofen_shoup_block_size(8), 2); + BOOST_CHECK_EQUAL(math::detail::kaltofen_shoup_block_size(9), 3); + BOOST_CHECK_EQUAL(math::detail::kaltofen_shoup_block_size(18), 3); + BOOST_CHECK_EQUAL(math::detail::kaltofen_shoup_block_size(19), 4); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_precomputation_builds_baby_and_giant_frobenius_steps) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + constexpr std::size_t block_size = 3; + const polynomial_type divisor = {fq_value_type(2), fq_value_type(3), fq_value_type::zero(), fq_value_type::one()}; + const polynomial_type x = {fq_value_type::zero(), fq_value_type::one()}; polynomial_arithmetic::polynomial_context arithmetic_context; - const auto result = math::distinct_degree_factorization_reference(input, arithmetic_context); + math::polynomial_frobenius_context frobenius_context(divisor, arithmetic_context); + math::detail::kaltofen_shoup_frobenius_precomputation precomputation(block_size, frobenius_context, + arithmetic_context); + + BOOST_CHECK_EQUAL(precomputation.block_size(), block_size); + polynomial_type expected_baby_step(x); + for (std::size_t index = 0; index <= block_size; ++index) { + BOOST_CHECK(precomputation.baby_step(index) == expected_baby_step); + if (index != block_size) { + math::frobenius_map(expected_baby_step, expected_baby_step, frobenius_context, arithmetic_context); + } + } - BOOST_CHECK(result.complete); - BOOST_REQUIRE_EQUAL(result.factors.size(), 1); - BOOST_CHECK_EQUAL(result.factors.front().irreducible_factor_degree, 1); - BOOST_CHECK(result.factors.front().polynomial == input); + polynomial_type expected_giant_step(x); + polynomial_type giant_step = precomputation.baby_step(block_size); + for (std::size_t block = 1; block <= 3; ++block) { + math::frobenius_map(expected_giant_step, expected_giant_step, block_size, frobenius_context, + arithmetic_context); + BOOST_CHECK(giant_step == expected_giant_step); + if (block != 3) { + precomputation.apply_block_frobenius(giant_step, giant_step, frobenius_context, arithmetic_context); + } + } +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_precomputation_rejects_a_zero_block_size) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + const polynomial_type divisor = {fq_value_type::one(), fq_value_type::zero(), fq_value_type::one()}; + polynomial_arithmetic::polynomial_context arithmetic_context; + math::polynomial_frobenius_context frobenius_context(divisor, arithmetic_context); + + BOOST_CHECK_THROW( + math::detail::kaltofen_shoup_frobenius_precomputation(0, frobenius_context, arithmetic_context), + std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_precomputation_reduces_the_initial_baby_step) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + polynomial_arithmetic::polynomial_context arithmetic_context; + const polynomial_type linear_divisor = {fq_value_type(5), fq_value_type::one()}; + math::polynomial_frobenius_context linear_frobenius_context(linear_divisor, arithmetic_context); + math::detail::kaltofen_shoup_frobenius_precomputation linear_precomputation( + 1, linear_frobenius_context, arithmetic_context); + BOOST_CHECK(linear_precomputation.baby_step(0) == polynomial_type({-fq_value_type(5)})); + + const polynomial_type constant_divisor = {fq_value_type(7)}; + math::polynomial_frobenius_context constant_frobenius_context(constant_divisor, arithmetic_context); + math::detail::kaltofen_shoup_frobenius_precomputation constant_precomputation( + 1, constant_frobenius_context, arithmetic_context); + BOOST_CHECK(constant_precomputation.baby_step(0) == polynomial_type({fq_value_type::zero()})); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_coarse_blocks_extract_degree_intervals) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + backend_type backend; + const polynomial_type linear_factor = {fq_value_type::one(), fq_value_type::one()}; + // Three generates the multiplicative group of BN254 Fq, so 3 is not a square and neither 3 nor 9 = 3^2 is a + // cube. Consequently X^2 - 3, X^3 - 3, and X^3 - 9 are irreducible over this field. + const polynomial_type quadratic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::one()}; + const polynomial_type first_cubic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + const polynomial_type second_cubic_factor = {-fq_value_type(9), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + const polynomial_type first_block = multiply(backend, linear_factor, quadratic_factor); + const polynomial_type second_block = multiply(backend, first_cubic_factor, second_cubic_factor); + const polynomial_type input = multiply(backend, first_block, second_block); + + constexpr std::size_t block_size = 2; + polynomial_arithmetic::polynomial_context arithmetic_context; + math::polynomial_frobenius_context frobenius_context(input, arithmetic_context); + math::detail::kaltofen_shoup_frobenius_precomputation precomputation(block_size, frobenius_context, + arithmetic_context); + + polynomial_type giant_step = precomputation.baby_step(block_size); + polynomial_type factor; + math::detail::kaltofen_shoup_coarse_block_factor(factor, input, giant_step, block_size, precomputation, + frobenius_context, arithmetic_context); + BOOST_CHECK(factor == first_block); + + precomputation.apply_block_frobenius(giant_step, giant_step, frobenius_context, arithmetic_context); + math::detail::kaltofen_shoup_coarse_block_factor(factor, second_block, giant_step, 1, precomputation, + frobenius_context, arithmetic_context); + BOOST_CHECK(factor == second_block); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_coarse_block_rejects_an_invalid_degree_count) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + const polynomial_type divisor = {fq_value_type::one(), fq_value_type::zero(), fq_value_type::one()}; + polynomial_arithmetic::polynomial_context arithmetic_context; + math::polynomial_frobenius_context frobenius_context(divisor, arithmetic_context); + math::detail::kaltofen_shoup_frobenius_precomputation precomputation(2, frobenius_context, + arithmetic_context); + const polynomial_type giant_step = precomputation.baby_step(2); + polynomial_type output; + + BOOST_CHECK_THROW(math::detail::kaltofen_shoup_coarse_block_factor(output, divisor, giant_step, 0, precomputation, + frobenius_context, arithmetic_context), + std::invalid_argument); + BOOST_CHECK_THROW(math::detail::kaltofen_shoup_coarse_block_factor(output, divisor, giant_step, 3, precomputation, + frobenius_context, arithmetic_context), + std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_fine_splitting_separates_exact_degrees) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + using factor_type = math::distinct_degree_factor; + + backend_type backend; + const polynomial_type linear_factor = {fq_value_type::one(), fq_value_type::one()}; + // Three generates the multiplicative group of BN254 Fq, so 3 is not a square and neither 3 nor 9 = 3^2 is a + // cube. Consequently X^2 - 3, X^3 - 3, and X^3 - 9 are irreducible over this field. + const polynomial_type quadratic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::one()}; + const polynomial_type first_cubic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + const polynomial_type second_cubic_factor = {-fq_value_type(9), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + const polynomial_type first_block = multiply(backend, linear_factor, quadratic_factor); + const polynomial_type second_block = multiply(backend, first_cubic_factor, second_cubic_factor); + const polynomial_type input = multiply(backend, first_block, second_block); + + constexpr std::size_t block_size = 2; + polynomial_arithmetic::polynomial_context arithmetic_context; + math::polynomial_frobenius_context frobenius_context(input, arithmetic_context); + math::detail::kaltofen_shoup_frobenius_precomputation precomputation(block_size, frobenius_context, + arithmetic_context); + + std::vector factors; + std::size_t callback_count = 0; + auto record_factor = [&](const factor_type &) { + ++callback_count; + return math::factorization_control::continue_factorization; + }; + + polynomial_type giant_step = precomputation.baby_step(block_size); + BOOST_CHECK(math::detail::kaltofen_shoup_split_coarse_block(factors, first_block, giant_step, 1, block_size, + precomputation, arithmetic_context, record_factor) == + math::factorization_control::continue_factorization); + + precomputation.apply_block_frobenius(giant_step, giant_step, frobenius_context, arithmetic_context); + BOOST_CHECK(math::detail::kaltofen_shoup_split_coarse_block(factors, second_block, giant_step, 3, 1, precomputation, + arithmetic_context, record_factor) == + math::factorization_control::continue_factorization); + + BOOST_CHECK_EQUAL(callback_count, 3); + BOOST_REQUIRE_EQUAL(factors.size(), 3); + BOOST_CHECK(factors[0] == factor_type({linear_factor, 1})); + BOOST_CHECK(factors[1] == factor_type({quadratic_factor, 2})); + BOOST_CHECK(factors[2] == factor_type({second_block, 3})); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_fine_splitting_honors_an_early_stop) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + using factor_type = math::distinct_degree_factor; + + backend_type backend; + const polynomial_type linear_factor = {fq_value_type::one(), fq_value_type::one()}; + const polynomial_type quadratic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::one()}; + const polynomial_type input = multiply(backend, linear_factor, quadratic_factor); + + constexpr std::size_t block_size = 2; + polynomial_arithmetic::polynomial_context arithmetic_context; + math::polynomial_frobenius_context frobenius_context(input, arithmetic_context); + math::detail::kaltofen_shoup_frobenius_precomputation precomputation(block_size, frobenius_context, + arithmetic_context); + const polynomial_type giant_step = precomputation.baby_step(block_size); + std::vector factors; + + const auto control = math::detail::kaltofen_shoup_split_coarse_block( + factors, input, giant_step, 1, block_size, precomputation, arithmetic_context, + [](const factor_type &) { return math::factorization_control::stop_factorization; }); + + BOOST_CHECK(control == math::factorization_control::stop_factorization); + BOOST_REQUIRE_EQUAL(factors.size(), 1); + BOOST_CHECK(factors.front() == factor_type({linear_factor, 1})); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_fine_splitting_rejects_invalid_degree_intervals) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + using factor_type = math::distinct_degree_factor; + + backend_type backend; + const polynomial_type linear_factor = {fq_value_type::one(), fq_value_type::one()}; + const polynomial_type quadratic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::one()}; + const polynomial_type input = multiply(backend, linear_factor, quadratic_factor); + + constexpr std::size_t block_size = 2; + polynomial_arithmetic::polynomial_context arithmetic_context; + math::polynomial_frobenius_context frobenius_context(input, arithmetic_context); + math::detail::kaltofen_shoup_frobenius_precomputation precomputation(block_size, frobenius_context, + arithmetic_context); + const polynomial_type giant_step = precomputation.baby_step(block_size); + std::vector factors; + auto continue_factorization = [](const factor_type &) { + return math::factorization_control::continue_factorization; + }; + + BOOST_CHECK_THROW(math::detail::kaltofen_shoup_split_coarse_block(factors, input, giant_step, 0, 1, precomputation, + arithmetic_context, continue_factorization), + std::invalid_argument); + BOOST_CHECK_THROW(math::detail::kaltofen_shoup_split_coarse_block(factors, input, giant_step, 1, 0, precomputation, + arithmetic_context, continue_factorization), + std::invalid_argument); + BOOST_CHECK_THROW(math::detail::kaltofen_shoup_split_coarse_block(factors, input, giant_step, 1, 3, precomputation, + arithmetic_context, continue_factorization), + std::invalid_argument); + + // The stated interval contains only degree one, so the unclassified quadratic must be rejected. + BOOST_CHECK_THROW(math::detail::kaltofen_shoup_split_coarse_block(factors, input, giant_step, 1, 1, precomputation, + arithmetic_context, continue_factorization), + std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_internal_driver_processes_multiple_blocks) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + using factor_type = math::distinct_degree_factor; + + backend_type backend; + const polynomial_type linear_factor = {fq_value_type::one(), fq_value_type::one()}; + const polynomial_type quadratic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::one()}; + const polynomial_type first_cubic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + const polynomial_type second_cubic_factor = {-fq_value_type(9), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + const polynomial_type degree_three_factors = multiply(backend, first_cubic_factor, second_cubic_factor); + polynomial_type input = multiply(backend, linear_factor, quadratic_factor); + input = multiply(backend, input, degree_three_factors); + + polynomial_arithmetic::polynomial_context arithmetic_context; + std::vector factors; + std::size_t callback_count = 0; + // A block size of two forces a full degree-1-to-2 block followed by a partial block containing degree three. + const auto control = math::detail::kaltofen_shoup_factor_monic_square_free( + factors, input, 2, arithmetic_context, [&](const factor_type &) { + ++callback_count; + return math::factorization_control::continue_factorization; + }); + + BOOST_CHECK(control == math::factorization_control::continue_factorization); + BOOST_CHECK_EQUAL(callback_count, 3); + BOOST_REQUIRE_EQUAL(factors.size(), 3); + BOOST_CHECK(factors[0] == factor_type({linear_factor, 1})); + BOOST_CHECK(factors[1] == factor_type({quadratic_factor, 2})); + BOOST_CHECK(factors[2] == factor_type({degree_three_factors, 3})); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_internal_driver_emits_the_final_irreducible_factor) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + using factor_type = math::distinct_degree_factor; + + const polynomial_type cubic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + polynomial_arithmetic::polynomial_context arithmetic_context; + std::vector factors; + const auto control = math::detail::kaltofen_shoup_factor_monic_square_free( + factors, cubic_factor, 2, arithmetic_context, + [](const factor_type &) { return math::factorization_control::continue_factorization; }); + + BOOST_CHECK(control == math::factorization_control::continue_factorization); + BOOST_REQUIRE_EQUAL(factors.size(), 1); + BOOST_CHECK(factors.front() == factor_type({cubic_factor, 3})); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_internal_driver_rejects_a_zero_block_size) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + using factor_type = math::distinct_degree_factor; + + const polynomial_type input = {fq_value_type::one(), fq_value_type::one()}; + polynomial_arithmetic::polynomial_context arithmetic_context; + std::vector factors; + + BOOST_CHECK_THROW(math::detail::kaltofen_shoup_factor_monic_square_free( + factors, input, 0, arithmetic_context, + [](const factor_type &) { return math::factorization_control::continue_factorization; }), + std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(kaltofen_shoup_internal_driver_propagates_an_early_stop) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + using factor_type = math::distinct_degree_factor; + + backend_type backend; + const polynomial_type linear_factor = {fq_value_type::one(), fq_value_type::one()}; + const polynomial_type quadratic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::one()}; + const polynomial_type cubic_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + polynomial_type input = multiply(backend, linear_factor, quadratic_factor); + input = multiply(backend, input, cubic_factor); + + polynomial_arithmetic::polynomial_context arithmetic_context; + std::vector factors; + const auto control = math::detail::kaltofen_shoup_factor_monic_square_free( + factors, input, 2, arithmetic_context, [](const factor_type &factor) { + return factor.irreducible_factor_degree == 2 ? math::factorization_control::stop_factorization : + math::factorization_control::continue_factorization; + }); + + BOOST_CHECK(control == math::factorization_control::stop_factorization); + BOOST_REQUIRE_EQUAL(factors.size(), 2); + BOOST_CHECK(factors[0] == factor_type({linear_factor, 1})); + BOOST_CHECK(factors[1] == factor_type({quadratic_factor, 2})); } BOOST_AUTO_TEST_SUITE_END()