github.com/rmera/gochem@v0.7.1/v3/gonum_purego.go (about) 1 // +build ignored 2 3 /* 4 * gonum_go.go, part of gochem. 5 * 6 * Copyright 2012 Raul Mera <rmera{at}chemDOThelsinkiDOTfi> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License as 10 * published by the Free Software Foundation; either version 2.1 of the 11 * License, or (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General 19 * Public License along with this program. If not, see 20 * <http://www.gnu.org/licenses/>. 21 * 22 * Gochem is developed at the laboratory for instruction in Swedish, Department of Chemistry, 23 * University of Helsinki, Finland. 24 * 25 */ 26 27 //Package chem provides atom and molecule structures, facilities for reading and writing some 28 //files used in computational chemistry and some functions for geometric manipulations and shape 29 //indicators. 30 31 //All the *Vec functions will operate/produce column or row vectors depending on whether the matrix underlying Dense 32 //is row or column major. 33 34 package chem 35 36 import ( 37 "fmt" 38 "math" 39 "sort" 40 41 matrix "github.com/skelterjohn/go.matrix" 42 ) 43 44 /*Here I make a -very incomplete- implementation of the gonum api backed by go.matrix, which will enable me to port gochem to gonum. 45 * Since the agreement in the gonum community was NOT to build a temporary implementation, I just build the functions that 46 * gochem uses, on my own type (which should implement all the relevant gonum interfaces). 47 * all the gonum-owned names will start with gn (i.e. RandomFunc becomes gnRandomFunc) so its latter easy to use search and replace to set the 48 * correct import path when gonum is implemented (such as gonum.RandomFunc) 49 */ 50 51 //INTERFACES This part is from GONUM, copyright, the gonum authors. 52 53 //This part of the package is deprecated. Gonum is the only implemantation used in goChem now. 54 55 type Normer interface { 56 Norm(o float64) float64 57 } 58 59 type NormerMatrix interface { 60 Normer 61 Matrix 62 } 63 64 // BlasMatrix represents a cblas native representation of a matrix. 65 type BlasMatrix struct { 66 Rows, Cols int 67 Stride int 68 Data []float64 69 } 70 71 //VecMatrix is a set of vectors in 3D space. The underlying implementation varies. 72 type VecMatrix struct { 73 *Dense 74 } 75 76 //For the pure-go implementation I must implement Dense on top of go.matrix 77 type chemDense struct { 78 *Dense 79 } 80 81 //For the pure-go implementation I must implement Dense on top of go.matrix 82 type Dense struct { 83 *matrix.DenseMatrix 84 } 85 86 //Generate and returns a CoorMatrix with arbitrary shape from data. 87 func newDense(data []float64, rows, cols int) *Dense { 88 if len(data) < cols*rows { 89 panic(notEnoughElements) 90 } 91 return &Dense{matrix.MakeDenseMatrix(data, rows, cols)} 92 93 } 94 95 func det(A *VecMatrix) float64 { 96 return A.Det() 97 98 } 99 100 //Generate and returns a CoorMatrix with arbitrary shape from data. 101 func newchemDense(data []float64, rows, cols int) (*chemDense, error) { 102 if len(data) < cols*rows { 103 return nil, fmt.Errorf(string(notEnoughElements)) 104 } 105 return &chemDense{newDense(data, rows, cols)}, nil 106 107 } 108 109 //NewVecs enerates and returns a VecMatrix with 3 columns from data. 110 func NewVecs(data []float64) (*VecMatrix, error) { 111 ///fmt.Println("JAJAJA BIENVENIDO AL INFIERNO!!!!") //Surely a debugging msg I had forgotten about. It would have rather surprised any Spanish-speaking user :-) 112 const cols int = 3 113 l := len(data) 114 rows := l / cols 115 if l%cols != 0 { 116 return nil, fmt.Errorf("Input slice lenght %d not divisible by %d: %d", rows, cols, rows%cols) 117 } 118 r := newDense(data, rows, cols) 119 return &VecMatrix{r}, nil 120 } 121 122 //Returns and empty, but not nil, Dense. It barely allocates memory 123 func EmptyDense() *Dense { 124 var a *matrix.DenseMatrix 125 return &Dense{a} 126 127 } 128 129 //Returns an zero-filled Dense with the given dimensions 130 //It is to be substituted by the Gonum function. 131 func gnZeros(rows, cols int) *chemDense { 132 return &chemDense{&Dense{matrix.Zeros(rows, cols)}} 133 } 134 135 //Returns an identity matrix spanning span cols and rows 136 func gnEye(span int) *chemDense { 137 A := gnZeros(span, span) 138 for i := 0; i < span; i++ { 139 A.Set(i, i, 1.0) 140 } 141 return A 142 } 143 144 func Eye(span int) *chemDense { 145 return gnEye(span) 146 } 147 148 //Some temporary support function. 149 //func Eigen(in *Dense, epsilon float64) (*Dense, []float64, error) { 150 // i, j, k := gnEigen(in, epsilon) 151 // return i, j, k 152 //} 153 154 //This is a facility to sort Eigenvectors/Eigenvalues pairs 155 //It satisfies the sort.Interface interface. 156 type eigenpair struct { 157 //evecs must have as many rows as evals has elements. 158 evecs *VecMatrix 159 evals sort.Float64Slice 160 } 161 162 func (E eigenpair) Less(i, j int) bool { 163 return E.evals[i] < E.evals[j] 164 } 165 func (E eigenpair) Swap(i, j int) { 166 E.evals.Swap(i, j) 167 // E.evecs[i],E.evecs[j]=E.evecs[j],E.evecs[i] 168 E.evecs.SwapRows(i, j) 169 } 170 func (E eigenpair) Len() int { 171 return len(E.evals) 172 } 173 174 //EigenWrap wraps the matrix.DenseMatrix.Eigen() function in order to guarantee 175 //That the eigenvectors and eigenvalues are sorted according to the eigenvalues 176 //It also guarantees orthonormality and handness. I don't know how many of 177 //these are already guaranteed by Eig(). Will delete the unneeded parts 178 //And even this whole function when sure. 179 func EigenWrap(in *VecMatrix, epsilon float64) (*VecMatrix, []float64, error) { 180 var err error 181 if epsilon < 0 { 182 epsilon = appzero 183 } 184 evecsDM, vals, _ := in.Eigen() 185 evecs := &VecMatrix{&Dense{evecsDM}} 186 // fmt.Println("evecs fresh", evecs) /////// 187 evals := [3]float64{vals.Get(0, 0), vals.Get(1, 1), vals.Get(2, 2)} //go.matrix specific code here. 188 f := func() { evecs.TCopy(evecs) } 189 if err = gnMaybe(gnPanicker(f)); err != nil { 190 return nil, nil, err 191 } 192 eig := eigenpair{evecs, evals[:]} 193 194 // fmt.Println("evecs presort", eig.evecs) ///////// 195 sort.Sort(eig) 196 197 //Here I should orthonormalize vectors if needed instead of just complaining. 198 //I think orthonormality is guaranteed by DenseMatrix.Eig() If it is, Ill delete all this 199 //If not I'll add ortonormalization routines. 200 eigrows, _ := eig.evecs.Dims() 201 // fmt.Println("evecs", eig.evecs) //////////////// 202 for i := 0; i < eigrows; i++ { 203 vectori := eig.evecs.RowView(i) 204 for j := i + 1; j < eigrows; j++ { 205 vectorj := eig.evecs.RowView(j) 206 if math.Abs(vectori.Dot(vectorj)) > epsilon && i != j { 207 return eig.evecs, evals[:], notOrthogonal 208 } 209 } 210 // fmt.Println("VECTORS", eig.evecs) ////////////////////////// 211 if math.Abs(vectori.Norm(2)-1) > epsilon { 212 //Of course I could just normalize the vectors instead of complaining. 213 //err= fmt.Errorf("Vectors not normalized %s",err.Error()) 214 215 } 216 } 217 //Checking and fixing the handness of the matrix.This if-else is Jannes idea, 218 //I don't really know whether it works. 219 eig.evecs.TCopy(eig.evecs) 220 if eig.evecs.Det() < 0 { 221 eig.evecs.Scale(-1, eig.evecs) //SSC 222 } else { 223 /* 224 eig.evecs.TransposeInPlace() 225 eig.evecs.ScaleRow(0,-1) 226 eig.evecs.ScaleRow(2,-1) 227 eig.evecs.TransposeInPlace() 228 */ 229 // fmt.Println("all good, I guess") 230 } 231 eig.evecs.TCopy(eig.evecs) 232 return eig.evecs, eig.evals, err //Returns a slice of evals 233 } 234 235 //Returns the singular value decomposition of matrix A 236 func gnSVD(A *chemDense) (*chemDense, *chemDense, *chemDense) { 237 U, s, V, err := A.SVD() 238 if err != nil { 239 panic(err.Error()) 240 } 241 theU := chemDense{&Dense{U}} 242 sigma := chemDense{&Dense{s}} 243 theV := chemDense{&Dense{V}} 244 return &theU, &sigma, &theV 245 246 } 247 248 //returns a rows,cols matrix filled with gnOnes. 249 func gnOnes(rows, cols int) *chemDense { 250 gnOnes := gnZeros(rows, cols) 251 for i := 0; i < rows; i++ { 252 for j := 0; j < cols; j++ { 253 gnOnes.Set(i, j, 1) 254 } 255 } 256 return gnOnes 257 } 258 259 func gnMul(A, B Matrix) *chemDense { 260 ar, _ := A.Dims() 261 _, bc := B.Dims() 262 C := gnZeros(ar, bc) 263 C.Mul(A, B) 264 return C 265 } 266 267 func gnCopy(A Matrix) *chemDense { 268 r, c := A.Dims() 269 B := gnZeros(r, c) 270 B.Copy(A) 271 return B 272 } 273 274 func gnT(A Matrix) *chemDense { 275 r, c := A.Dims() 276 B := gnZeros(c, r) 277 B.TCopy(A) 278 return B 279 } 280 281 //Methods 282 /* When gonum is ready, all this functions will take a num.Matrix interface as an argument, instead of a 283 * Dense*/ 284 285 func (F *Dense) Add(A, B Matrix) { 286 ar, ac := A.Dims() 287 br, bc := B.Dims() 288 fr, fc := F.Dims() 289 if ac != bc || br != ar || ac != fc || ar != fr { 290 panic(gnErrShape) 291 } 292 for i := 0; i < fr; i++ { 293 for j := 0; j < fc; j++ { 294 F.Set(i, j, A.At(i, j)+B.At(i, j)) 295 } 296 } 297 298 } 299 300 func (F *Dense) BlasMatrix() BlasMatrix { 301 b := new(BlasMatrix) 302 r, c := F.Dims() 303 b.Rows = r 304 b.Cols = c 305 b.Stride = 0 //not really 306 b.Data = F.Array() 307 return *b 308 } 309 310 func (F *Dense) At(A, B int) float64 { 311 return F.Get(A, B) 312 } 313 314 func (F *Dense) Copy(A Matrix) { 315 ar, ac := A.Dims() 316 fr, fc := F.Dims() 317 if ac > fc || ar > fr { 318 // fmt.Println(ar, ac, fr, fc) 319 panic(gnErrShape) 320 } 321 322 for i := 0; i < ar; i++ { 323 for j := 0; j < ac; j++ { 324 F.Set(i, j, A.At(i, j)) 325 } 326 327 } 328 329 } 330 331 //Returns an array with the data in the ith row of F 332 func (F *Dense) Col(a []float64, i int) []float64 { 333 r, c := F.Dims() 334 if i >= c { 335 panic("Matrix: Requested column out of bounds") 336 } 337 if a == nil { 338 a = make([]float64, r, r) 339 } 340 for j := 0; j < r; j++ { 341 if j >= len(a) { 342 break 343 } 344 a[j] = F.At(j, i) 345 } 346 return a 347 } 348 349 func (F *Dense) Dims() (int, int) { 350 return F.Rows(), F.Cols() 351 } 352 353 //Dot returns the dot product between 2 vectors or matrices 354 func (F *Dense) Dot(B Matrix) float64 { 355 frows, fcols := F.Dims() 356 brows, bcols := B.Dims() 357 if fcols != bcols || frows != brows { 358 panic(gnErrShape) 359 } 360 a, b := F.Dims() 361 A := gnZeros(a, b) 362 A.MulElem(F, B) 363 return A.Sum() 364 } 365 366 //puts the inverse of B in F or panics if F is non-singular. 367 //its just a dirty minor adaptation from the code in go.matrix from John Asmuth 368 //it will be replaced by the gonum implementation when the library is ready. 369 //Notice that this doesn't actually check for errors, the error value returned is always nil. 370 //I just did that crappy fix because this gomatrix-interface should be taken out or deprecated soon. 371 func gnInverse(B Matrix) (*VecMatrix, error) { 372 //fr,fc:=F.Dims() 373 ar, ac := B.Dims() 374 if ac != ar { 375 panic(gnErrSquare) 376 } 377 F := ZeroVecs(ar) 378 var ok bool 379 var A *VecMatrix 380 A, ok = B.(*VecMatrix) 381 if !ok { 382 C, ok := B.(*Dense) 383 if !ok { 384 panic("Few types are allowed so far") 385 } 386 A = &VecMatrix{C} 387 388 } 389 augt, _ := A.Augment(matrix.Eye(ar)) 390 aug := &Dense{augt} 391 augr, _ := aug.Dims() 392 for i := 0; i < augr; i++ { 393 j := i 394 for k := i; k < augr; k++ { 395 if math.Abs(aug.Get(k, i)) > math.Abs(aug.Get(j, i)) { 396 j = k 397 } 398 } 399 if j != i { 400 aug.SwapRows(i, j) 401 } 402 if aug.Get(i, i) == 0 { 403 panic(gnErrSingular) 404 } 405 aug.ScaleRow(i, 1.0/aug.Get(i, i)) 406 for k := 0; k < augr; k++ { 407 if k == i { 408 continue 409 } 410 aug.ScaleAddRow(k, i, -aug.Get(k, i)) 411 } 412 } 413 F.SubMatrix(aug, 0, ac, ar, ac) 414 return F, nil 415 } 416 417 //A slightly modified version of John Asmuth's ParalellProduct function. 418 func (F *Dense) Mul(A, B Matrix) { 419 Arows, Acols := A.Dims() 420 Brows, Bcols := B.Dims() 421 if Acols != Brows { 422 panic(gnErrShape) 423 } 424 if F == nil { 425 F = gnZeros(Arows, Bcols).Dense //I don't know if the final API will allow this. 426 } 427 428 in := make(chan int) 429 quit := make(chan bool) 430 431 dotRowCol := func() { 432 for { 433 select { 434 case i := <-in: 435 sums := make([]float64, Bcols) 436 for k := 0; k < Acols; k++ { 437 for j := 0; j < Bcols; j++ { 438 sums[j] += A.At(i, k) * B.At(k, j) 439 } 440 } 441 for j := 0; j < Bcols; j++ { 442 F.Set(i, j, sums[j]) 443 } 444 case <-quit: 445 return 446 } 447 } 448 } 449 450 threads := 2 451 452 for i := 0; i < threads; i++ { 453 go dotRowCol() 454 } 455 456 for i := 0; i < Arows; i++ { 457 in <- i 458 } 459 460 for i := 0; i < threads; i++ { 461 quit <- true 462 } 463 464 return 465 } 466 467 func (F *Dense) MulElem(A, B Matrix) { 468 arows, acols := A.Dims() 469 brows, bcols := B.Dims() 470 frows, fcols := F.Dims() 471 if arows != brows || acols != bcols || arows != frows || acols != fcols { 472 panic(gnErrShape) 473 } 474 for i := 0; i < arows; i++ { 475 for j := 0; j < acols; j++ { 476 F.Set(i, j, A.At(i, j)*B.At(i, j)) 477 } 478 479 } 480 } 481 482 func (F *Dense) Norm(i float64) float64 { 483 //The parameters is for compatibility 484 return F.TwoNorm() 485 } 486 487 //Returns an array with the data in the ith row of F 488 func (F *Dense) Row(a []float64, i int) []float64 { 489 r, c := F.Dims() 490 if i >= r { 491 panic("Matrix: Requested row out of bounds") 492 } 493 if a == nil { 494 a = make([]float64, c, c) 495 } 496 for j := 0; j < c; j++ { 497 if j >= len(a) { 498 break 499 } 500 a[j] = F.At(i, j) 501 } 502 return a 503 } 504 505 //Scale the matrix A by a number i, putting the result in the received. 506 func (F *Dense) Scale(i float64, A Matrix) { 507 if A == F { //if A and F points to the same object. 508 F.scaleAux(i) 509 } else { 510 F.Copy(A) 511 F.scaleAux(i) 512 } 513 } 514 515 //Puts a view of the given col of the matrix on the receiver 516 func (F *VecMatrix) ColView(i int) *VecMatrix { 517 r := new(Dense) 518 *r = *F.Dense 519 Fr, _ := F.Dims() 520 r.View(0, i, Fr, 1) 521 return &VecMatrix{r} 522 } 523 524 //Returns view of the given vector of the matrix in the receiver 525 func (F *VecMatrix) VecView(i int) *VecMatrix { 526 r := new(Dense) 527 *r = *F.Dense 528 r.View(i, 0, 1, 3) 529 return &VecMatrix{r} 530 } 531 532 //View returns a view of F starting from i,j and spanning r rows and 533 //c columns. Changes in the view are reflected in F and vice-versa 534 //This view has the wrong signature for the interface mat64.Viewer, 535 //But the right signatur was not possible to implement. Notice that very little 536 //memory allocation happens, only a couple of ints and pointers. 537 func (F *VecMatrix) View(i, j, r, c int) *VecMatrix { 538 ret := new(Dense) 539 *ret = *F.Dense 540 ret.View(i, j, r, c) 541 return &VecMatrix{ret} 542 } 543 544 func (F *Dense) scaleAux(factor float64) { 545 fr, fc := F.Dims() 546 for i := 0; i < fr; i++ { 547 for j := 0; j < fc; j++ { 548 F.Set(i, j, F.At(i, j)*factor) 549 } 550 551 } 552 } 553 554 //When go.matrix is abandoned it is necesary to implement SetMatrix 555 //SetMatrix() 556 //Copies A into F aligning A(0,0) with F(i,j) 557 func (F *Dense) SetMatrix(i, j int, A Matrix) { 558 fr, fc := F.Dims() 559 ar, ac := A.Dims() 560 if ar+i > fr || ac+j > fc { 561 panic(gnErrShape) 562 } 563 for l := 0; l < ar; l++ { 564 for m := 0; m < ac; m++ { 565 F.Set(l+i, m+j, A.At(l, m)) 566 } 567 } 568 } 569 570 //puts in F a matrix consisting in A over B 571 func (F *Dense) Stack(A, B Matrix) { 572 Arows, Acols := A.Dims() 573 Brows, Bcols := B.Dims() 574 Frows, Fcols := F.Dims() 575 576 if Acols != Bcols || Acols != Fcols || Arows+Brows != Frows { 577 panic(gnErrShape) 578 } 579 580 for i := 0; i < Arows+Brows; i++ { 581 for j := 0; j < Acols; j++ { 582 if i < Arows { 583 F.Set(i, j, A.At(i, j)) 584 } else { 585 F.Set(i, j, B.At(i-Arows, j)) 586 } 587 } 588 } 589 590 return 591 } 592 593 //Subtracts the matrix B from A putting the result in F 594 func (F *Dense) Sub(A, B Matrix) { 595 ar, ac := A.Dims() 596 br, bc := B.Dims() 597 fr, fc := F.Dims() 598 if ac != bc || br != ar || ac != fc || ar != fr { 599 panic(gnErrShape) 600 } 601 for i := 0; i < fr; i++ { 602 for j := 0; j < fc; j++ { 603 F.Set(i, j, A.At(i, j)-B.At(i, j)) 604 } 605 } 606 607 } 608 609 //not tested 610 //returns a copy of the submatrix of A starting by the point i,j and 611 //spanning rows rows and cols columns. 612 func (F *Dense) SubMatrix(A *Dense, i, j, rows, cols int) { 613 temp := Dense{A.GetMatrix(i, j, rows, cols)} 614 F.Copy(&temp) 615 } 616 617 //Sum returns the sum of all elements in matrix A. 618 func (F *Dense) Sum() float64 { 619 Rows, Cols := F.Dims() 620 var sum float64 621 for i := 0; i < Cols; i++ { 622 for j := 0; j < Rows; j++ { 623 sum += F.Get(j, i) 624 } 625 } 626 return sum 627 } 628 629 //Transpose 630 func (F *Dense) TCopy(A Matrix) { 631 ar, ac := A.Dims() 632 fr, fc := F.Dims() 633 if ar != fc || ac != fr { 634 panic(gnErrShape) 635 } 636 var B *Dense 637 var ok bool 638 if B, ok = A.(*Dense); !ok { 639 if C, ok := A.(*VecMatrix); ok { 640 B = C.Dense 641 } else if D, ok := A.(*chemDense); ok { 642 B = D.Dense 643 } else { 644 panic("Only Dense, chemDense and VecMatrix are currently accepted") 645 } 646 } 647 //we do it in a different way if you pass the received as the argument 648 //(transpose in place) We could use continue for i==j 649 if F == B { 650 /* for i := 0; i < ar; i++ { 651 for j := 0; j < i; j++ { 652 tmp := A.At(i, j) 653 F.Set(i, j, A.At(j, i)) 654 F.Set(j, i, tmp) 655 } 656 */F.TransposeInPlace() 657 } else { 658 F.DenseMatrix = B.Transpose() 659 /* 660 for i := 0; i < ar; i++ { 661 for j := 0; j < ac; j++ { 662 F.Set(j, i, A.At(i, j)) 663 } 664 } 665 */ 666 } 667 } 668 669 //Unit takes a vector and divides it by its norm 670 //thus obtaining an unitary vector pointing in the same direction as 671 //vector. 672 func (F *Dense) Unit(A NormerMatrix) { 673 norm := 1.0 / A.Norm(2) 674 F.Scale(norm, A) 675 } 676 677 func (F *Dense) View(i, j, rows, cols int) { 678 F.DenseMatrix = F.GetMatrix(i, j, rows, cols) 679 } 680 681 /**These are from the current proposal for gonum, by Dan Kortschak. It will be taken out 682 * from here when gonum is implemented. The gn prefix is appended to the names to make them 683 * unimported and to allow easy use of search/replace to add the "num" prefix when I change to 684 * gonum.**/ 685 686 // A Panicker is a function that may panic. 687 type gnPanicker func() 688 689 // Maybe will recover a panic with a type matrix.Error from fn, and return this error. 690 // Any other error is re-panicked. 691 func gnMaybe(fn gnPanicker) (err error) { 692 defer func() { 693 if r := recover(); r != nil { 694 var ok bool 695 if err, ok = r.(gnError); ok { 696 return 697 } 698 panic(r) 699 } 700 }() 701 fn() 702 return 703 } 704 705 // Type Error represents matrix package errors. These errors can be recovered by Maybe wrappers. 706 type gnError string 707 708 func (err gnError) Error() string { return string(err) } 709 710 const ( 711 //RM 712 not3xXMatrix = gnError("matrix: The other dimmension should be 3") 713 notOrthogonal = gnError("matrix: Vectors nor orthogonal") 714 notEnoughElements = gnError("matrix: not enough elements") 715 //end RM 716 gnErrIndexOutOfRange = gnError("matrix: index out of range") 717 gnErrZeroLength = gnError("matrix: zero length in matrix definition") 718 gnErrRowLength = gnError("matrix: row length mismatch") 719 gnErrColLength = gnError("matrix: col length mismatch") 720 gnErrSquare = gnError("matrix: expect square matrix") 721 gnErrNormOrder = gnError("matrix: invalid norm order for matrix") 722 gnErrSingular = gnError("matrix: matrix is singular") 723 gnErrShape = gnError("matrix: dimension mismatch") 724 gnErrIllegalStride = gnError("matrix: illegal stride") 725 gnErrPivot = gnError("matrix: malformed pivot list") 726 )