001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.math.matrix; 031 032import gov.sandia.cognition.math.matrix.DimensionalityMismatchException; 033import gov.sandia.cognition.math.matrix.Matrix; 034import gov.sandia.cognition.math.matrix.MatrixEntry; 035import gov.sandia.cognition.math.matrix.MatrixFactory; 036import gov.sandia.cognition.math.matrix.Vector; 037import gov.sandia.cognition.math.matrix.VectorEntry; 038import gov.sandia.cognition.math.matrix.mtj.DenseMatrixFactoryMTJ; 039import gov.sandia.cognition.math.matrix.mtj.SparseColumnMatrix; 040import gov.sandia.cognition.math.matrix.mtj.SparseMatrixFactoryMTJ; 041import gov.sandia.cognition.math.matrix.mtj.SparseRowMatrix; 042import gov.sandia.cognition.math.matrix.mtj.SparseVector; 043 044import java.util.Random; 045 046import no.uib.cipr.matrix.sparse.FlexCompColMatrix; 047import no.uib.cipr.matrix.sparse.FlexCompRowMatrix; 048 049import com.jmatio.types.MLArray; 050import com.jmatio.types.MLDouble; 051 052/** 053 * Utility methods for dealing with the cognitive foundry {@link Matrix}. 054 * 055 * @author Sina Samangooei (ss@ecs.soton.ac.uk) 056 * 057 */ 058public class CFMatrixUtils { 059 private static final double EPS = 1e-100; 060 061 /** 062 * Create a new matrix with the absolute values of the input matrix. 063 * 064 * @param mat 065 * input matrix 066 * @return absolute matrix 067 */ 068 public static Matrix abs(Matrix mat) { 069 final Matrix ret = mat.clone(); 070 final int nrows = ret.getNumRows(); 071 final int ncols = ret.getNumColumns(); 072 for (int r = 0; r < nrows; r++) { 073 for (int c = 0; c < ncols; c++) { 074 ret.setElement(r, c, Math.abs(mat.getElement(r, c))); 075 } 076 } 077 return ret; 078 } 079 080 /** 081 * Compute the absolute sum of values in the matrix 082 * 083 * @param mat 084 * the matrix 085 * @return the absolute sum 086 */ 087 public static double absSum(Matrix mat) { 088 if (mat instanceof SparseColumnMatrix) { 089 return absSumSparse((SparseColumnMatrix) mat); 090 } else if (mat instanceof SparseRowMatrix) { 091 return absSumSparse((SparseRowMatrix) mat); 092 } 093 094 double tot = 0; 095 final int nrows = mat.getNumRows(); 096 final int ncols = mat.getNumColumns(); 097 for (int r = 0; r < nrows; r++) { 098 for (int c = 0; c < ncols; c++) { 099 final double element = mat.getElement(r, c); 100 if (Double.isNaN(element)) { 101 throw new RuntimeException("hmm?"); 102 } 103 tot += Math.abs(element); 104 } 105 } 106 return tot; 107 } 108 109 /** 110 * @param mat 111 * @return sum of a {@link SparseColumnMatrix} 112 */ 113 public static double absSumSparse(SparseColumnMatrix mat) { 114 double sum = 0; 115 for (int i = 0; i < mat.getNumColumns(); i++) { 116 sum += mat.getColumn(i).sum(); 117 } 118 return sum; 119 } 120 121 /** 122 * @param mat 123 * @return sum of a {@link SparseRowMatrix} 124 */ 125 public static double absSumSparse(SparseRowMatrix mat) { 126 double sum = 0; 127 for (int i = 0; i < mat.getNumRows(); i++) { 128 sum += mat.getRow(i).sum(); 129 } 130 return sum; 131 } 132 133 /** 134 * Multiply a matrix by a constant, storing the results in the input matrix. 135 * 136 * @param mat 137 * the matrix 138 * @param etat 139 * the constant 140 * @return the input matrix 141 */ 142 public static Matrix timesInplace(Matrix mat, double etat) { 143 mat.scaleEquals(etat); 144 return mat; 145 } 146 147 /** 148 * Convert a matlab {@link MLArray} to a {@link Matrix} 149 * 150 * @param mlArray 151 * the matrlab matrix 152 * @return the matrix 153 */ 154 public static Matrix asMat(MLArray mlArray) { 155 final MLDouble mlArrayDbl = (MLDouble) mlArray; 156 final int rows = mlArray.getM(); 157 final int cols = mlArray.getN(); 158 159 final Matrix mat = SparseMatrixFactoryMTJ.INSTANCE.createMatrix(rows, cols); 160 161 for (int r = 0; r < rows; r++) { 162 for (int c = 0; c < cols; c++) { 163 mat.setElement(r, c, mlArrayDbl.get(r, c)); 164 } 165 } 166 return mat; 167 } 168 169 /** 170 * Convert a matlab {@link MLArray} to a {@link Matrix} 171 * 172 * @param name 173 * @param mat 174 * 175 * @return the matrix 176 */ 177 public static MLArray toMLArray(String name, Matrix mat) { 178 final MLDouble mlArrayDbl = new MLDouble(name, new int[] { mat.getNumRows(), mat.getNumColumns() }); 179 180 for (final MatrixEntry matrixEntry : mat) { 181 mlArrayDbl.set(matrixEntry.getValue(), matrixEntry.getRowIndex(), matrixEntry.getColumnIndex()); 182 } 183 return mlArrayDbl; 184 } 185 186 /** 187 * Compute the proportion of completely zero rows to rows with non-zero 188 * values 189 * 190 * @param mat 191 * the matrix 192 * @return the row sparsity 193 */ 194 public static double rowSparsity(Matrix mat) { 195 final double nrows = mat.getNumRows(); 196 double nsparse = 0; 197 for (int r = 0; r < nrows; r++) { 198 if (mat.getRow(r).sum() == 0) { 199 nsparse++; 200 } 201 } 202 return nsparse / nrows; 203 } 204 205 /** 206 * Compute the proportion of completely zero columns to columns with 207 * non-zero values 208 * 209 * @param mat 210 * the matrix 211 * @return the column sparsity 212 */ 213 public static double colSparcity(Matrix mat) { 214 final double ncols = mat.getNumColumns(); 215 double nsparse = 0; 216 for (int c = 0; c < ncols; c++) { 217 if (mat.getColumn(c).sum() == 0) { 218 nsparse++; 219 } 220 } 221 return nsparse / ncols; 222 } 223 224 /** 225 * Add a constant to the matrix, returning the input matrix 226 * 227 * @param mat 228 * the matrix 229 * @param etat 230 * the constant 231 * @return the matrix 232 */ 233 public static Matrix plusInplace(Matrix mat, double etat) { 234 final int nrows = mat.getNumRows(); 235 final int ncols = mat.getNumColumns(); 236 for (int r = 0; r < nrows; r++) { 237 for (int c = 0; c < ncols; c++) { 238 mat.setElement(r, c, mat.getElement(r, c) + etat); 239 } 240 } 241 return mat; 242 } 243 244 /** 245 * Extract the diagonal elements as a vector 246 * 247 * @param mat 248 * the matrix to extract from 249 * @return the diagonal 250 */ 251 public static Vector diag(Matrix mat) { 252 Vector ret; 253 254 if (mat.getNumColumns() > mat.getNumRows()) { 255 ret = mat.getRow(0); 256 } 257 else { 258 ret = mat.getColumn(0); 259 } 260 final int rowcol = ret.getDimensionality(); 261 for (int rc = 0; rc < rowcol; rc++) { 262 ret.setElement(rc, mat.getElement(rc, rc)); 263 } 264 return ret; 265 } 266 267 /** 268 * Stack matrices vertically 269 * 270 * @param matrixFactory 271 * factory to create output matrix 272 * @param matricies 273 * matrices to stack 274 * @return matrix created from the stacking 275 */ 276 public static Matrix vstack(MatrixFactory<? extends Matrix> matrixFactory, Matrix... matricies) { 277 int nrows = 0; 278 int ncols = 0; 279 for (final Matrix matrix : matricies) { 280 nrows += matrix.getNumRows(); 281 ncols = matrix.getNumColumns(); 282 } 283 final Matrix ret = matrixFactory.createMatrix(nrows, ncols); 284 int currentRow = 0; 285 for (final Matrix matrix : matricies) { 286 ret.setSubMatrix(currentRow, 0, matrix); 287 currentRow += matrix.getNumRows(); 288 } 289 return ret; 290 } 291 292 /** 293 * Stack matrices vertically 294 * 295 * @param matricies 296 * matrices to stack 297 * @return matrix created from the stacking 298 */ 299 public static Matrix vstack(Matrix... matricies) { 300 return vstack(MatrixFactory.getDefault(), matricies); 301 } 302 303 /** 304 * Get the data array backing this matrix (in column-major format) 305 * 306 * @param w 307 * the matrix 308 * @return the data 309 */ 310 public static double[] getData(Matrix w) { 311 return ((no.uib.cipr.matrix.DenseMatrix) DenseMatrixFactoryMTJ.INSTANCE.copyMatrix(w).getInternalMatrix()) 312 .getData(); 313 } 314 315 /** 316 * Get the minimum element value 317 * 318 * @param u 319 * the matrix 320 * @return the min value 321 */ 322 public static double min(Matrix u) { 323 double min = Double.MAX_VALUE; 324 for (final MatrixEntry matrixEntry : u) { 325 min = Math.min(min, matrixEntry.getValue()); 326 } 327 return min; 328 } 329 330 /** 331 * Get the maximum element value 332 * 333 * @param u 334 * the matrix 335 * @return the max value 336 */ 337 public static double max(Matrix u) { 338 double max = -Double.MAX_VALUE; 339 for (final MatrixEntry matrixEntry : u) { 340 max = Math.max(max, matrixEntry.getValue()); 341 } 342 return max; 343 } 344 345 /** 346 * Get the minimum element value 347 * 348 * @param column 349 * the vector 350 * @return the min value 351 */ 352 public static double min(Vector column) { 353 double min = Double.MAX_VALUE; 354 for (final VectorEntry vectorEntry : column) { 355 min = Math.min(min, vectorEntry.getValue()); 356 } 357 return min; 358 } 359 360 /** 361 * Get the maximum element value 362 * 363 * @param column 364 * the vector 365 * @return the max value 366 */ 367 public static double max(Vector column) { 368 double max = -Double.MAX_VALUE; 369 for (final VectorEntry vectorEntry : column) { 370 max = Math.max(max, vectorEntry.getValue()); 371 } 372 return max; 373 } 374 375 /** 376 * Compute the sparsity 377 * 378 * @param mat 379 * the matrix 380 * @return the sparsity 381 */ 382 public static double sparsity(Matrix mat) { 383 double count = 0; 384 final double size = mat.getNumRows() * mat.getNumColumns(); 385 if (mat instanceof SparseRowMatrix) { 386 for (int i = 0; i < mat.getNumRows(); i++) { 387 final SparseVector row = (SparseVector) mat.getRow(i); 388 count += row.getNumElementsUsed(); 389 } 390 } else if (mat instanceof SparseColumnMatrix) { 391 for (int i = 0; i < mat.getNumColumns(); i++) { 392 final SparseVector col = (SparseVector) mat.getColumn(i); 393 count += col.getNumElementsUsed(); 394 } 395 } else { 396 for (final MatrixEntry matrixEntry : mat) { 397 if (matrixEntry.getValue() != 0) 398 count++; 399 } 400 } 401 402 return (size - count) / size; 403 } 404 405 /** 406 * Bring each element to the power d 407 * 408 * @param degree 409 * @param d 410 * @return the input 411 */ 412 public static <T extends Matrix> T powInplace(T degree, double d) { 413 for (final MatrixEntry ent : degree) { 414 degree.setElement(ent.getRowIndex(), ent.getColumnIndex(), Math.pow(ent.getValue(), d)); 415 } 416 return degree; 417 } 418 419 /** 420 * Bring each element to the power d 421 * 422 * @param degree 423 * @param d 424 * @return the input 425 */ 426 public static <T extends Vector> T powInplace(T degree, double d) { 427 for (final VectorEntry ent : degree) { 428 degree.setElement(ent.getIndex(), Math.pow(ent.getValue(), d)); 429 } 430 return degree; 431 } 432 433 /** 434 * @param laplacian 435 * @return copy and convert 436 */ 437 public static Jama.Matrix asJama(Matrix laplacian) { 438 439 final Jama.Matrix ret = new Jama.Matrix(laplacian.getNumRows(), laplacian.getNumColumns()); 440 for (final MatrixEntry matrixEntry : laplacian) { 441 ret.set(matrixEntry.getRowIndex(), matrixEntry.getColumnIndex(), matrixEntry.getValue()); 442 } 443 444 return ret; 445 } 446 447 /** 448 * @param vt 449 * @return mean of each row 450 */ 451 public static Vector rowMean(Matrix vt) { 452 final Vector sumOfColumns = vt.sumOfColumns(); 453 sumOfColumns.scaleEquals(1. / vt.getNumColumns()); 454 return sumOfColumns; 455 } 456 457 /** 458 * @param vt 459 * @return mean of each row 460 */ 461 public static Vector colMean(Matrix vt) { 462 final Vector sumOfColumns = vt.sumOfRows(); 463 sumOfColumns.scaleEquals(1. / vt.getNumRows()); 464 return sumOfColumns; 465 } 466 467 /** 468 * @param A 469 * @param col 470 */ 471 public static void minusEqualsCol(Matrix A, Vector col) { 472 for (int i = 0; i < A.getNumRows(); i++) { 473 for (int j = 0; j < A.getNumColumns(); j++) { 474 A.setElement(i, j, A.getElement(i, j) - col.getElement(i)); 475 } 476 } 477 } 478 479 /** 480 * @param A 481 * @param row 482 */ 483 public static void minusEqualsRow(Matrix A, Vector row) { 484 for (int i = 0; i < A.getNumRows(); i++) { 485 for (int j = 0; j < A.getNumColumns(); j++) { 486 A.setElement(i, j, A.getElement(i, j) - row.getElement(j)); 487 } 488 } 489 } 490 491 /** 492 * Create a random {@link SparseColumnMatrix} 493 * 494 * @param rows 495 * number of rows 496 * @param cols 497 * number of columns 498 * @param min 499 * the minimum value 500 * @param max 501 * the maximum value 502 * @param density 503 * the matrix density 504 * @param random 505 * the RNG 506 * @return the random matrix 507 */ 508 public static SparseColumnMatrix randomSparseCol(int rows, int cols, double min, double max, double density, 509 Random random) 510 { 511 final SparseColumnMatrix ret = SparseMatrixFactoryMTJ.INSTANCE.createWrapper(new FlexCompColMatrix(rows, cols)); 512 for (int i = 0; i < cols; i++) { 513 final Vector v = ret.getColumn(i); 514 for (int j = 0; j < rows; j++) { 515 if (random.nextDouble() <= density) { 516 v.setElement(j, random.nextDouble() * (max - min) + min); 517 } 518 } 519 } 520 return ret; 521 } 522 523 /** 524 * Create a random {@link SparseRowMatrix} 525 * 526 * @param rows 527 * number of rows 528 * @param cols 529 * number of columns 530 * @param min 531 * the minimum value 532 * @param max 533 * the maximum value 534 * @param density 535 * the matrix density 536 * @param random 537 * the RNG 538 * @return the random matrix 539 */ 540 public static SparseRowMatrix randomSparseRow(int rows, int cols, double min, double max, double density, 541 Random random) 542 { 543 final SparseRowMatrix ret = SparseMatrixFactoryMTJ.INSTANCE.createWrapper(new FlexCompRowMatrix(rows, cols)); 544 for (int i = 0; i < rows; i++) { 545 final Vector v = ret.getRow(i); 546 for (int j = 0; j < cols; j++) { 547 if (random.nextDouble() <= density) { 548 v.setElement(j, random.nextDouble() * (max - min) + min); 549 } 550 } 551 } 552 return ret; 553 } 554 555 /** 556 * @param a 557 * @param b 558 * @return performs a dot product taking advantage of the sparcity in b and 559 * a 560 */ 561 public static SparseRowMatrix fastsparsedot(SparseRowMatrix a, SparseColumnMatrix b) { 562 final SparseRowMatrix ret = SparseMatrixFactoryMTJ.INSTANCE.createMatrix(a.getNumRows(), b.getNumColumns()); 563 if (a.getNumColumns() != b.getNumRows()) { 564 return null; 565 } 566 for (int i = 0; i < a.getNumRows(); i++) { 567 final no.uib.cipr.matrix.sparse.SparseVector arow = a.getInternalMatrix().getRow(i); 568 for (int j = 0; j < b.getNumColumns(); j++) { 569 double v = 0; 570 final no.uib.cipr.matrix.sparse.SparseVector bcol = b.getInternalMatrix().getColumn(j); 571 final no.uib.cipr.matrix.sparse.SparseVector leading; 572 final no.uib.cipr.matrix.sparse.SparseVector other; 573 if (arow.getUsed() < bcol.getUsed()) { 574 leading = arow; 575 other = bcol; 576 } else { 577 leading = bcol; 578 other = arow; 579 } 580 for (final no.uib.cipr.matrix.VectorEntry vectorEntry : leading) { 581 v += vectorEntry.get() * other.get(vectorEntry.index()); 582 } 583 if (Math.abs(v) > EPS) { 584 ret.setElement(i, j, v); 585 } 586 } 587 } 588 589 return ret; 590 } 591 592 /** 593 * @param a 594 * @param b 595 * @return checks for the fastest way to do this dot product 596 */ 597 public static Matrix fastdot(Matrix a, Matrix b) { 598 if (a instanceof SparseRowMatrix && b instanceof SparseColumnMatrix) { 599 return fastsparsedot((SparseRowMatrix) a, (SparseColumnMatrix) b); 600 } 601 return a.times(b); 602 } 603 604 /** 605 * @param a 606 * @return turns the provided matrix into a {@link SparseColumnMatrix} 607 */ 608 public static SparseColumnMatrix asSparseColumn(Matrix a) { 609 return SparseMatrixFactoryMTJ.INSTANCE.createWrapper( 610 new FlexCompColMatrix( 611 SparseMatrixFactoryMTJ.INSTANCE.copyMatrix(a).getInternalMatrix() 612 ) 613 ); 614 } 615 616 /** 617 * Convert a {@link Matrix} to a {@link SparseRowMatrix} 618 * 619 * @param a 620 * @return the {@link SparseRowMatrix} 621 */ 622 public static SparseRowMatrix asSparseRow(Matrix a) { 623 return SparseMatrixFactoryMTJ.INSTANCE.copyMatrix(a); 624 } 625 626 /** 627 * 628 * @param ret 629 * @param c 630 * @param col 631 */ 632 public static void fastsetcol(Matrix ret, int c, Vector col) { 633 if (ret instanceof SparseColumnMatrix && col instanceof SparseVector) { 634 ((SparseColumnMatrix) ret).setColumn(c, (SparseVector) col); 635 } 636 else { 637 ret.setColumn(c, col); 638 } 639 } 640 641 /** 642 * @param A 643 * @param B 644 * @return A - B, done using 645 * {@link #fastsparseminusEquals(SparseColumnMatrix, SparseColumnMatrix)} 646 * if possible 647 */ 648 public static Matrix fastminusEquals(Matrix A, Matrix B) { 649 if (A instanceof SparseColumnMatrix && B instanceof SparseColumnMatrix) { 650 return fastsparseminusEquals((SparseColumnMatrix) A, (SparseColumnMatrix) B); 651 } 652 A.minusEquals(B); 653 return A; 654 } 655 656 /** 657 * @param A 658 * @param B 659 * @return A - B, done using {@link #fastminusEquals(Matrix, Matrix)} if 660 * possible 661 */ 662 public static Matrix fastminus(Matrix A, Matrix B) { 663 return fastminusEquals(A.clone(), B); 664 } 665 666 /** 667 * @param A 668 * @param B 669 * @return A - B 670 */ 671 public static Matrix fastsparseminusEquals(SparseColumnMatrix A, SparseColumnMatrix B) { 672 if (A.getNumColumns() != B.getNumColumns() || A.getNumRows() != B.getNumRows()) { 673 throw new DimensionalityMismatchException(); 674 } 675 final SparseColumnMatrix ret = A; 676 677 final FlexCompColMatrix retint = ret.getInternalMatrix(); 678 final FlexCompColMatrix Bint = B.getInternalMatrix(); 679 for (int i = 0; i < A.getNumColumns(); i++) { 680 final no.uib.cipr.matrix.sparse.SparseVector aCol = retint.getColumn(i); 681 final no.uib.cipr.matrix.sparse.SparseVector bCol = Bint.getColumn(i); 682 aCol.add(-1, bCol); 683 } 684 return ret; 685 } 686 687 /** 688 * @param A 689 * @param B 690 * @return A - B 691 */ 692 public static Matrix fastsparseminusEquals(SparseRowMatrix A, SparseRowMatrix B) { 693 if (A.getNumColumns() != B.getNumColumns() || A.getNumRows() != B.getNumRows()) { 694 throw new DimensionalityMismatchException(); 695 } 696 final SparseRowMatrix ret = A; 697 698 final FlexCompRowMatrix retInt = ret.getInternalMatrix(); 699 final FlexCompRowMatrix bint = B.getInternalMatrix(); 700 for (int i = 0; i < A.getNumRows(); i++) { 701 final no.uib.cipr.matrix.sparse.SparseVector aCol = retInt.getRow(i); 702 final no.uib.cipr.matrix.sparse.SparseVector bCol = bint.getRow(i); 703 aCol.add(-1, bCol); 704 } 705 return ret; 706 } 707 708 /** 709 * @param mat 710 * @return checks elements using {@link Double#isNaN()} 711 */ 712 public static boolean containsNaN(Matrix mat) { 713 for (final MatrixEntry matrixEntry : mat) { 714 if (Double.isNaN(matrixEntry.getValue())) { 715 return true; 716 } 717 } 718 return false; 719 } 720 721 /** 722 * @param vec 723 * @return checks elements using {@link Double#isNaN()} 724 */ 725 public static boolean containsNaN(Vector vec) { 726 for (final VectorEntry vectorEntry : vec) { 727 if (Double.isNaN(vectorEntry.getValue())) { 728 return true; 729 } 730 } 731 return false; 732 } 733 734 /** 735 * @param mat 736 * @return checks elements using {@link Double#isInfinite()} 737 */ 738 public static boolean containsInfinity(Matrix mat) { 739 for (final MatrixEntry matrixEntry : mat) { 740 if (Double.isInfinite(matrixEntry.getValue())) { 741 return true; 742 } 743 } 744 return false; 745 } 746 747 /** 748 * Sum the matrix entries 749 * 750 * @param A 751 * @return the sum 752 */ 753 public static double sum(Matrix A) { 754 double sum = 0; 755 for (final MatrixEntry matrixEntry : A) { 756 sum += matrixEntry.getValue(); 757 } 758 return sum; 759 } 760 761}