github.com/rmera/gochem@v0.7.1/handy.go (about) 1 /* 2 * handy.go, part of gochem. 3 * 4 * 5 * Copyright 2012 Raul Mera <rmera{at}chemDOThelsinkiDOTfi> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU Lesser General Public License as 9 * published by the Free Software Foundation; either version 2.1 of the 10 * License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General 18 * Public License along with this program. If not, see 19 * <http://www.gnu.org/licenses/>. 20 * 21 * 22 * Gochem is developed at the laboratory for instruction in Swedish, Department of Chemistry, 23 * University of Helsinki, Finland. 24 * 25 * 26 */ 27 28 package chem 29 30 import ( 31 "fmt" 32 "math" 33 "strings" 34 35 v3 "github.com/rmera/gochem/v3" 36 ) 37 38 // NegateIndexes, given a set of indexes and the length of a molecule, produces 39 // a set of all the indexes _not_ in the original set. 40 func NegateIndexes(indexes []int, length int) []int { 41 ret := make([]int, 0, length-len(indexes)) 42 for i := 0; i < length; i++ { 43 if !isInInt(indexes, i) { 44 ret = append(ret, i) 45 46 } 47 } 48 return ret 49 } 50 51 const allchains = "*ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 52 53 // FixGromacsPDB fixes the problem that Gromacs PDBs have when there are more than 10000 residues 54 // Gromacs simply restarts the numbering. Since solvents (where this is likely to happen) don't have 55 // chain ID in Gromacs, it's impossible to distinguish between the water 1 and the water 10001. FixGromacsPDB 56 // Adds a chain ID to the newly restrated residue that is the letter/symbol coming after the last seen chain ID 57 // in the constant allchains defined in this file. The current implementation does nothing if a chain ID is already 58 // defined, even if it is wrong (if 9999 and the following 0 residue have the same chain). 59 func FixGromacsPDB(mol Atomer) { 60 // fmt.Println("FIXING!") 61 previd := 999999 62 lastchain := "*" 63 for i := 0; i < mol.Len(); i++ { 64 at := mol.Atom(i) 65 if at.Chain == " " { 66 if previd > at.MolID { 67 index := strings.Index(allchains, lastchain) + 1 68 // fmt.Println("new chain index:", index) 69 lastchain = string(allchains[index]) 70 } 71 at.Chain = lastchain 72 // fmt.Println(lastchain) ///////// 73 } else { 74 lastchain = at.Chain 75 } 76 previd = at.MolID 77 } 78 } 79 80 // Molecules2Atoms gets a selection list from a list of residues. 81 // It select all the atoms that form part of the residues in the list. 82 // It doesnt return errors. If a residue is out of range, no atom will 83 // be returned for it. Atoms are also required to be part of one of the chains 84 // specified in chains, but a nil "chains" can be given to select all chains. 85 func Molecules2Atoms(mol Atomer, residues []int, chains []string) []int { 86 atlist := make([]int, 0, len(residues)*3) 87 for key := 0; key < mol.Len(); key++ { 88 at := mol.Atom(key) 89 if isInInt(residues, at.MolID) && (isInString(chains, at.Chain) || len(chains) == 0) { 90 atlist = append(atlist, key) 91 } 92 } 93 return atlist 94 95 } 96 97 // EasyShape takes a matrix of coordinates, a value for epsilon (a number close to 98 // zero, the closer, the more 99 // strict the orthogonality requriements are) and an (optative) masser and returns 100 // two shape indicators based on the elipsoid of inertia (or it massless equivalent) 101 // a linear and circular distortion indicators, and an error or nil (in that order). 102 // if you give a negative number as epsilon, the default (quite strict) will be used. 103 func EasyShape(coords *v3.Matrix, epsilon float64, mol ...Masser) (float64, float64, error) { 104 var masses []float64 105 var err2 error 106 var err error 107 if len(mol) < 0 { 108 masses = nil 109 } else { 110 masses, err = mol[0].Masses() 111 if err != nil { 112 masses = nil 113 err2 = err 114 } 115 } 116 moment, err := MomentTensor(coords, masses) 117 if err != nil { 118 return -1, -1, err 119 } 120 rhos, err := Rhos(moment, epsilon) 121 if err != nil { 122 return -1, -1, err 123 } 124 linear, circular, err := RhoShapeIndexes(rhos) 125 if err != nil { 126 return -1, -1, err 127 } 128 return linear, circular, err2 129 } 130 131 // MolIDNameChain2Index takes a molID (residue number), atom name, chain index and a molecule Atomer. 132 // it returns the index associated with the atom in question in the Ref. The function returns also an error (if failure of warning) 133 // or nil (if succses and no warnings). Note that this function is not efficient to call several times to retrieve many atoms. 134 func MolIDNameChain2Index(mol Atomer, molID int, name, chain string) (int, error) { 135 var ret int = -1 136 var err error 137 if mol == nil { 138 return -1, CError{"goChem: Given a nil chem.Atomer", []string{"MolIDNameChain2Index"}} 139 } 140 for i := 0; i != mol.Len(); i++ { 141 a := mol.Atom(i) 142 if a.Name == "" && err == nil { 143 err = CError{"Warning: The Atoms does not seem to contain PDB-type information", []string{"MolIDNameChain2Index"}} //We set this error but will still keep running the function in case the data is present later in the molecule. 144 } 145 if a.MolID == molID && a.Name == name && a.Chain == chain { 146 ret = i 147 break 148 } 149 150 } 151 if ret == -1 { 152 var p string 153 if err != nil { 154 p = err.Error() 155 } 156 err = CError{fmt.Sprintf("%s, No atomic index found in the Atomer given for the given MolID, atom name and chain. %s %d", p, chain, molID), []string{"MolIDNameChain2Index"}} 157 } 158 return ret, err 159 } 160 161 // OnesMass returns a column matrix with lenght rosw. 162 // This matrix can be used as a dummy mass matrix 163 // for geometric calculations. 164 func OnesMass(lenght int) *v3.Matrix { 165 return v3.Dense2Matrix(gnOnes(lenght, 1)) 166 } 167 168 // Super determines the best rotation and translations to superimpose the coords in test 169 // considering only the atoms present in the slices of int slices indexes. 170 // The first indexes slices will be assumed to contain test indexes and the second, template indexes. 171 // If you give only one, it will be assumed to correspond to test, if test has more atoms than 172 // elements on the indexes set, or templa, otherwise. If no indexes are given, all atoms on each system 173 // will be superimposed. The number of atoms superimposed on both systems must be equal. 174 // Super modifies the test matrix, but template and indexes are not touched. 175 func Super(test, templa *v3.Matrix, indexes ...[]int) (*v3.Matrix, error) { 176 var ctest *v3.Matrix 177 var ctempla *v3.Matrix 178 if len(indexes) == 0 || indexes[0] == nil || len(indexes[0]) == 0 { //If you put the date in the SECOND slice, you are just messing with me. 179 ctest = test 180 ctempla = templa 181 } else if len(indexes) == 1 { 182 if test.NVecs() > len(indexes[0]) { 183 ctest = v3.Zeros(len(indexes[0])) 184 ctest.SomeVecs(test, indexes[0]) 185 ctempla = templa 186 } else if templa.NVecs() > len(indexes[0]) { 187 ctempla = v3.Zeros(len(indexes[0])) 188 ctempla.SomeVecs(templa, indexes[0]) 189 } else { 190 return nil, fmt.Errorf("chem.Super: Indexes don't match molecules") 191 } 192 } else { 193 ctest = v3.Zeros(len(indexes[0])) 194 ctest.SomeVecs(test, indexes[0]) 195 ctempla = v3.Zeros(len(indexes[1])) 196 ctempla.SomeVecs(templa, indexes[1]) 197 } 198 199 if ctest.NVecs() != ctempla.NVecs() { 200 return nil, fmt.Errorf("chem.Super: Ill formed coordinates for Superposition") 201 } 202 203 _, rotation, trans1, trans2, err1 := RotatorTranslatorToSuper(ctest, ctempla) 204 if err1 != nil { 205 return nil, errDecorate(err1, "Super") 206 } 207 test.AddVec(test, trans1) 208 // fmt.Println("test1",test, rotation) /////////////77 209 test.Mul(test, rotation) 210 // fmt.Println("test2",test) /////////// 211 test.AddVec(test, trans2) 212 // fmt.Println("test3",test) /////// 213 return test, nil 214 } 215 216 // RotateAbout about rotates the coordinates in coordsorig around by angle radians around the axis 217 // given by the vector axis. It returns the rotated coordsorig, since the original is not affected. 218 // Uses Clifford algebra. 219 func RotateAbout(coordsorig, ax1, ax2 *v3.Matrix, angle float64) (*v3.Matrix, error) { 220 coordsLen := coordsorig.NVecs() 221 coords := v3.Zeros(coordsLen) 222 translation := v3.Zeros(ax1.NVecs()) 223 translation.Copy(ax1) 224 axis := v3.Zeros(ax2.NVecs()) 225 axis.Sub(ax2, ax1) // the rotation axis 226 f := func() { coords.SubVec(coordsorig, translation) } 227 if err := gnMaybe(gnPanicker(f)); err != nil { 228 return nil, CError{err.Error(), []string{"v3.Matrix.SubVec", "RotateAbout"}} 229 } 230 Rot := v3.Zeros(coordsLen) 231 Rot = Rotate(coords, Rot, axis, angle) 232 g := func() { Rot.AddVec(Rot, translation) } 233 if err := gnMaybe(gnPanicker(g)); err != nil { 234 return nil, CError{err.Error(), []string{"v3.Matrix.AddVec", "RotateAbout"}} 235 236 } 237 return Rot, nil 238 } 239 240 // EulerRotateAbout uses Euler angles to rotate the coordinates in coordsorig around by angle 241 // radians around the axis given by the vector axis. It returns the rotated coordsorig, 242 // since the original is not affected. It seems more clunky than the RotateAbout, which uses Clifford algebra. 243 // I leave it for benchmark, mostly, and might remove it later. There is no test for this function! 244 func EulerRotateAbout(coordsorig, ax1, ax2 *v3.Matrix, angle float64) (*v3.Matrix, error) { 245 r, _ := coordsorig.Dims() 246 coords := v3.Zeros(r) 247 translation := v3.Zeros(ax1.NVecs()) 248 translation.Copy(ax1) 249 axis := v3.Zeros(ax2.NVecs()) 250 axis.Sub(ax2, ax1) //now it became the rotation axis 251 f := func() { coords.SubVec(coordsorig, translation) } 252 if err := gnMaybe(gnPanicker(f)); err != nil { 253 return nil, CError{err.Error(), []string{"v3.Matrix.Subvec", "EulerRotateAbout"}} 254 255 } 256 Zswitch := RotatorToNewZ(axis) 257 coords.Mul(coords, Zswitch) //rotated 258 Zrot, err := RotatorAroundZ(angle) 259 if err != nil { 260 return nil, errDecorate(err, "EulerRotateAbout") 261 } 262 // Zsr, _ := Zswitch.Dims() 263 // RevZ := v3.Zeros(Zsr) 264 RevZ, err := gnInverse(Zswitch, nil) 265 if err != nil { 266 return nil, errDecorate(err, "EulerRotateAbout") 267 } 268 coords.Mul(coords, Zrot) //rotated 269 coords.Mul(coords, RevZ) 270 coords.AddVec(coords, translation) 271 return coords, nil 272 } 273 274 // Corrupted is a convenience function to check that a reference and a trajectory have the same number of atoms 275 func Corrupted(X Traj, R Atomer) error { 276 if X.Len() != R.Len() { 277 return CError{"Mismatched number of atoms/coordinates", []string{"Corrupted"}} 278 } 279 return nil 280 } 281 282 //Some internal convenience functions. 283 284 // isInInt is a helper for the RamaList function, 285 // returns true if test is in container, false otherwise. 286 func isInInt(container []int, test int) bool { 287 if container == nil { 288 return false 289 } 290 for _, i := range container { 291 if test == i { 292 return true 293 } 294 } 295 return false 296 } 297 298 // Same as the previous, but with strings. 299 func isInString(container []string, test string) bool { 300 if container == nil { 301 return false 302 } 303 for _, i := range container { 304 if test == i { 305 return true 306 } 307 } 308 return false 309 } 310 311 // MakeWater Creates a water molecule at distance Angstroms from a2, in a direction that is angle radians from the axis defined by a1 and a2. 312 // Notice that the exact position of the water is not well defined when angle is not zero. One can always use the RotateAbout 313 // function to move the molecule to the desired location. If oxygen is true, the oxygen will be pointing to a2. Otherwise, 314 // one of the hydrogens will. 315 func MakeWater(a1, a2 *v3.Matrix, distance, angle float64, oxygen bool) *v3.Matrix { 316 water := v3.Zeros(3) 317 const WaterOHDist = 0.96 318 const WaterAngle = 52.25 319 const deg2rad = 0.0174533 320 w := water.VecView(0) //we first set the O coordinates 321 w.Copy(a2) 322 w.Sub(w, a1) 323 w.Unit(w) 324 dist := v3.Zeros(1) 325 dist.Sub(a1, a2) 326 a1a2dist := dist.Norm(2) 327 // fmt.Println("ala2dist", a1a2dist, distance) ////////////////7777 328 w.Scale(distance+a1a2dist, w) 329 w.Add(w, a1) 330 for i := 0; i <= 1; i++ { 331 o := water.VecView(0) 332 w = water.VecView(i + 1) 333 w.Copy(o) 334 // fmt.Println("w1", w) //////// 335 w.Sub(w, a2) 336 // fmt.Println("w12", w) /////////////// 337 w.Unit(w) 338 // fmt.Println("w4", w) 339 w.Scale(WaterOHDist+distance, w) 340 // fmt.Println("w3", w, WaterOHDist, distance) 341 o.Sub(o, a2) 342 t, _ := v3.NewMatrix([]float64{0, 0, 1}) 343 upp := v3.Zeros(1) 344 upp.Cross(w, t) 345 // fmt.Println("upp", upp, w, t) 346 upp.Add(upp, o) 347 upp.Add(upp, a2) 348 //water.SetMatrix(3,0,upp) 349 w.Add(w, a2) 350 o.Add(o, a2) 351 sign := 1.0 352 if i == 1 { 353 sign = -1.0 354 } 355 temp, _ := RotateAbout(w, o, upp, deg2rad*WaterAngle*sign) 356 w.SetMatrix(0, 0, temp) 357 } 358 var v1, v2 *v3.Matrix 359 if angle != 0 { 360 v1 = v3.Zeros(1) 361 v2 = v3.Zeros(1) 362 v1.Sub(a2, a1) 363 v2.Copy(v1) 364 v2.Set(0, 2, v2.At(0, 2)+1) //a "random" modification. The idea is that its not colinear with v1 365 v3 := cross(v1, v2) 366 v3.Add(v3, a2) 367 water, _ = RotateAbout(water, a2, v3, angle) 368 } 369 if oxygen { 370 return water 371 } 372 //we move things so an hydrogen points to a2 and modify the distance acordingly. 373 e1 := water.VecView(0) 374 e2 := water.VecView(1) 375 e3 := water.VecView(2) 376 if v1 == nil { 377 v1 = v3.Zeros(1) 378 } 379 if v2 == nil { 380 v2 = v3.Zeros(1) 381 } 382 v1.Sub(e2, e1) 383 v2.Sub(e3, e1) 384 axis := cross(v1, v2) 385 axis.Add(axis, e1) 386 water, _ = RotateAbout(water, e1, axis, deg2rad*(180-WaterAngle)) 387 v1.Sub(e1, a2) 388 v1.Unit(v1) 389 v1.Scale(WaterOHDist, v1) 390 water.AddVec(water, v1) 391 return water 392 } 393 394 // FixNumbering will put the internal numbering+1 in the atoms and residue fields, so they match the current residues/atoms 395 // in the molecule 396 func FixNumbering(r Atomer) { 397 resid := 0 398 prevres := -1 399 for i := 0; i < r.Len(); i++ { 400 at := r.Atom(i) 401 at.ID = i + 1 402 if prevres != at.MolID { 403 prevres = at.MolID 404 resid++ 405 } 406 at.MolID = resid 407 } 408 } 409 410 // CutBackRef takes a list of lists of residues and selects 411 // from r all atoms in each the list list[i] and belonging to the chain chain[i]. 412 // It caps the N and C terminal 413 // of each list with -COH for the N terminal and NH2 for C terminal. 414 // the residues on each sublist should be contiguous to each other. 415 // for instance, {6,7,8} is a valid sublist, {6,8,9} is not. 416 // This is NOT currently checked by the function!. It returns the list of kept atoms 417 func CutBackRef(r Atomer, chains []string, list [][]int) ([]int, error) { 418 //i:=r.Len() 419 if len(chains) != len(list) { 420 return nil, CError{fmt.Sprintf("Mismatched chains (%d) and list (%d) slices", len(chains), len(list)), []string{"CutBackRef"}} 421 } 422 var ret []int //This will be filled with the atoms that are kept, and will be returned. 423 for k, v := range list { 424 nter := v[0] 425 cter := v[len(v)-1] 426 nresname := "" 427 cresname := "" 428 for j := 0; j < r.Len(); j++ { 429 if r.Atom(j).MolID == nter && r.Atom(j).Chain == chains[k] { 430 nresname = r.Atom(j).MolName 431 break 432 } 433 } 434 if nresname == "" { 435 //we will protest if the Nter is not found. If Cter is not found we will just 436 //cut at the real Cter 437 return nil, CError{fmt.Sprintf("list %d contains residue numbers out of boundaries", k), []string{"CutBackRef"}} 438 439 } 440 for j := 0; j < r.Len(); j++ { 441 curr := r.Atom(j) 442 if curr.Chain != chains[k] { 443 continue 444 } 445 if curr.MolID == cter { 446 cresname = curr.MolName 447 } 448 if curr.MolID == nter-1 { 449 makeNcap(curr, nresname) 450 } 451 if curr.MolID == cter+1 { 452 makeCcap(curr, cresname) 453 } 454 } 455 } 456 for _, i := range list { 457 t := Molecules2Atoms(r, i, chains) 458 // fmt.Println("t", len(t)) 459 ret = append(ret, t...) 460 } 461 // j:=0 462 // for i:=0;;i++{ 463 // index:=i-j 464 // if index>=r.Len(){ 465 // break 466 // } 467 // if !isInInt(ret, i){ 468 // r.DelAtom(index) 469 // j++ 470 // } 471 // } 472 return ret, nil 473 } 474 475 func makeNcap(at *Atom, resname string) { 476 if !isInString([]string{"C", "O", "CA"}, at.Name) { 477 return 478 } 479 at.MolID = at.MolID + 1 480 at.MolName = resname 481 if at.Name == "C" { 482 at.Name = "CTZ" 483 } 484 if at.Name == "CA" { 485 at.Name = "HCZ" 486 at.Symbol = "H" 487 } 488 } 489 490 func makeCcap(at *Atom, resname string) { 491 if !isInString([]string{"N", "H", "CA"}, at.Name) { 492 return 493 } 494 at.MolID = at.MolID - 1 495 at.MolName = resname 496 if at.Name == "N" { 497 at.Name = "NTZ" 498 } 499 if at.Name == "CA" { 500 at.Name = "HNZ" 501 at.Symbol = "H" 502 } 503 } 504 505 /* 506 //Takes a list of lists of residues and produces a new set of coordinates 507 //whitout any atom not in the lists or not from the chain chain. It caps the N and C terminal 508 //of each list with -COH for the N terminal and NH2 for C terminal. 509 //the residues on each sublist should be contiguous to each other. 510 //for instance, {6,7,8} is a valid sublist, {6,8,9} is not. 511 //This is NOT currently checked by the function! 512 //In addition, the Ref provided should have already been processed by 513 //CutBackRef, which is not checked either. 514 func CutBackCoords(r Ref, coords *v3.Matrix, chain string, list [][]int) (*v3.Matrix, error) { 515 //this is actually a really silly function. So far I dont check for errors, but I keep the return balue 516 //In case I do later. 517 var biglist []int 518 for _, i := range list { 519 smallist := Molecules2Atoms(r, i, []string{chain}) 520 biglist = append(biglist, smallist...) 521 } 522 NewVecs := v3.Zeros(len(biglist), 3) 523 NewVecs.SomeVecs(coords, biglist) 524 return NewVecs, nil 525 526 } 527 */ 528 529 // CutLateralRef will return a list with the atom indexes of the lateral chains of the residues in list 530 // for each of these residues it will change the alpha carbon to oxygen and change the residue number of the rest 531 // of the backbone to -1. 532 func CutBetaRef(r Atomer, chain []string, list []int) []int { 533 // pairs := make([][]int,1,10) 534 // pairs[0]=make([]int,0,2) 535 for i := 0; i < r.Len(); i++ { 536 curr := r.Atom(i) 537 if isInInt(list, curr.MolID) && isInString(chain, curr.Chain) { 538 if curr.Name == "CB" { 539 // pairs[len(pairs)-1][1]=i //I am assuming that CA will show before CB in the PDB, which is rather weak 540 // paairs=append(pairs,make([]int,1,2)) 541 } 542 if curr.Name == "CA" { 543 curr.Name = "HB4" 544 curr.Symbol = "H" 545 // pairs[len(pairs)-1]=append(pairs[len(pairs)-1],i) 546 } else if isInString([]string{"C", "H", "HA", "O", "N"}, curr.Name) { //change the res number of the backbone so it is not considered 547 curr.MolID = -1 548 } 549 550 } 551 } 552 newlist := Molecules2Atoms(r, list, chain) 553 return newlist 554 } 555 556 // CutAlphaRef will return a list with the atoms in the residues indicated by list, in the chains given. 557 // The carbonyl carbon and amide nitrogen for each residue will be transformer into hydrogens. The MolID of the 558 // other backbone atoms will be set to -1 so they are no longer considered. 559 func CutAlphaRef(r Atomer, chain []string, list []int) []int { 560 for i := 0; i < r.Len(); i++ { 561 curr := r.Atom(i) 562 if isInInt(list, curr.MolID) && isInString(chain, curr.Chain) { 563 if curr.Name == "C" { 564 curr.Name = "HA2" 565 curr.Symbol = "H" 566 } else if curr.Name == "N" { 567 curr.Name = "HA3" 568 curr.Symbol = "H" 569 } else if isInString([]string{"H", "O"}, curr.Name) { //change the res number of the backbone so it is not considered 570 curr.MolID = -1 571 } 572 573 } 574 } 575 newlist := Molecules2Atoms(r, list, chain) 576 return newlist 577 } 578 579 // TagAtomsByName will tag all atoms with a given name in a given list of atoms. 580 // return the number of tagged atoms 581 func TagAtomsByName(r Atomer, name string, list []int) int { 582 tag := 0 583 for i := 0; i < r.Len(); i++ { 584 curr := r.Atom(i) 585 if isInInt(list, i) && curr.Name == name { 586 curr.Tag = 1 587 tag++ 588 } 589 } 590 return tag 591 } 592 593 // ScaleBonds scales all bonds between atoms in the same residue with names n1, n2 to a final lenght finallengt, by moving the atoms n2. 594 // the o<ration is executed in place. 595 func ScaleBonds(coords *v3.Matrix, mol Atomer, n1, n2 string, finallenght float64) { 596 for i := 0; i < mol.Len(); i++ { 597 c1 := mol.Atom(i) 598 if c1.Name != n1 { 599 continue 600 } 601 for j := 0; j < mol.Len(); j++ { 602 c2 := mol.Atom(j) 603 if c1.MolID == c2.MolID && c1.Name == n1 && c2.Name == n2 { 604 A := coords.VecView(i) 605 B := coords.VecView(j) 606 ScaleBond(A, B, finallenght) 607 } 608 } 609 } 610 } 611 612 // ScaleBond takes a C-H bond and moves the H (in place) so the distance between them is the one given (bond). 613 // CAUTION: I have only tested it for the case where the original distance>bond, although I expect it to also work in the other case. 614 func ScaleBond(C, H *v3.Matrix, bond float64) { 615 Odist := v3.Zeros(1) 616 Odist.Sub(H, C) 617 distance := Odist.Norm(2) 618 Odist.Scale((distance-bond)/distance, Odist) 619 H.Sub(H, Odist) 620 } 621 622 // Merges A and B in a single topology which is returned 623 func MergeAtomers(A, B Atomer) *Topology { 624 al := A.Len() 625 l := al + B.Len() 626 full := make([]*Atom, l, l) 627 for k, _ := range full { 628 if k < al { 629 full[k] = A.Atom(k) 630 } else { 631 full[k] = B.Atom(k - al) 632 } 633 } 634 a, aok := A.(AtomMultiCharger) 635 b, bok := B.(AtomMultiCharger) 636 var charge, multi int 637 if aok && bok { 638 charge = a.Charge() + b.Charge() 639 multi = (a.Multi() - 1 + b.Multi()) //Not TOO sure about this. 640 } else { 641 multi = 1 642 } 643 return NewTopology(charge, multi, full) 644 } 645 646 // SelCone, Given a set of cartesian points in sellist, obtains a vector "plane" normal to the best plane passing through the points. 647 // It selects atoms from the set A that are inside a cone in the direction of "plane" that starts from the geometric center of the cartesian points, 648 // and has an angle of angle (radians), up to a distance distance. The cone is approximated by a set of radius-increasing cilinders with height thickness. 649 // If one starts from one given point, 2 cgnOnes, one in each direction, are possible. If whatcone is 0, both cgnOnes are considered. 650 // if whatcone<0, only the cone opposite to the plane vector direction. If whatcone>0, only the cone in the plane vector direction. 651 // the 'initial' argument allows the construction of a truncate cone with a radius of initial. 652 func SelCone(B, selection *v3.Matrix, angle, distance, thickness, initial float64, whatcone int) []int { 653 A := v3.Zeros(B.NVecs()) 654 A.Copy(B) //We will be altering the input so its better to work with a copy. 655 ar, _ := A.Dims() 656 selected := make([]int, 0, 3) 657 neverselected := make([]int, 0, 30000) //waters that are too far to ever be selected 658 nevercutoff := distance / math.Cos(angle) //cutoff to be added to neverselected 659 A, _, err := MassCenter(A, selection, nil) //Centrate A in the geometric center of the selection, Its easier for the following calculations 660 if err != nil { 661 panic(PanicMsg(err.Error())) 662 } 663 selection, _, _ = MassCenter(selection, selection, nil) //Centrate the selection as well 664 plane, err := BestPlane(selection, nil) //I have NO idea which direction will this vector point. We might need its negative. 665 if err != nil { 666 panic(PanicMsg(err.Error())) 667 } 668 for i := thickness / 2; i <= distance; i += thickness { 669 maxdist := math.Tan(angle)*i + initial //this should give me the radius of the cone at this point 670 for j := 0; j < ar; j++ { 671 if isInInt(selected, j) || isInInt(neverselected, j) { //we dont scan things that we have already selected, or are too far 672 continue 673 } 674 atom := A.VecView(j) 675 proj := Projection(atom, plane) 676 norm := proj.Norm(2) 677 //Now at what side of the plane is the atom? 678 angle := Angle(atom, plane) 679 if whatcone > 0 { 680 if angle > math.Pi/2 { 681 continue 682 } 683 } else if whatcone < 0 { 684 if angle < math.Pi/2 { 685 continue 686 } 687 } 688 if norm > i+(thickness/2.0) || norm < (i-thickness/2.0) { 689 continue 690 } 691 proj.Sub(proj, atom) 692 projnorm := proj.Norm(2) 693 if projnorm <= maxdist { 694 selected = append(selected, j) 695 } 696 if projnorm >= nevercutoff { 697 neverselected = append(neverselected, j) 698 } 699 } 700 } 701 return selected 702 }