1 //===- Matrix.cpp - MLIR Matrix Class -------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "mlir/Analysis/Presburger/Matrix.h" 10 #include "mlir/Analysis/Presburger/Fraction.h" 11 #include "mlir/Analysis/Presburger/Utils.h" 12 #include "llvm/Support/MathExtras.h" 13 #include "llvm/Support/raw_ostream.h" 14 #include <algorithm> 15 #include <cassert> 16 #include <utility> 17 18 using namespace mlir; 19 using namespace presburger; 20 21 template <typename T> 22 Matrix<T>::Matrix(unsigned rows, unsigned columns, unsigned reservedRows, 23 unsigned reservedColumns) 24 : nRows(rows), nColumns(columns), 25 nReservedColumns(std::max(nColumns, reservedColumns)), 26 data(nRows * nReservedColumns) { 27 data.reserve(std::max(nRows, reservedRows) * nReservedColumns); 28 } 29 30 /// We cannot use the default implementation of operator== as it compares 31 /// fields like `reservedColumns` etc., which are not part of the data. 32 template <typename T> 33 bool Matrix<T>::operator==(const Matrix<T> &m) const { 34 if (nRows != m.getNumRows()) 35 return false; 36 if (nColumns != m.getNumColumns()) 37 return false; 38 39 for (unsigned i = 0; i < nRows; i++) 40 if (getRow(i) != m.getRow(i)) 41 return false; 42 43 return true; 44 } 45 46 template <typename T> 47 Matrix<T> Matrix<T>::identity(unsigned dimension) { 48 Matrix matrix(dimension, dimension); 49 for (unsigned i = 0; i < dimension; ++i) 50 matrix(i, i) = 1; 51 return matrix; 52 } 53 54 template <typename T> 55 unsigned Matrix<T>::getNumReservedRows() const { 56 return data.capacity() / nReservedColumns; 57 } 58 59 template <typename T> 60 void Matrix<T>::reserveRows(unsigned rows) { 61 data.reserve(rows * nReservedColumns); 62 } 63 64 template <typename T> 65 unsigned Matrix<T>::appendExtraRow() { 66 resizeVertically(nRows + 1); 67 return nRows - 1; 68 } 69 70 template <typename T> 71 unsigned Matrix<T>::appendExtraRow(ArrayRef<T> elems) { 72 assert(elems.size() == nColumns && "elems must match row length!"); 73 unsigned row = appendExtraRow(); 74 for (unsigned col = 0; col < nColumns; ++col) 75 at(row, col) = elems[col]; 76 return row; 77 } 78 79 template <typename T> 80 Matrix<T> Matrix<T>::transpose() const { 81 Matrix<T> transp(nColumns, nRows); 82 for (unsigned row = 0; row < nRows; ++row) 83 for (unsigned col = 0; col < nColumns; ++col) 84 transp(col, row) = at(row, col); 85 86 return transp; 87 } 88 89 template <typename T> 90 void Matrix<T>::resizeHorizontally(unsigned newNColumns) { 91 if (newNColumns < nColumns) 92 removeColumns(newNColumns, nColumns - newNColumns); 93 if (newNColumns > nColumns) 94 insertColumns(nColumns, newNColumns - nColumns); 95 } 96 97 template <typename T> 98 void Matrix<T>::resize(unsigned newNRows, unsigned newNColumns) { 99 resizeHorizontally(newNColumns); 100 resizeVertically(newNRows); 101 } 102 103 template <typename T> 104 void Matrix<T>::resizeVertically(unsigned newNRows) { 105 nRows = newNRows; 106 data.resize(nRows * nReservedColumns); 107 } 108 109 template <typename T> 110 void Matrix<T>::swapRows(unsigned row, unsigned otherRow) { 111 assert((row < getNumRows() && otherRow < getNumRows()) && 112 "Given row out of bounds"); 113 if (row == otherRow) 114 return; 115 for (unsigned col = 0; col < nColumns; col++) 116 std::swap(at(row, col), at(otherRow, col)); 117 } 118 119 template <typename T> 120 void Matrix<T>::swapColumns(unsigned column, unsigned otherColumn) { 121 assert((column < getNumColumns() && otherColumn < getNumColumns()) && 122 "Given column out of bounds"); 123 if (column == otherColumn) 124 return; 125 for (unsigned row = 0; row < nRows; row++) 126 std::swap(at(row, column), at(row, otherColumn)); 127 } 128 129 template <typename T> 130 MutableArrayRef<T> Matrix<T>::getRow(unsigned row) { 131 return {&data[row * nReservedColumns], nColumns}; 132 } 133 134 template <typename T> 135 ArrayRef<T> Matrix<T>::getRow(unsigned row) const { 136 return {&data[row * nReservedColumns], nColumns}; 137 } 138 139 template <typename T> 140 void Matrix<T>::setRow(unsigned row, ArrayRef<T> elems) { 141 assert(elems.size() == getNumColumns() && 142 "elems size must match row length!"); 143 for (unsigned i = 0, e = getNumColumns(); i < e; ++i) 144 at(row, i) = elems[i]; 145 } 146 147 template <typename T> 148 void Matrix<T>::insertColumn(unsigned pos) { 149 insertColumns(pos, 1); 150 } 151 template <typename T> 152 void Matrix<T>::insertColumns(unsigned pos, unsigned count) { 153 if (count == 0) 154 return; 155 assert(pos <= nColumns); 156 unsigned oldNReservedColumns = nReservedColumns; 157 if (nColumns + count > nReservedColumns) { 158 nReservedColumns = llvm::NextPowerOf2(nColumns + count); 159 data.resize(nRows * nReservedColumns); 160 } 161 nColumns += count; 162 163 for (int ri = nRows - 1; ri >= 0; --ri) { 164 for (int ci = nReservedColumns - 1; ci >= 0; --ci) { 165 unsigned r = ri; 166 unsigned c = ci; 167 T &dest = data[r * nReservedColumns + c]; 168 if (c >= nColumns) { // NOLINT 169 // Out of bounds columns are zero-initialized. NOLINT because clang-tidy 170 // complains about this branch being the same as the c >= pos one. 171 // 172 // TODO: this case can be skipped if the number of reserved columns 173 // didn't change. 174 dest = 0; 175 } else if (c >= pos + count) { 176 // Shift the data occuring after the inserted columns. 177 dest = data[r * oldNReservedColumns + c - count]; 178 } else if (c >= pos) { 179 // The inserted columns are also zero-initialized. 180 dest = 0; 181 } else { 182 // The columns before the inserted columns stay at the same (row, col) 183 // but this corresponds to a different location in the linearized array 184 // if the number of reserved columns changed. 185 if (nReservedColumns == oldNReservedColumns) 186 break; 187 dest = data[r * oldNReservedColumns + c]; 188 } 189 } 190 } 191 } 192 193 template <typename T> 194 void Matrix<T>::removeColumn(unsigned pos) { 195 removeColumns(pos, 1); 196 } 197 template <typename T> 198 void Matrix<T>::removeColumns(unsigned pos, unsigned count) { 199 if (count == 0) 200 return; 201 assert(pos + count - 1 < nColumns); 202 for (unsigned r = 0; r < nRows; ++r) { 203 for (unsigned c = pos; c < nColumns - count; ++c) 204 at(r, c) = at(r, c + count); 205 for (unsigned c = nColumns - count; c < nColumns; ++c) 206 at(r, c) = 0; 207 } 208 nColumns -= count; 209 } 210 211 template <typename T> 212 void Matrix<T>::insertRow(unsigned pos) { 213 insertRows(pos, 1); 214 } 215 template <typename T> 216 void Matrix<T>::insertRows(unsigned pos, unsigned count) { 217 if (count == 0) 218 return; 219 220 assert(pos <= nRows); 221 resizeVertically(nRows + count); 222 for (int r = nRows - 1; r >= int(pos + count); --r) 223 copyRow(r - count, r); 224 for (int r = pos + count - 1; r >= int(pos); --r) 225 for (unsigned c = 0; c < nColumns; ++c) 226 at(r, c) = 0; 227 } 228 229 template <typename T> 230 void Matrix<T>::removeRow(unsigned pos) { 231 removeRows(pos, 1); 232 } 233 template <typename T> 234 void Matrix<T>::removeRows(unsigned pos, unsigned count) { 235 if (count == 0) 236 return; 237 assert(pos + count - 1 <= nRows); 238 for (unsigned r = pos; r + count < nRows; ++r) 239 copyRow(r + count, r); 240 resizeVertically(nRows - count); 241 } 242 243 template <typename T> 244 void Matrix<T>::copyRow(unsigned sourceRow, unsigned targetRow) { 245 if (sourceRow == targetRow) 246 return; 247 for (unsigned c = 0; c < nColumns; ++c) 248 at(targetRow, c) = at(sourceRow, c); 249 } 250 251 template <typename T> 252 void Matrix<T>::fillRow(unsigned row, const T &value) { 253 for (unsigned col = 0; col < nColumns; ++col) 254 at(row, col) = value; 255 } 256 257 // moveColumns is implemented by moving the columns adjacent to the source range 258 // to their final position. When moving right (i.e. dstPos > srcPos), the range 259 // of the adjacent columns is [srcPos + num, dstPos + num). When moving left 260 // (i.e. dstPos < srcPos) the range of the adjacent columns is [dstPos, srcPos). 261 // First, zeroed out columns are inserted in the final positions of the adjacent 262 // columns. Then, the adjacent columns are moved to their final positions by 263 // swapping them with the zeroed columns. Finally, the now zeroed adjacent 264 // columns are deleted. 265 template <typename T> 266 void Matrix<T>::moveColumns(unsigned srcPos, unsigned num, unsigned dstPos) { 267 if (num == 0) 268 return; 269 270 int offset = dstPos - srcPos; 271 if (offset == 0) 272 return; 273 274 assert(srcPos + num <= getNumColumns() && 275 "move source range exceeds matrix columns"); 276 assert(dstPos + num <= getNumColumns() && 277 "move destination range exceeds matrix columns"); 278 279 unsigned insertCount = offset > 0 ? offset : -offset; 280 unsigned finalAdjStart = offset > 0 ? srcPos : srcPos + num; 281 unsigned curAdjStart = offset > 0 ? srcPos + num : dstPos; 282 // TODO: This can be done using std::rotate. 283 // Insert new zero columns in the positions where the adjacent columns are to 284 // be moved. 285 insertColumns(finalAdjStart, insertCount); 286 // Update curAdjStart if insertion of new columns invalidates it. 287 if (finalAdjStart < curAdjStart) 288 curAdjStart += insertCount; 289 290 // Swap the adjacent columns with inserted zero columns. 291 for (unsigned i = 0; i < insertCount; ++i) 292 swapColumns(finalAdjStart + i, curAdjStart + i); 293 294 // Delete the now redundant zero columns. 295 removeColumns(curAdjStart, insertCount); 296 } 297 298 template <typename T> 299 void Matrix<T>::addToRow(unsigned sourceRow, unsigned targetRow, 300 const T &scale) { 301 addToRow(targetRow, getRow(sourceRow), scale); 302 } 303 304 template <typename T> 305 void Matrix<T>::addToRow(unsigned row, ArrayRef<T> rowVec, const T &scale) { 306 if (scale == 0) 307 return; 308 for (unsigned col = 0; col < nColumns; ++col) 309 at(row, col) += scale * rowVec[col]; 310 } 311 312 template <typename T> 313 void Matrix<T>::scaleRow(unsigned row, const T &scale) { 314 for (unsigned col = 0; col < nColumns; ++col) 315 at(row, col) *= scale; 316 } 317 318 template <typename T> 319 void Matrix<T>::addToColumn(unsigned sourceColumn, unsigned targetColumn, 320 const T &scale) { 321 if (scale == 0) 322 return; 323 for (unsigned row = 0, e = getNumRows(); row < e; ++row) 324 at(row, targetColumn) += scale * at(row, sourceColumn); 325 } 326 327 template <typename T> 328 void Matrix<T>::negateColumn(unsigned column) { 329 for (unsigned row = 0, e = getNumRows(); row < e; ++row) 330 at(row, column) = -at(row, column); 331 } 332 333 template <typename T> 334 void Matrix<T>::negateRow(unsigned row) { 335 for (unsigned column = 0, e = getNumColumns(); column < e; ++column) 336 at(row, column) = -at(row, column); 337 } 338 339 template <typename T> 340 void Matrix<T>::negateMatrix() { 341 for (unsigned row = 0; row < nRows; ++row) 342 negateRow(row); 343 } 344 345 template <typename T> 346 SmallVector<T, 8> Matrix<T>::preMultiplyWithRow(ArrayRef<T> rowVec) const { 347 assert(rowVec.size() == getNumRows() && "Invalid row vector dimension!"); 348 349 SmallVector<T, 8> result(getNumColumns(), T(0)); 350 for (unsigned col = 0, e = getNumColumns(); col < e; ++col) 351 for (unsigned i = 0, e = getNumRows(); i < e; ++i) 352 result[col] += rowVec[i] * at(i, col); 353 return result; 354 } 355 356 template <typename T> 357 SmallVector<T, 8> Matrix<T>::postMultiplyWithColumn(ArrayRef<T> colVec) const { 358 assert(getNumColumns() == colVec.size() && 359 "Invalid column vector dimension!"); 360 361 SmallVector<T, 8> result(getNumRows(), T(0)); 362 for (unsigned row = 0, e = getNumRows(); row < e; row++) 363 for (unsigned i = 0, e = getNumColumns(); i < e; i++) 364 result[row] += at(row, i) * colVec[i]; 365 return result; 366 } 367 368 /// Set M(row, targetCol) to its remainder on division by M(row, sourceCol) 369 /// by subtracting from column targetCol an appropriate integer multiple of 370 /// sourceCol. This brings M(row, targetCol) to the range [0, M(row, 371 /// sourceCol)). Apply the same column operation to otherMatrix, with the same 372 /// integer multiple. 373 static void modEntryColumnOperation(Matrix<DynamicAPInt> &m, unsigned row, 374 unsigned sourceCol, unsigned targetCol, 375 Matrix<DynamicAPInt> &otherMatrix) { 376 assert(m(row, sourceCol) != 0 && "Cannot divide by zero!"); 377 assert(m(row, sourceCol) > 0 && "Source must be positive!"); 378 DynamicAPInt ratio = -floorDiv(m(row, targetCol), m(row, sourceCol)); 379 m.addToColumn(sourceCol, targetCol, ratio); 380 otherMatrix.addToColumn(sourceCol, targetCol, ratio); 381 } 382 383 template <typename T> 384 Matrix<T> Matrix<T>::getSubMatrix(unsigned fromRow, unsigned toRow, 385 unsigned fromColumn, 386 unsigned toColumn) const { 387 assert(fromRow <= toRow && "end of row range must be after beginning!"); 388 assert(toRow < nRows && "end of row range out of bounds!"); 389 assert(fromColumn <= toColumn && 390 "end of column range must be after beginning!"); 391 assert(toColumn < nColumns && "end of column range out of bounds!"); 392 Matrix<T> subMatrix(toRow - fromRow + 1, toColumn - fromColumn + 1); 393 for (unsigned i = fromRow; i <= toRow; ++i) 394 for (unsigned j = fromColumn; j <= toColumn; ++j) 395 subMatrix(i - fromRow, j - fromColumn) = at(i, j); 396 return subMatrix; 397 } 398 399 template <typename T> 400 void Matrix<T>::print(raw_ostream &os) const { 401 PrintTableMetrics ptm = {0, 0, "-"}; 402 for (unsigned row = 0; row < nRows; ++row) 403 for (unsigned column = 0; column < nColumns; ++column) 404 updatePrintMetrics<T>(at(row, column), ptm); 405 unsigned MIN_SPACING = 1; 406 for (unsigned row = 0; row < nRows; ++row) { 407 for (unsigned column = 0; column < nColumns; ++column) { 408 printWithPrintMetrics<T>(os, at(row, column), MIN_SPACING, ptm); 409 } 410 os << "\n"; 411 } 412 } 413 414 /// We iterate over the `indicator` bitset, checking each bit. If a bit is 1, 415 /// we append it to one matrix, and if it is zero, we append it to the other. 416 template <typename T> 417 std::pair<Matrix<T>, Matrix<T>> 418 Matrix<T>::splitByBitset(ArrayRef<int> indicator) { 419 Matrix<T> rowsForOne(0, nColumns), rowsForZero(0, nColumns); 420 for (unsigned i = 0; i < nRows; i++) { 421 if (indicator[i] == 1) 422 rowsForOne.appendExtraRow(getRow(i)); 423 else 424 rowsForZero.appendExtraRow(getRow(i)); 425 } 426 return {rowsForOne, rowsForZero}; 427 } 428 429 template <typename T> 430 void Matrix<T>::dump() const { 431 print(llvm::errs()); 432 } 433 434 template <typename T> 435 bool Matrix<T>::hasConsistentState() const { 436 if (data.size() != nRows * nReservedColumns) 437 return false; 438 if (nColumns > nReservedColumns) 439 return false; 440 #ifdef EXPENSIVE_CHECKS 441 for (unsigned r = 0; r < nRows; ++r) 442 for (unsigned c = nColumns; c < nReservedColumns; ++c) 443 if (data[r * nReservedColumns + c] != 0) 444 return false; 445 #endif 446 return true; 447 } 448 449 namespace mlir { 450 namespace presburger { 451 template class Matrix<DynamicAPInt>; 452 template class Matrix<Fraction>; 453 } // namespace presburger 454 } // namespace mlir 455 456 IntMatrix IntMatrix::identity(unsigned dimension) { 457 IntMatrix matrix(dimension, dimension); 458 for (unsigned i = 0; i < dimension; ++i) 459 matrix(i, i) = 1; 460 return matrix; 461 } 462 463 std::pair<IntMatrix, IntMatrix> IntMatrix::computeHermiteNormalForm() const { 464 // We start with u as an identity matrix and perform operations on h until h 465 // is in hermite normal form. We apply the same sequence of operations on u to 466 // obtain a transform that takes h to hermite normal form. 467 IntMatrix h = *this; 468 IntMatrix u = IntMatrix::identity(h.getNumColumns()); 469 470 unsigned echelonCol = 0; 471 // Invariant: in all rows above row, all columns from echelonCol onwards 472 // are all zero elements. In an iteration, if the curent row has any non-zero 473 // elements echelonCol onwards, we bring one to echelonCol and use it to 474 // make all elements echelonCol + 1 onwards zero. 475 for (unsigned row = 0; row < h.getNumRows(); ++row) { 476 // Search row for a non-empty entry, starting at echelonCol. 477 unsigned nonZeroCol = echelonCol; 478 for (unsigned e = h.getNumColumns(); nonZeroCol < e; ++nonZeroCol) { 479 if (h(row, nonZeroCol) == 0) 480 continue; 481 break; 482 } 483 484 // Continue to the next row with the same echelonCol if this row is all 485 // zeros from echelonCol onwards. 486 if (nonZeroCol == h.getNumColumns()) 487 continue; 488 489 // Bring the non-zero column to echelonCol. This doesn't affect rows 490 // above since they are all zero at these columns. 491 if (nonZeroCol != echelonCol) { 492 h.swapColumns(nonZeroCol, echelonCol); 493 u.swapColumns(nonZeroCol, echelonCol); 494 } 495 496 // Make h(row, echelonCol) non-negative. 497 if (h(row, echelonCol) < 0) { 498 h.negateColumn(echelonCol); 499 u.negateColumn(echelonCol); 500 } 501 502 // Make all the entries in row after echelonCol zero. 503 for (unsigned i = echelonCol + 1, e = h.getNumColumns(); i < e; ++i) { 504 // We make h(row, i) non-negative, and then apply the Euclidean GCD 505 // algorithm to (row, i) and (row, echelonCol). At the end, one of them 506 // has value equal to the gcd of the two entries, and the other is zero. 507 508 if (h(row, i) < 0) { 509 h.negateColumn(i); 510 u.negateColumn(i); 511 } 512 513 unsigned targetCol = i, sourceCol = echelonCol; 514 // At every step, we set h(row, targetCol) %= h(row, sourceCol), and 515 // swap the indices sourceCol and targetCol. (not the columns themselves) 516 // This modulo is implemented as a subtraction 517 // h(row, targetCol) -= quotient * h(row, sourceCol), 518 // where quotient = floor(h(row, targetCol) / h(row, sourceCol)), 519 // which brings h(row, targetCol) to the range [0, h(row, sourceCol)). 520 // 521 // We are only allowed column operations; we perform the above 522 // for every row, i.e., the above subtraction is done as a column 523 // operation. This does not affect any rows above us since they are 524 // guaranteed to be zero at these columns. 525 while (h(row, targetCol) != 0 && h(row, sourceCol) != 0) { 526 modEntryColumnOperation(h, row, sourceCol, targetCol, u); 527 std::swap(targetCol, sourceCol); 528 } 529 530 // One of (row, echelonCol) and (row, i) is zero and the other is the gcd. 531 // Make it so that (row, echelonCol) holds the non-zero value. 532 if (h(row, echelonCol) == 0) { 533 h.swapColumns(i, echelonCol); 534 u.swapColumns(i, echelonCol); 535 } 536 } 537 538 // Make all entries before echelonCol non-negative and strictly smaller 539 // than the pivot entry. 540 for (unsigned i = 0; i < echelonCol; ++i) 541 modEntryColumnOperation(h, row, echelonCol, i, u); 542 543 ++echelonCol; 544 } 545 546 return {h, u}; 547 } 548 549 DynamicAPInt IntMatrix::normalizeRow(unsigned row, unsigned cols) { 550 return normalizeRange(getRow(row).slice(0, cols)); 551 } 552 553 DynamicAPInt IntMatrix::normalizeRow(unsigned row) { 554 return normalizeRow(row, getNumColumns()); 555 } 556 557 DynamicAPInt IntMatrix::determinant(IntMatrix *inverse) const { 558 assert(nRows == nColumns && 559 "determinant can only be calculated for square matrices!"); 560 561 FracMatrix m(*this); 562 563 FracMatrix fracInverse(nRows, nColumns); 564 DynamicAPInt detM = m.determinant(&fracInverse).getAsInteger(); 565 566 if (detM == 0) 567 return DynamicAPInt(0); 568 569 if (!inverse) 570 return detM; 571 572 *inverse = IntMatrix(nRows, nColumns); 573 for (unsigned i = 0; i < nRows; i++) 574 for (unsigned j = 0; j < nColumns; j++) 575 inverse->at(i, j) = (fracInverse.at(i, j) * detM).getAsInteger(); 576 577 return detM; 578 } 579 580 FracMatrix FracMatrix::identity(unsigned dimension) { 581 return Matrix::identity(dimension); 582 } 583 584 FracMatrix::FracMatrix(IntMatrix m) 585 : FracMatrix(m.getNumRows(), m.getNumColumns()) { 586 for (unsigned i = 0, r = m.getNumRows(); i < r; i++) 587 for (unsigned j = 0, c = m.getNumColumns(); j < c; j++) 588 this->at(i, j) = m.at(i, j); 589 } 590 591 Fraction FracMatrix::determinant(FracMatrix *inverse) const { 592 assert(nRows == nColumns && 593 "determinant can only be calculated for square matrices!"); 594 595 FracMatrix m(*this); 596 FracMatrix tempInv(nRows, nColumns); 597 if (inverse) 598 tempInv = FracMatrix::identity(nRows); 599 600 Fraction a, b; 601 // Make the matrix into upper triangular form using 602 // gaussian elimination with row operations. 603 // If inverse is required, we apply more operations 604 // to turn the matrix into diagonal form. We apply 605 // the same operations to the inverse matrix, 606 // which is initially identity. 607 // Either way, the product of the diagonal elements 608 // is then the determinant. 609 for (unsigned i = 0; i < nRows; i++) { 610 if (m(i, i) == 0) 611 // First ensure that the diagonal 612 // element is nonzero, by swapping 613 // it with a nonzero row. 614 for (unsigned j = i + 1; j < nRows; j++) { 615 if (m(j, i) != 0) { 616 m.swapRows(j, i); 617 if (inverse) 618 tempInv.swapRows(j, i); 619 break; 620 } 621 } 622 623 b = m.at(i, i); 624 if (b == 0) 625 return 0; 626 627 // Set all elements above the 628 // diagonal to zero. 629 if (inverse) { 630 for (unsigned j = 0; j < i; j++) { 631 if (m.at(j, i) == 0) 632 continue; 633 a = m.at(j, i); 634 // Set element (j, i) to zero 635 // by subtracting the ith row, 636 // appropriately scaled. 637 m.addToRow(i, j, -a / b); 638 tempInv.addToRow(i, j, -a / b); 639 } 640 } 641 642 // Set all elements below the 643 // diagonal to zero. 644 for (unsigned j = i + 1; j < nRows; j++) { 645 if (m.at(j, i) == 0) 646 continue; 647 a = m.at(j, i); 648 // Set element (j, i) to zero 649 // by subtracting the ith row, 650 // appropriately scaled. 651 m.addToRow(i, j, -a / b); 652 if (inverse) 653 tempInv.addToRow(i, j, -a / b); 654 } 655 } 656 657 // Now only diagonal elements of m are nonzero, but they are 658 // not necessarily 1. To get the true inverse, we should 659 // normalize them and apply the same scale to the inverse matrix. 660 // For efficiency we skip scaling m and just scale tempInv appropriately. 661 if (inverse) { 662 for (unsigned i = 0; i < nRows; i++) 663 for (unsigned j = 0; j < nRows; j++) 664 tempInv.at(i, j) = tempInv.at(i, j) / m(i, i); 665 666 *inverse = std::move(tempInv); 667 } 668 669 Fraction determinant = 1; 670 for (unsigned i = 0; i < nRows; i++) 671 determinant *= m.at(i, i); 672 673 return determinant; 674 } 675 676 FracMatrix FracMatrix::gramSchmidt() const { 677 // Create a copy of the argument to store 678 // the orthogonalised version. 679 FracMatrix orth(*this); 680 681 // For each vector (row) in the matrix, subtract its unit 682 // projection along each of the previous vectors. 683 // This ensures that it has no component in the direction 684 // of any of the previous vectors. 685 for (unsigned i = 1, e = getNumRows(); i < e; i++) { 686 for (unsigned j = 0; j < i; j++) { 687 Fraction jNormSquared = dotProduct(orth.getRow(j), orth.getRow(j)); 688 assert(jNormSquared != 0 && "some row became zero! Inputs to this " 689 "function must be linearly independent."); 690 Fraction projectionScale = 691 dotProduct(orth.getRow(i), orth.getRow(j)) / jNormSquared; 692 orth.addToRow(j, i, -projectionScale); 693 } 694 } 695 return orth; 696 } 697 698 // Convert the matrix, interpreted (row-wise) as a basis 699 // to an LLL-reduced basis. 700 // 701 // This is an implementation of the algorithm described in 702 // "Factoring polynomials with rational coefficients" by 703 // A. K. Lenstra, H. W. Lenstra Jr., L. Lovasz. 704 // 705 // Let {b_1, ..., b_n} be the current basis and 706 // {b_1*, ..., b_n*} be the Gram-Schmidt orthogonalised 707 // basis (unnormalized). 708 // Define the Gram-Schmidt coefficients μ_ij as 709 // (b_i • b_j*) / (b_j* • b_j*), where (•) represents the inner product. 710 // 711 // We iterate starting from the second row to the last row. 712 // 713 // For the kth row, we first check μ_kj for all rows j < k. 714 // We subtract b_j (scaled by the integer nearest to μ_kj) 715 // from b_k. 716 // 717 // Now, we update k. 718 // If b_k and b_{k-1} satisfy the Lovasz condition 719 // |b_k|^2 ≥ (δ - μ_k{k-1}^2) |b_{k-1}|^2, 720 // we are done and we increment k. 721 // Otherwise, we swap b_k and b_{k-1} and decrement k. 722 // 723 // We repeat this until k = n and return. 724 void FracMatrix::LLL(Fraction delta) { 725 DynamicAPInt nearest; 726 Fraction mu; 727 728 // `gsOrth` holds the Gram-Schmidt orthogonalisation 729 // of the matrix at all times. It is recomputed every 730 // time the matrix is modified during the algorithm. 731 // This is naive and can be optimised. 732 FracMatrix gsOrth = gramSchmidt(); 733 734 // We start from the second row. 735 unsigned k = 1; 736 while (k < getNumRows()) { 737 for (unsigned j = k - 1; j < k; j--) { 738 // Compute the Gram-Schmidt coefficient μ_jk. 739 mu = dotProduct(getRow(k), gsOrth.getRow(j)) / 740 dotProduct(gsOrth.getRow(j), gsOrth.getRow(j)); 741 nearest = round(mu); 742 // Subtract b_j scaled by the integer nearest to μ_jk from b_k. 743 addToRow(k, getRow(j), -Fraction(nearest, 1)); 744 gsOrth = gramSchmidt(); // Update orthogonalization. 745 } 746 mu = dotProduct(getRow(k), gsOrth.getRow(k - 1)) / 747 dotProduct(gsOrth.getRow(k - 1), gsOrth.getRow(k - 1)); 748 // Check the Lovasz condition for b_k and b_{k-1}. 749 if (dotProduct(gsOrth.getRow(k), gsOrth.getRow(k)) > 750 (delta - mu * mu) * 751 dotProduct(gsOrth.getRow(k - 1), gsOrth.getRow(k - 1))) { 752 // If it is satisfied, proceed to the next k. 753 k += 1; 754 } else { 755 // If it is not satisfied, decrement k (without 756 // going beyond the second row). 757 swapRows(k, k - 1); 758 gsOrth = gramSchmidt(); // Update orthogonalization. 759 k = k > 1 ? k - 1 : 1; 760 } 761 } 762 } 763 764 IntMatrix FracMatrix::normalizeRows() const { 765 unsigned numRows = getNumRows(); 766 unsigned numColumns = getNumColumns(); 767 IntMatrix normalized(numRows, numColumns); 768 769 DynamicAPInt lcmDenoms = DynamicAPInt(1); 770 for (unsigned i = 0; i < numRows; i++) { 771 // For a row, first compute the LCM of the denominators. 772 for (unsigned j = 0; j < numColumns; j++) 773 lcmDenoms = lcm(lcmDenoms, at(i, j).den); 774 // Then, multiply by it throughout and convert to integers. 775 for (unsigned j = 0; j < numColumns; j++) 776 normalized(i, j) = (at(i, j) * lcmDenoms).getAsInteger(); 777 } 778 return normalized; 779 } 780