Files
sqisign_new/src/quaternion/ref/generic/matkermod.c

422 lines
15 KiB
C
Raw Normal View History

#include "internal.h"
/***************** Howell form **********************/
/** @brief Compute u s.t. gcd(u, mod) = 1 and ux = gcd(x, mod)
*/
static int unit(ibz_t *unit, ibz_t *gcd, const ibz_t *x, const ibz_t *mod) {
if (ibz_is_zero(x))
return 0;
ibz_t stab, nmod, nmod2, thrash, tmp;
ibz_init(&stab); ibz_init(&nmod); ibz_init(&nmod2); ibz_init(&thrash); ibz_init(&tmp);
ibz_xgcd(gcd, unit, &thrash, x, mod);
ibz_div(&nmod, &thrash, mod, gcd);
// Stabilizer(unit, nmod)
ibz_gcd(&stab, unit, &nmod);
ibz_div(&nmod2, &thrash, mod, &stab);
ibz_div(&stab, &thrash, unit, &stab);
// Split(nmod2, stab)
for (int i = ibz_bitsize(&nmod2); i > 0; i >>= 1) {
ibz_mul(&stab, &stab, &stab);
ibz_mod(&stab, &stab, &nmod2);
}
ibz_gcd(&stab, &stab, &nmod2);
ibz_div(&stab, &thrash, &nmod2, &stab);
#ifndef NDEBUG
ibz_mul(&thrash, &stab, &nmod);
ibz_add(&thrash, unit, &thrash);
ibz_gcd(&thrash, &thrash, mod);
ibz_gcd(&tmp, unit, &nmod);
assert(ibz_cmp(&thrash, &tmp) == 0);
#endif
// Finish off
ibz_mul(&stab, &stab, &nmod);
ibz_add(unit, unit, &stab);
ibz_mod(unit, unit, mod);
#ifndef NDEBUG
ibz_gcd(&stab, unit, mod);
assert(ibz_is_one(&stab));
ibz_mul(&stab, unit, x);
ibz_mod(&stab, &stab, mod);
assert(ibz_cmp(&stab, gcd) == 0);
#endif
ibz_finalize(&stab); ibz_finalize(&nmod); ibz_finalize(&nmod2); ibz_finalize(&thrash); ibz_finalize(&tmp);
return 1;
}
/** @brief Linear combination of two columns
*
* (mat[][j] | mat[][k]) <- (mat[][j] | mat[][k]) * U (mod)
*
* only update columns between start (included) and end (excluded)
*/
static void gen_elem(int rows, int cols, ibz_t mat[rows][cols], int j, int k, int start, int end, const ibz_mat_2x2_t *U, const ibz_t *mod)
{
ibz_t tmp1, tmp2;
ibz_init(&tmp1); ibz_init(&tmp2);
for (int i = start; i < end; i++) {
ibz_mul(&tmp1, &mat[i][j], &(*U)[0][0]);
ibz_mul(&tmp2, &mat[i][k], &(*U)[1][0]);
ibz_add(&tmp1, &tmp1, &tmp2);
ibz_mul(&tmp2, &mat[i][j], &(*U)[0][1]);
ibz_mul(&mat[i][k], &mat[i][k], &(*U)[1][1]);
ibz_add(&mat[i][k], &tmp2, &mat[i][k]);
ibz_mod(&mat[i][j], &tmp1, mod);
ibz_mod(&mat[i][k], &mat[i][k], mod);
}
ibz_finalize(&tmp1); ibz_finalize(&tmp2);
}
/** @brief Swap columns j and k of mat
*/
static inline void swap_col(int rows, int cols, ibz_t mat[rows][cols], int j, int k)
{
ibz_t tmp;
ibz_init(&tmp);
for (int i = 0; i < rows; i++) {
ibz_copy(&tmp, &mat[i][j]);
ibz_copy(&mat[i][j], &mat[i][k]);
ibz_copy(&mat[i][k], &tmp);
}
ibz_finalize(&tmp);
}
/** @brief Check if column is all zeros
*/
static inline int is_col_zero(int rows, int cols, ibz_t mat[rows][cols], int j)
{
int is_zero = 1;
for (int i = 0; i < rows; i++)
is_zero &= ibz_is_zero(&mat[i][j]);
return is_zero;
}
/** Check that mat * trans = howell
*/
static int howell_check_matrices(int rows, int cols, const ibz_t howell[rows][rows+1], const ibz_t trans[rows+1][rows+1], const ibz_t mat[rows][cols], ibz_t *mod)
{
const int extra = rows + 1 - cols;
ibz_t test[rows][rows+1], res[rows][rows+1];
ibz_mat_init(rows, rows+1, test);
ibz_mat_init(rows, rows+1, res);
// copy mat to the right of test
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
ibz_mod(&test[i][j+extra], &mat[i][j], mod);
ibz_mat_mulmod(rows, rows+1, rows+1, res, test, trans, mod);
int ok = 1;
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows+1; j++)
ok &= ibz_cmp(&howell[i][j], &res[i][j]) == 0;
ibz_mat_finalize(rows, rows+1, res);
ibz_mat_finalize(rows, rows+1, test);
return ok;
}
void ibz_mat_mulmod(int from, int through, int to, ibz_t res[from][to], const ibz_t A[from][through], const ibz_t B[through][to], const ibz_t *mod)
{
ibz_t tmp;
ibz_init(&tmp);
for (int i = 0; i < from; i++) {
for (int j = 0; j < to; j++) {
ibz_set(&res[i][j], 0);
for (int k = 0; k < through; k++) {
ibz_mul(&tmp, &A[i][k], &B[k][j]);
ibz_add(&res[i][j], &res[i][j], &tmp);
ibz_mod(&res[i][j], &res[i][j], mod);
}
}
}
ibz_finalize(&tmp);
}
int ibz_mat_howell(int rows, int cols, ibz_t howell[rows][rows+1], ibz_t trans[rows+1][rows+1], const ibz_t mat[rows][cols], ibz_t *mod) {
assert(cols <= rows);
const int extra = rows + 1 - cols;
ibz_mat_2x2_t U;
ibz_t gcd, u, q;
ibz_mat_2x2_init(&U);
ibz_init(&gcd); ibz_init(&u); ibz_init(&q);
// copy mat to the right of howell
for (int i = 0; i < rows; i++) {
for (int j = cols; j < rows+1; j++)
ibz_set(&howell[i][j-cols], 0);
for (int j = 0; j < cols; j++)
ibz_mod(&howell[i][j+extra], &mat[i][j], mod);
}
// initialize trans to identity
if (trans) {
for (int i = 0; i < rows+1; i++) {
for (int j = 0; j < rows+1; j++)
if (j != i) ibz_set(&trans[i][j], 0);
ibz_set(&trans[i][i], 1);
}
assert(howell_check_matrices(rows, cols, howell, trans, mat, mod));
}
// Put in upper triangular form
for (int i = rows-1; i >= extra-1; i--) {
for (int j = extra; j <= i; j++) {
if (ibz_is_zero(&howell[i][j]))
continue;
ibz_xgcd_ann(&gcd, &U[0][0], &U[1][0], &U[0][1], &U[1][1], &howell[i][j], &howell[i][i+1]);
gen_elem(rows, rows+1, howell, j, i+1, 0, i, &U, mod);
ibz_set(&howell[i][j], 0);
ibz_copy(&howell[i][i+1], &gcd);
//
if (trans) {
gen_elem(rows+1, rows+1, trans, j, i+1, 0, rows+1, &U, mod);
assert(howell_check_matrices(rows, cols, howell, trans, mat, mod));
}
}
}
// Put in reduced Howell form
for (int i = rows-1; i >= 0; i--) {
/* normalize diagonal coefficient */
if (unit(&u, &gcd, &howell[i][i+1], mod)) {
for (int k = 0; k < i; k++) {
ibz_mul(&howell[k][i+1], &howell[k][i+1], &u);
ibz_mod(&howell[k][i+1], &howell[k][i+1], mod);
}
ibz_copy(&howell[i][i+1], &gcd);
//
if (trans) {
for (int k = 0; k < rows+1; k++) {
ibz_mul(&trans[k][i+1], &trans[k][i+1], &u);
ibz_mod(&trans[k][i+1], &trans[k][i+1], mod);
}
assert(howell_check_matrices(rows, cols, howell, trans, mat, mod));
}
}
/* reduce right of the diagonal */
ibz_t *pivot = &howell[i][i+1];
if (!ibz_is_zero(pivot)) {
for (int j = i+2; j < rows+1; j++) {
assert(ibz_cmp(pivot, &ibz_const_zero) > 0);
ibz_div(&q, &howell[i][j], &howell[i][j], pivot);
// howell[][j] -= q howell[][i+1]
for (int k = 0; k < i; k++) {
ibz_mul(&u, &q, &howell[k][i+1]);
ibz_sub(&howell[k][j], &howell[k][j], &u);
ibz_mod(&howell[k][j], &howell[k][j], mod);
}
// trans[][j] -= q trans[][i+1]
if (trans) {
for (int k = 0; k < rows+1; k++) {
ibz_mul(&u, &q, &trans[k][i+1]);
ibz_sub(&trans[k][j], &trans[k][j], &u);
ibz_mod(&trans[k][j], &trans[k][j], mod);
}
assert(howell_check_matrices(rows, cols, howell, trans, mat, mod));
}
}
}
/* ensure Howell property */
if (i > 0) {
ibz_gcd(&gcd, pivot, mod);
if (!ibz_is_one(&gcd)) {
// Ann(pivot)
ibz_div(&u, &gcd, mod, &gcd);
for (int k = 0; k < rows; k++) {
if (k < i) {
ibz_mul(&howell[k][0], &howell[k][i+1], &u);
ibz_mod(&howell[k][0], &howell[k][0], mod);
} else {
ibz_set(&howell[k][0], 0);
}
}
// trans[][0] += u trans[][i+1]
if (trans) {
for (int k = 0; k < rows+1; k++) {
ibz_mul(&q, &u, &trans[k][i+1]);
ibz_add(&trans[k][0], &trans[k][0], &q);
ibz_mod(&trans[k][0], &trans[k][0], mod);
}
assert(howell_check_matrices(rows, cols, howell, trans, mat, mod));
}
for (int i2 = i-1; i2 >= 0; i2--) {
if (ibz_is_zero(&howell[i2][0]))
continue;
if (ibz_is_zero(&howell[i2][i2+1])) {
swap_col(rows, rows+1, howell, 0, i2+1);
if (trans) {
swap_col(rows+1, rows+1, trans, 0, i2+1);
assert(howell_check_matrices(rows, cols, howell, trans, mat, mod));
}
continue;
}
ibz_xgcd_ann(&gcd, &U[0][0], &U[1][0], &U[0][1], &U[1][1], &howell[i2][0], &howell[i2][i2+1]);
gen_elem(rows, rows+1, howell, 0, i2+1, 0, i2, &U, mod);
ibz_set(&howell[i2][0], 0);
ibz_copy(&howell[i2][i2+1], &gcd);
//
if (trans) {
gen_elem(rows, rows+1, trans, 0, i2+1, 0, rows+1, &U, mod);
assert(howell_check_matrices(rows, cols, howell, trans, mat, mod));
}
}
}
}
}
/* put zero columns first */
int read, write;
for (read = rows, write = rows; read >= 1; read--) {
if (!is_col_zero(rows, rows+1, howell, read)) {
if (read < write) {
swap_col(rows, rows+1, howell, read, write);
if (trans) {
swap_col(rows+1, rows+1, trans, read, write);
assert(howell_check_matrices(rows, cols, howell, trans, mat, mod));
}
}
write--;
}
}
// Finalize
ibz_mat_2x2_finalize(&U);
ibz_finalize(&gcd); ibz_finalize(&u); ibz_finalize(&q);
return write + 1;
}
void ibz_mat_right_ker_mod(int rows, int cols, ibz_t ker[cols][cols], const ibz_t mat[rows][cols], ibz_t *mod)
{
assert(cols <= rows);
const int extra = rows + 1 - cols;
ibz_t tmp;
ibz_mat_2x2_t U;
ibz_t howell[rows][rows+1], trans[rows+1][rows+1], preker[rows+1][rows+1], near_ker[cols][rows+1];
ibz_init(&tmp);
ibz_mat_2x2_init(&U);
ibz_mat_init(rows, rows+1, howell);
ibz_mat_init(rows+1, rows+1, trans);
ibz_mat_init(rows+1, rows+1, preker);
ibz_mat_init(cols, rows+1, near_ker);
// Compute Howell form of mat
int zeros = ibz_mat_howell(rows, cols, howell, trans, mat, mod);
// Compute right kernel of Howell form
for (int j = rows, i = rows-1; j >= 0; j--) {
while (i >= 0 && ibz_is_zero(&howell[i][j]))
i--;
if (i < 0) {
ibz_set(&preker[j][j], 1);
continue;
}
ibz_t *pivot = &howell[i][j];
// Ann(pivot)
ibz_gcd(&tmp, pivot, mod);
if (!ibz_is_one(&tmp))
ibz_div(&preker[j][j], &tmp, mod, &tmp);
for (int j2 = j+1; j2 <= rows; j2++) {
// howell[i][j+1..rows] * preker[j+1..rows][j2]
for (int k = j+1; k <= rows; k++) {
ibz_mul(&tmp, &howell[i][k], &preker[k][j2]);
ibz_add(&preker[j][j2], &preker[j][j2], &tmp);
}
ibz_mod(&preker[j][j2], &preker[j][j2], mod);
//
ibz_div(&preker[j][j2], &tmp, &preker[j][j2], pivot);
assert(ibz_is_zero(&tmp));
if (!ibz_is_zero(&preker[j][j2]))
ibz_sub(&preker[j][j2], mod, &preker[j][j2]);
}
}
#ifndef NDEBUG
// Check that preker is indeed a kernel of howell
ibz_t acc;
ibz_init(&acc);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < rows+1; j++) {
ibz_set(&acc, 0);
for (int k = 0; k < rows+1; k++) {
ibz_mul(&tmp, &howell[i][k], &preker[k][j]);
ibz_add(&acc, &acc, &tmp);
}
ibz_mod(&acc, &acc, mod);
assert(ibz_is_zero(&acc));
}
}
ibz_finalize(&acc);
#endif
// Apply (bottom part of) transition matrix to computed kernel
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows+1; j++) {
for (int k = 0; k < rows+1; k++) {
ibz_mul(&tmp, &trans[i+extra][k], &preker[k][j]);
ibz_add(&near_ker[i][j], &near_ker[i][j], &tmp);
}
ibz_mod(&near_ker[i][j], &near_ker[i][j], mod);
}
}
// Move zero columns to the start
int read, write;
for (read = rows, write = rows; read >= 0; read--) {
if (!is_col_zero(cols, rows+1, near_ker, read)) {
if (read < write) {
swap_col(cols, rows+1, near_ker, read, write);
}
write--;
}
}
// Put in upper triangular form
const int diag_shift = rows - cols + 1; // i-th diagonal is at (i+diag_shift) column
for (int i = cols-1; i >= 0; i--) {
for (int j = write+1; j < i+diag_shift; j++) {
if (ibz_is_zero(&near_ker[i][j]))
continue;
ibz_xgcd_ann(&tmp, &U[0][0], &U[1][0], &U[0][1], &U[1][1], &near_ker[i][j], &near_ker[i][i+diag_shift]);
gen_elem(cols, rows+1, near_ker, j, i+diag_shift, 0, i+1, &U, mod);
ibz_set(&howell[i][j], 0);
ibz_copy(&howell[i][i+diag_shift], &tmp);
}
}
#ifndef NDEBUG
// Check that ker is indeed a kernel of mat
ibz_t check[rows][rows+1];
ibz_mat_init(rows, rows+1, check);
ibz_mat_mulmod(rows, cols, rows+1, check, mat, near_ker, mod);
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows+1; j++)
assert(ibz_is_zero(&check[i][j]));
ibz_mat_finalize(rows, rows+1, check);
#endif
// Copy result
for (int i = 0; i < cols; i++)
for (int j = 0; j < cols; j++)
ibz_copy(&ker[i][j], &near_ker[i][j+rows-cols+1]);
// Finalize
ibz_finalize(&tmp);
ibz_mat_2x2_finalize(&U);
ibz_mat_finalize(rows, rows+1, howell);
ibz_mat_finalize(rows+1, rows+1, trans);
ibz_mat_finalize(rows+1, rows+1, preker);
ibz_mat_finalize(cols, rows+1, near_ker);
}